Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /**
6 * Synchronous front-end for the JavaScript OS.File library.
7 * Unix implementation.
8 *
9 * This front-end is meant to be imported by a worker thread.
10 */
12 {
13 if (typeof Components != "undefined") {
14 // We do not wish osfile_unix_front.jsm to be used directly as a main thread
15 // module yet.
17 throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet");
18 }
19 (function(exports) {
20 "use strict";
22 // exports.OS.Unix is created by osfile_unix_back.jsm
23 if (exports.OS && exports.OS.File) {
24 return; // Avoid double-initialization
25 }
27 let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
28 let Path = require("resource://gre/modules/osfile/ospath.jsm");
29 let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
30 exports.OS.Unix.File._init();
31 let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end");
32 let Const = SharedAll.Constants.libc;
33 let UnixFile = exports.OS.Unix.File;
34 let Type = UnixFile.Type;
36 /**
37 * Representation of a file.
38 *
39 * You generally do not need to call this constructor yourself. Rather,
40 * to open a file, use function |OS.File.open|.
41 *
42 * @param fd A OS-specific file descriptor.
43 * @param {string} path File path of the file handle, used for error-reporting.
44 * @constructor
45 */
46 let File = function File(fd, path) {
47 exports.OS.Shared.AbstractFile.call(this, fd, path);
48 this._closeResult = null;
49 };
50 File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
52 /**
53 * Close the file.
54 *
55 * This method has no effect if the file is already closed. However,
56 * if the first call to |close| has thrown an error, further calls
57 * will throw the same error.
58 *
59 * @throws File.Error If closing the file revealed an error that could
60 * not be reported earlier.
61 */
62 File.prototype.close = function close() {
63 if (this._fd) {
64 let fd = this._fd;
65 this._fd = null;
66 // Call |close(fd)|, detach finalizer if any
67 // (|fd| may not be a CDataFinalizer if it has been
68 // instantiated from a controller thread).
69 let result = UnixFile._close(fd);
70 if (typeof fd == "object" && "forget" in fd) {
71 fd.forget();
72 }
73 if (result == -1) {
74 this._closeResult = new File.Error("close", ctypes.errno, this._path);
75 }
76 }
77 if (this._closeResult) {
78 throw this._closeResult;
79 }
80 return;
81 };
83 /**
84 * Read some bytes from a file.
85 *
86 * @param {C pointer} buffer A buffer for holding the data
87 * once it is read.
88 * @param {number} nbytes The number of bytes to read. It must not
89 * exceed the size of |buffer| in bytes but it may exceed the number
90 * of bytes unread in the file.
91 * @param {*=} options Additional options for reading. Ignored in
92 * this implementation.
93 *
94 * @return {number} The number of bytes effectively read. If zero,
95 * the end of the file has been reached.
96 * @throws {OS.File.Error} In case of I/O error.
97 */
98 File.prototype._read = function _read(buffer, nbytes, options = {}) {
99 // Populate the page cache with data from a file so the subsequent reads
100 // from that file will not block on disk I/O.
101 if (typeof(UnixFile.posix_fadvise) === 'function' &&
102 (options.sequential || !("sequential" in options))) {
103 UnixFile.posix_fadvise(this.fd, 0, nbytes,
104 OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
105 }
106 return throw_on_negative("read",
107 UnixFile.read(this.fd, buffer, nbytes),
108 this._path
109 );
110 };
112 /**
113 * Write some bytes to a file.
114 *
115 * @param {C pointer} buffer A buffer holding the data that must be
116 * written.
117 * @param {number} nbytes The number of bytes to write. It must not
118 * exceed the size of |buffer| in bytes.
119 * @param {*=} options Additional options for writing. Ignored in
120 * this implementation.
121 *
122 * @return {number} The number of bytes effectively written.
123 * @throws {OS.File.Error} In case of I/O error.
124 */
125 File.prototype._write = function _write(buffer, nbytes, options = {}) {
126 return throw_on_negative("write",
127 UnixFile.write(this.fd, buffer, nbytes),
128 this._path
129 );
130 };
132 /**
133 * Return the current position in the file.
134 */
135 File.prototype.getPosition = function getPosition(pos) {
136 return this.setPosition(0, File.POS_CURRENT);
137 };
139 /**
140 * Change the current position in the file.
141 *
142 * @param {number} pos The new position. Whether this position
143 * is considered from the current position, from the start of
144 * the file or from the end of the file is determined by
145 * argument |whence|. Note that |pos| may exceed the length of
146 * the file.
147 * @param {number=} whence The reference position. If omitted
148 * or |OS.File.POS_START|, |pos| is relative to the start of the
149 * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
150 * current position in the file. If |OS.File.POS_END|, |pos| is
151 * relative to the end of the file.
152 *
153 * @return The new position in the file.
154 */
155 File.prototype.setPosition = function setPosition(pos, whence) {
156 if (whence === undefined) {
157 whence = Const.SEEK_SET;
158 }
159 return throw_on_negative("setPosition",
160 UnixFile.lseek(this.fd, pos, whence),
161 this._path
162 );
163 };
165 /**
166 * Fetch the information on the file.
167 *
168 * @return File.Info The information on |this| file.
169 */
170 File.prototype.stat = function stat() {
171 throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr),
172 this._path);
173 return new File.Info(gStatData, this._path);
174 };
176 /**
177 * Set the file's access permissions. Without any options, the
178 * permissions are set to an approximation of what they would
179 * have been if the file had been created in its current
180 * directory in the "most typical" fashion for the operating
181 * system. In the current implementation, this means we set
182 * the POSIX file mode to (0666 & ~umask).
183 *
184 * This operation is likely to fail if applied to a file that was
185 * not created by the currently running program (more precisely,
186 * if it was created by a program running under a different OS-level
187 * user account). It may also fail, or silently do nothing, if the
188 * filesystem containing the file does not support access permissions.
189 *
190 * @param {*=} options
191 * - {number} unixMode If present, the POSIX file mode is set to
192 * exactly this value, unless |unixHonorUmask| is
193 * also present.
194 * - {bool} unixHonorUmask If true, any |unixMode| value is modified by
195 * the process umask, as open() would have done.
196 */
197 File.prototype.setPermissions = function setPermissions(options = {}) {
198 throw_on_negative("setPermissions",
199 UnixFile.fchmod(this.fd, unixMode(options)),
200 this._path);
201 };
203 /**
204 * Set the last access and modification date of the file.
205 * The time stamp resolution is 1 second at best, but might be worse
206 * depending on the platform.
207 *
208 * @param {Date,number=} accessDate The last access date. If numeric,
209 * milliseconds since epoch. If omitted or null, then the current date
210 * will be used.
211 * @param {Date,number=} modificationDate The last modification date. If
212 * numeric, milliseconds since epoch. If omitted or null, then the current
213 * date will be used.
214 *
215 * @throws {TypeError} In case of invalid parameters.
216 * @throws {OS.File.Error} In case of I/O error.
217 */
218 File.prototype.setDates = function setDates(accessDate, modificationDate) {
219 accessDate = normalizeDate("File.prototype.setDates", accessDate);
220 modificationDate = normalizeDate("File.prototype.setDates",
221 modificationDate);
222 gTimevals[0].tv_sec = (accessDate / 1000) | 0;
223 gTimevals[0].tv_usec = 0;
224 gTimevals[1].tv_sec = (modificationDate / 1000) | 0;
225 gTimevals[1].tv_usec = 0;
226 throw_on_negative("setDates",
227 UnixFile.futimes(this.fd, gTimevalsPtr),
228 this._path);
229 };
231 /**
232 * Flushes the file's buffers and causes all buffered data
233 * to be written.
234 * Disk flushes are very expensive and therefore should be used carefully,
235 * sparingly and only in scenarios where it is vital that data survives
236 * system crashes. Even though the function will be executed off the
237 * main-thread, it might still affect the overall performance of any
238 * running application.
239 *
240 * @throws {OS.File.Error} In case of I/O error.
241 */
242 File.prototype.flush = function flush() {
243 throw_on_negative("flush", UnixFile.fsync(this.fd), this._path);
244 };
246 // The default unix mode for opening (0600)
247 const DEFAULT_UNIX_MODE = 384;
249 /**
250 * Open a file
251 *
252 * @param {string} path The path to the file.
253 * @param {*=} mode The opening mode for the file, as
254 * an object that may contain the following fields:
255 *
256 * - {bool} truncate If |true|, the file will be opened
257 * for writing. If the file does not exist, it will be
258 * created. If the file exists, its contents will be
259 * erased. Cannot be specified with |create|.
260 * - {bool} create If |true|, the file will be opened
261 * for writing. If the file exists, this function fails.
262 * If the file does not exist, it will be created. Cannot
263 * be specified with |truncate| or |existing|.
264 * - {bool} existing. If the file does not exist, this function
265 * fails. Cannot be specified with |create|.
266 * - {bool} read If |true|, the file will be opened for
267 * reading. The file may also be opened for writing, depending
268 * on the other fields of |mode|.
269 * - {bool} write If |true|, the file will be opened for
270 * writing. The file may also be opened for reading, depending
271 * on the other fields of |mode|.
272 * - {bool} append If |true|, the file will be opened for appending,
273 * meaning the equivalent of |.setPosition(0, POS_END)| is executed
274 * before each write. The default is |true|, i.e. opening a file for
275 * appending. Specify |append: false| to open the file in regular mode.
276 *
277 * If neither |truncate|, |create| or |write| is specified, the file
278 * is opened for reading.
279 *
280 * Note that |false|, |null| or |undefined| flags are simply ignored.
281 *
282 * @param {*=} options Additional options for file opening. This
283 * implementation interprets the following fields:
284 *
285 * - {number} unixFlags If specified, file opening flags, as
286 * per libc function |open|. Replaces |mode|.
287 * - {number} unixMode If specified, a file creation mode,
288 * as per libc function |open|. If unspecified, files are
289 * created with a default mode of 0600 (file is private to the
290 * user, the user can read and write).
291 *
292 * @return {File} A file object.
293 * @throws {OS.File.Error} If the file could not be opened.
294 */
295 File.open = function Unix_open(path, mode, options = {}) {
296 let omode = options.unixMode !== undefined ?
297 options.unixMode : DEFAULT_UNIX_MODE;
298 let flags;
299 if (options.unixFlags !== undefined) {
300 flags = options.unixFlags;
301 } else {
302 mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
303 // Handle read/write
304 if (!mode.write) {
305 flags = Const.O_RDONLY;
306 } else if (mode.read) {
307 flags = Const.O_RDWR;
308 } else {
309 flags = Const.O_WRONLY;
310 }
311 // Finally, handle create/existing/trunc
312 if (mode.trunc) {
313 if (mode.existing) {
314 flags |= Const.O_TRUNC;
315 } else {
316 flags |= Const.O_CREAT | Const.O_TRUNC;
317 }
318 } else if (mode.create) {
319 flags |= Const.O_CREAT | Const.O_EXCL;
320 } else if (mode.read && !mode.write) {
321 // flags are sufficient
322 } else if (!mode.existing) {
323 flags |= Const.O_CREAT;
324 }
325 if (mode.append) {
326 flags |= Const.O_APPEND;
327 }
328 }
329 return error_or_file(UnixFile.open(path, flags, omode), path);
330 };
332 /**
333 * Checks if a file exists
334 *
335 * @param {string} path The path to the file.
336 *
337 * @return {bool} true if the file exists, false otherwise.
338 */
339 File.exists = function Unix_exists(path) {
340 if (UnixFile.access(path, Const.F_OK) == -1) {
341 return false;
342 } else {
343 return true;
344 }
345 };
347 /**
348 * Remove an existing file.
349 *
350 * @param {string} path The name of the file.
351 * @param {*=} options Additional options.
352 * - {bool} ignoreAbsent If |false|, throw an error if the file does
353 * not exist. |true| by default.
354 *
355 * @throws {OS.File.Error} In case of I/O error.
356 */
357 File.remove = function remove(path, options = {}) {
358 let result = UnixFile.unlink(path);
359 if (result == -1) {
360 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
361 ctypes.errno == Const.ENOENT) {
362 return;
363 }
364 throw new File.Error("remove", ctypes.errno, path);
365 }
366 };
368 /**
369 * Remove an empty directory.
370 *
371 * @param {string} path The name of the directory to remove.
372 * @param {*=} options Additional options.
373 * - {bool} ignoreAbsent If |false|, throw an error if the directory
374 * does not exist. |true| by default
375 */
376 File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
377 let result = UnixFile.rmdir(path);
378 if (result == -1) {
379 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
380 ctypes.errno == Const.ENOENT) {
381 return;
382 }
383 throw new File.Error("removeEmptyDir", ctypes.errno, path);
384 }
385 };
387 /**
388 * Gets the number of bytes available on disk to the current user.
389 *
390 * @param {string} sourcePath Platform-specific path to a directory on
391 * the disk to query for free available bytes.
392 *
393 * @return {number} The number of bytes available for the current user.
394 * @throws {OS.File.Error} In case of any error.
395 */
396 File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) {
397 let fileSystemInfo = new Type.statvfs.implementation();
398 let fileSystemInfoPtr = fileSystemInfo.address();
400 throw_on_negative("statvfs", UnixFile.statvfs(sourcePath, fileSystemInfoPtr));
402 let bytes = new Type.uint64_t.implementation(
403 fileSystemInfo.f_bsize * fileSystemInfo.f_bavail);
405 return bytes.value;
406 };
408 /**
409 * Default mode for opening directories.
410 */
411 const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU;
413 /**
414 * Create a directory.
415 *
416 * @param {string} path The name of the directory.
417 * @param {*=} options Additional options. This
418 * implementation interprets the following fields:
419 *
420 * - {number} unixMode If specified, a file creation mode,
421 * as per libc function |mkdir|. If unspecified, dirs are
422 * created with a default mode of 0700 (dir is private to
423 * the user, the user can read, write and execute).
424 * - {bool} ignoreExisting If |false|, throw error if the directory
425 * already exists. |true| by default
426 * - {string} from If specified, the call to |makeDir| creates all the
427 * ancestors of |path| that are descendants of |from|. Note that |from|
428 * and its existing descendants must be user-writeable and that |path|
429 * must be a descendant of |from|.
430 * Example:
431 * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
432 * creates directories profileDir/foo, profileDir/foo/bar
433 */
434 File._makeDir = function makeDir(path, options = {}) {
435 let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR;
436 let result = UnixFile.mkdir(path, omode);
437 if (result == -1) {
438 if ((!("ignoreExisting" in options) || options.ignoreExisting) &&
439 (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) {
440 return;
441 }
442 throw new File.Error("makeDir", ctypes.errno, path);
443 }
444 };
446 /**
447 * Copy a file to a destination.
448 *
449 * @param {string} sourcePath The platform-specific path at which
450 * the file may currently be found.
451 * @param {string} destPath The platform-specific path at which the
452 * file should be copied.
453 * @param {*=} options An object which may contain the following fields:
454 *
455 * @option {bool} noOverwrite - If set, this function will fail if
456 * a file already exists at |destPath|. Otherwise, if this file exists,
457 * it will be erased silently.
458 *
459 * @throws {OS.File.Error} In case of any error.
460 *
461 * General note: The behavior of this function is defined only when
462 * it is called on a single file. If it is called on a directory, the
463 * behavior is undefined and may not be the same across all platforms.
464 *
465 * General note: The behavior of this function with respect to metadata
466 * is unspecified. Metadata may or may not be copied with the file. The
467 * behavior may not be the same across all platforms.
468 */
469 File.copy = null;
471 /**
472 * Move a file to a destination.
473 *
474 * @param {string} sourcePath The platform-specific path at which
475 * the file may currently be found.
476 * @param {string} destPath The platform-specific path at which the
477 * file should be moved.
478 * @param {*=} options An object which may contain the following fields:
479 *
480 * @option {bool} noOverwrite - If set, this function will fail if
481 * a file already exists at |destPath|. Otherwise, if this file exists,
482 * it will be erased silently.
483 * @option {bool} noCopy - If set, this function will fail if the
484 * operation is more sophisticated than a simple renaming, i.e. if
485 * |sourcePath| and |destPath| are not situated on the same device.
486 *
487 * @throws {OS.File.Error} In case of any error.
488 *
489 * General note: The behavior of this function is defined only when
490 * it is called on a single file. If it is called on a directory, the
491 * behavior is undefined and may not be the same across all platforms.
492 *
493 * General note: The behavior of this function with respect to metadata
494 * is unspecified. Metadata may or may not be moved with the file. The
495 * behavior may not be the same across all platforms.
496 */
497 File.move = null;
499 if (UnixFile.copyfile) {
500 // This implementation uses |copyfile(3)|, from the BSD library.
501 // Adding copying of hierarchies and/or attributes is just a flag
502 // away.
503 File.copy = function copyfile(sourcePath, destPath, options = {}) {
504 let flags = Const.COPYFILE_DATA;
505 if (options.noOverwrite) {
506 flags |= Const.COPYFILE_EXCL;
507 }
508 throw_on_negative("copy",
509 UnixFile.copyfile(sourcePath, destPath, null, flags),
510 sourcePath
511 );
512 };
513 } else {
514 // If the OS does not implement file copying for us, we need to
515 // implement it ourselves. For this purpose, we need to define
516 // a pumping function.
518 /**
519 * Copy bytes from one file to another one.
520 *
521 * @param {File} source The file containing the data to be copied. It
522 * should be opened for reading.
523 * @param {File} dest The file to which the data should be written. It
524 * should be opened for writing.
525 * @param {*=} options An object which may contain the following fields:
526 *
527 * @option {number} nbytes The maximal number of bytes to
528 * copy. If unspecified, copy everything from the current
529 * position.
530 * @option {number} bufSize A hint regarding the size of the
531 * buffer to use for copying. The implementation may decide to
532 * ignore this hint.
533 * @option {bool} unixUserland Will force the copy operation to be
534 * caried out in user land, instead of using optimized syscalls such
535 * as splice(2).
536 *
537 * @throws {OS.File.Error} In case of error.
538 */
539 let pump;
541 // A buffer used by |pump_userland|
542 let pump_buffer = null;
544 // An implementation of |pump| using |read|/|write|
545 let pump_userland = function pump_userland(source, dest, options = {}) {
546 let bufSize = options.bufSize > 0 ? options.bufSize : 4096;
547 let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
548 if (!pump_buffer || pump_buffer.length < bufSize) {
549 pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize);
550 }
551 let read = source._read.bind(source);
552 let write = dest._write.bind(dest);
553 // Perform actual copy
554 let total_read = 0;
555 while (true) {
556 let chunk_size = Math.min(nbytes, bufSize);
557 let bytes_just_read = read(pump_buffer, bufSize);
558 if (bytes_just_read == 0) {
559 return total_read;
560 }
561 total_read += bytes_just_read;
562 let bytes_written = 0;
563 do {
564 bytes_written += write(
565 pump_buffer.addressOfElement(bytes_written),
566 bytes_just_read - bytes_written
567 );
568 } while (bytes_written < bytes_just_read);
569 nbytes -= bytes_written;
570 if (nbytes <= 0) {
571 return total_read;
572 }
573 }
574 };
576 // Fortunately, under Linux, that pumping function can be optimized.
577 if (UnixFile.splice) {
578 const BUFSIZE = 1 << 17;
580 // An implementation of |pump| using |splice| (for Linux/Android)
581 pump = function pump_splice(source, dest, options = {}) {
582 let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
583 let pipe = [];
584 throw_on_negative("pump", UnixFile.pipe(pipe));
585 let pipe_read = pipe[0];
586 let pipe_write = pipe[1];
587 let source_fd = source.fd;
588 let dest_fd = dest.fd;
589 let total_read = 0;
590 let total_written = 0;
591 try {
592 while (true) {
593 let chunk_size = Math.min(nbytes, BUFSIZE);
594 let bytes_read = throw_on_negative("pump",
595 UnixFile.splice(source_fd, null,
596 pipe_write, null, chunk_size, 0)
597 );
598 if (!bytes_read) {
599 break;
600 }
601 total_read += bytes_read;
602 let bytes_written = throw_on_negative(
603 "pump",
604 UnixFile.splice(pipe_read, null,
605 dest_fd, null, bytes_read,
606 (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0
607 ));
608 if (!bytes_written) {
609 // This should never happen
610 throw new Error("Internal error: pipe disconnected");
611 }
612 total_written += bytes_written;
613 nbytes -= bytes_read;
614 if (!nbytes) {
615 break;
616 }
617 }
618 return total_written;
619 } catch (x) {
620 if (x.unixErrno == Const.EINVAL) {
621 // We *might* be on a file system that does not support splice.
622 // Try again with a fallback pump.
623 if (total_read) {
624 source.setPosition(-total_read, File.POS_CURRENT);
625 }
626 if (total_written) {
627 dest.setPosition(-total_written, File.POS_CURRENT);
628 }
629 return pump_userland(source, dest, options);
630 }
631 throw x;
632 } finally {
633 pipe_read.dispose();
634 pipe_write.dispose();
635 }
636 };
637 } else {
638 // Fallback implementation of pump for other Unix platforms.
639 pump = pump_userland;
640 }
642 // Implement |copy| using |pump|.
643 // This implementation would require some work before being able to
644 // copy directories
645 File.copy = function copy(sourcePath, destPath, options = {}) {
646 let source, dest;
647 let result;
648 try {
649 source = File.open(sourcePath);
650 // Need to open the output file with |append:false|, or else |splice|
651 // won't work.
652 if (options.noOverwrite) {
653 dest = File.open(destPath, {create:true, append:false});
654 } else {
655 dest = File.open(destPath, {trunc:true, append:false});
656 }
657 if (options.unixUserland) {
658 result = pump_userland(source, dest, options);
659 } else {
660 result = pump(source, dest, options);
661 }
662 } catch (x) {
663 if (dest) {
664 dest.close();
665 }
666 if (source) {
667 source.close();
668 }
669 throw x;
670 }
671 };
672 } // End of definition of copy
674 // Implement |move| using |rename| (wherever possible) or |copy|
675 // (if files are on distinct devices).
676 File.move = function move(sourcePath, destPath, options = {}) {
677 // An implementation using |rename| whenever possible or
678 // |File.pump| when required, for other Unices.
679 // It can move directories on one file system, not
680 // across file systems
682 // If necessary, fail if the destination file exists
683 if (options.noOverwrite) {
684 let fd = UnixFile.open(destPath, Const.O_RDONLY, 0);
685 if (fd != -1) {
686 fd.dispose();
687 // The file exists and we have access
688 throw new File.Error("move", Const.EEXIST, sourcePath);
689 } else if (ctypes.errno == Const.EACCESS) {
690 // The file exists and we don't have access
691 throw new File.Error("move", Const.EEXIST, sourcePath);
692 }
693 }
695 // If we can, rename the file
696 let result = UnixFile.rename(sourcePath, destPath);
697 if (result != -1)
698 return;
700 // If the error is not EXDEV ("not on the same device"),
701 // or if the error is EXDEV and we have passed an option
702 // that prevents us from crossing devices, throw the
703 // error.
704 if (ctypes.errno != Const.EXDEV || options.noCopy) {
705 throw new File.Error("move", ctypes.errno, sourcePath);
706 }
708 // Otherwise, copy and remove.
709 File.copy(sourcePath, destPath, options);
710 // FIXME: Clean-up in case of copy error?
711 File.remove(sourcePath);
712 };
714 File.unixSymLink = function unixSymLink(sourcePath, destPath) {
715 throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath),
716 sourcePath);
717 };
719 /**
720 * Iterate on one directory.
721 *
722 * This iterator will not enter subdirectories.
723 *
724 * @param {string} path The directory upon which to iterate.
725 * @param {*=} options Ignored in this implementation.
726 *
727 * @throws {File.Error} If |path| does not represent a directory or
728 * if the directory cannot be iterated.
729 * @constructor
730 */
731 File.DirectoryIterator = function DirectoryIterator(path, options) {
732 exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
733 this._path = path;
734 this._dir = UnixFile.opendir(this._path);
735 if (this._dir == null) {
736 let error = ctypes.errno;
737 if (error != Const.ENOENT) {
738 throw new File.Error("DirectoryIterator", error, path);
739 }
740 this._exists = false;
741 this._closed = true;
742 } else {
743 this._exists = true;
744 this._closed = false;
745 }
746 };
747 File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
749 /**
750 * Return the next entry in the directory, if any such entry is
751 * available.
752 *
753 * Skip special directories "." and "..".
754 *
755 * @return {File.Entry} The next entry in the directory.
756 * @throws {StopIteration} Once all files in the directory have been
757 * encountered.
758 */
759 File.DirectoryIterator.prototype.next = function next() {
760 if (!this._exists) {
761 throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
762 }
763 if (this._closed) {
764 throw StopIteration;
765 }
766 for (let entry = UnixFile.readdir(this._dir);
767 entry != null && !entry.isNull();
768 entry = UnixFile.readdir(this._dir)) {
769 let contents = entry.contents;
770 let name = contents.d_name.readString();
771 if (name == "." || name == "..") {
772 continue;
773 }
775 let isDir, isSymLink;
776 if (!("d_type" in contents)) {
777 // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
778 let path = Path.join(this._path, name);
779 throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path);
780 isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR;
781 isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK;
782 } else {
783 isDir = contents.d_type == Const.DT_DIR;
784 isSymLink = contents.d_type == Const.DT_LNK;
785 }
787 return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path);
788 }
789 this.close();
790 throw StopIteration;
791 };
793 /**
794 * Close the iterator and recover all resources.
795 * You should call this once you have finished iterating on a directory.
796 */
797 File.DirectoryIterator.prototype.close = function close() {
798 if (this._closed) return;
799 this._closed = true;
800 UnixFile.closedir(this._dir);
801 this._dir = null;
802 };
804 /**
805 * Determine whether the directory exists.
806 *
807 * @return {boolean}
808 */
809 File.DirectoryIterator.prototype.exists = function exists() {
810 return this._exists;
811 };
813 /**
814 * Return directory as |File|
815 */
816 File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() {
817 if (!this._dir) throw File.Error.closed("unixAsFile", this._path);
818 return error_or_file(UnixFile.dirfd(this._dir), this._path);
819 };
821 /**
822 * An entry in a directory.
823 */
824 File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) {
825 // Copy the relevant part of |unix_entry| to ensure that
826 // our data is not overwritten prematurely.
827 this._parent = parent;
828 let path = Path.join(this._parent, name);
830 SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path);
831 };
832 File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
834 /**
835 * Return a version of an instance of
836 * File.DirectoryIterator.Entry that can be sent from a worker
837 * thread to the main thread. Note that deserialization is
838 * asymmetric and returns an object with a different
839 * implementation.
840 */
841 File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
842 if (!value instanceof File.DirectoryIterator.Entry) {
843 throw new TypeError("parameter of " +
844 "File.DirectoryIterator.Entry.toMsg must be a " +
845 "File.DirectoryIterator.Entry");
846 }
847 let serialized = {};
848 for (let key in File.DirectoryIterator.Entry.prototype) {
849 serialized[key] = value[key];
850 }
851 return serialized;
852 };
854 let gStatData = new Type.stat.implementation();
855 let gStatDataPtr = gStatData.address();
856 let gTimevals = new Type.timevals.implementation();
857 let gTimevalsPtr = gTimevals.address();
858 let MODE_MASK = 4095 /*= 07777*/;
859 File.Info = function Info(stat, path) {
860 let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR;
861 let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK;
862 let size = Type.off_t.importFromC(stat.st_size);
864 let lastAccessDate = new Date(stat.st_atime * 1000);
865 let lastModificationDate = new Date(stat.st_mtime * 1000);
866 let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000);
868 let unixOwner = Type.uid_t.importFromC(stat.st_uid);
869 let unixGroup = Type.gid_t.importFromC(stat.st_gid);
870 let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK);
872 SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
873 lastAccessDate, lastModificationDate, unixLastStatusChangeDate,
874 unixOwner, unixGroup, unixMode);
876 // Some platforms (e.g. MacOS X, some BSDs) store a file creation date
877 if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
878 let date = new Date(stat.st_birthtime * 1000);
880 /**
881 * The date of creation of this file.
882 *
883 * Note that the date returned by this method is not always
884 * reliable. Not all file systems are able to provide this
885 * information.
886 *
887 * @type {Date}
888 */
889 this.macBirthDate = date;
890 }
891 };
892 File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
894 // Deprecated, use macBirthDate/winBirthDate instead
895 Object.defineProperty(File.Info.prototype, "creationDate", {
896 get: function creationDate() {
897 // On the Macintosh, returns the birth date if available.
898 // On other Unix, as the birth date is not available,
899 // returns the epoch.
900 return this.macBirthDate || new Date(0);
901 }
902 });
904 /**
905 * Return a version of an instance of File.Info that can be sent
906 * from a worker thread to the main thread. Note that deserialization
907 * is asymmetric and returns an object with a different implementation.
908 */
909 File.Info.toMsg = function toMsg(stat) {
910 if (!stat instanceof File.Info) {
911 throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
912 }
913 let serialized = {};
914 for (let key in File.Info.prototype) {
915 serialized[key] = stat[key];
916 }
917 return serialized;
918 };
920 /**
921 * Fetch the information on a file.
922 *
923 * @param {string} path The full name of the file to open.
924 * @param {*=} options Additional options. In this implementation:
925 *
926 * - {bool} unixNoFollowingLinks If set and |true|, if |path|
927 * represents a symbolic link, the call will return the information
928 * of the link itself, rather than that of the target file.
929 *
930 * @return {File.Information}
931 */
932 File.stat = function stat(path, options = {}) {
933 if (options.unixNoFollowingLinks) {
934 throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path);
935 } else {
936 throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path);
937 }
938 return new File.Info(gStatData, path);
939 };
941 /**
942 * Set the file's access permissions. Without any options, the
943 * permissions are set to an approximation of what they would
944 * have been if the file had been created in its current
945 * directory in the "most typical" fashion for the operating
946 * system. In the current implementation, this means we set
947 * the POSIX file mode to (0666 & ~umask).
948 *
949 * This operation is likely to fail if applied to a file that was
950 * not created by the currently running program (more precisely,
951 * if it was created by a program running under a different OS-level
952 * user account). It may also fail, or silently do nothing, if the
953 * filesystem containing the file does not support access permissions.
954 *
955 * @param {string} path The name of the file to reset the permissions of.
956 * @param {*=} options
957 * - {number} unixMode If present, the POSIX file mode is set to
958 * exactly this value, unless |unixHonorUmask| is
959 * also present.
960 * - {bool} unixHonorUmask If true, any |unixMode| value is modified by
961 * the process umask, as open() would have done.
962 */
963 File.setPermissions = function setPermissions(path, options = {}) {
964 throw_on_negative("setPermissions",
965 UnixFile.chmod(path, unixMode(options)),
966 path);
967 };
969 /**
970 * Set the last access and modification date of the file.
971 * The time stamp resolution is 1 second at best, but might be worse
972 * depending on the platform.
973 *
974 * @param {string} path The full name of the file to set the dates for.
975 * @param {Date,number=} accessDate The last access date. If numeric,
976 * milliseconds since epoch. If omitted or null, then the current date
977 * will be used.
978 * @param {Date,number=} modificationDate The last modification date. If
979 * numeric, milliseconds since epoch. If omitted or null, then the current
980 * date will be used.
981 *
982 * @throws {TypeError} In case of invalid paramters.
983 * @throws {OS.File.Error} In case of I/O error.
984 */
985 File.setDates = function setDates(path, accessDate, modificationDate) {
986 accessDate = normalizeDate("File.setDates", accessDate);
987 modificationDate = normalizeDate("File.setDates", modificationDate);
988 gTimevals[0].tv_sec = (accessDate / 1000) | 0;
989 gTimevals[0].tv_usec = 0;
990 gTimevals[1].tv_sec = (modificationDate / 1000) | 0;
991 gTimevals[1].tv_usec = 0;
992 throw_on_negative("setDates",
993 UnixFile.utimes(path, gTimevalsPtr),
994 path);
995 };
997 File.read = exports.OS.Shared.AbstractFile.read;
998 File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
999 File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
1000 File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
1002 /**
1003 * Remove an existing directory and its contents.
1004 *
1005 * @param {string} path The name of the directory.
1006 * @param {*=} options Additional options.
1007 * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
1008 * exist. |true| by default.
1009 * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
1010 * permission.
1011 *
1012 * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
1013 * not a directory.
1014 *
1015 * Note: This function will remove a symlink even if it points a directory.
1016 */
1017 File.removeDir = function(path, options) {
1018 let isSymLink;
1019 try {
1020 let info = File.stat(path, {unixNoFollowingLinks: true});
1021 isSymLink = info.isSymLink;
1022 } catch (e) {
1023 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
1024 ctypes.errno == Const.ENOENT) {
1025 return;
1026 }
1027 throw e;
1028 }
1029 if (isSymLink) {
1030 // A Unix symlink itself is not a directory even if it points
1031 // a directory.
1032 File.remove(path, options);
1033 return;
1034 }
1035 exports.OS.Shared.AbstractFile.removeRecursive(path, options);
1036 };
1038 /**
1039 * Get the current directory by getCurrentDirectory.
1040 */
1041 File.getCurrentDirectory = function getCurrentDirectory() {
1042 let path = UnixFile.get_current_dir_name?UnixFile.get_current_dir_name():
1043 UnixFile.getwd_auto(null);
1044 throw_on_null("getCurrentDirectory", path);
1045 return path.readString();
1046 };
1048 /**
1049 * Set the current directory by setCurrentDirectory.
1050 */
1051 File.setCurrentDirectory = function setCurrentDirectory(path) {
1052 throw_on_negative("setCurrentDirectory",
1053 UnixFile.chdir(path),
1054 path
1055 );
1056 };
1058 /**
1059 * Get/set the current directory.
1060 */
1061 Object.defineProperty(File, "curDir", {
1062 set: function(path) {
1063 this.setCurrentDirectory(path);
1064 },
1065 get: function() {
1066 return this.getCurrentDirectory();
1067 }
1068 }
1069 );
1071 // Utility functions
1073 /**
1074 * Turn the result of |open| into an Error or a File
1075 * @param {number} maybe The result of the |open| operation that may
1076 * represent either an error or a success. If -1, this function raises
1077 * an error holding ctypes.errno, otherwise it returns the opened file.
1078 * @param {string=} path The path of the file.
1079 */
1080 function error_or_file(maybe, path) {
1081 if (maybe == -1) {
1082 throw new File.Error("open", ctypes.errno, path);
1083 }
1084 return new File(maybe, path);
1085 }
1087 /**
1088 * Utility function to sort errors represented as "-1" from successes.
1089 *
1090 * @param {string=} operation The name of the operation. If unspecified,
1091 * the name of the caller function.
1092 * @param {number} result The result of the operation that may
1093 * represent either an error or a success. If -1, this function raises
1094 * an error holding ctypes.errno, otherwise it returns |result|.
1095 * @param {string=} path The path of the file.
1096 */
1097 function throw_on_negative(operation, result, path) {
1098 if (result < 0) {
1099 throw new File.Error(operation, ctypes.errno, path);
1100 }
1101 return result;
1102 }
1104 /**
1105 * Utility function to sort errors represented as |null| from successes.
1106 *
1107 * @param {string=} operation The name of the operation. If unspecified,
1108 * the name of the caller function.
1109 * @param {pointer} result The result of the operation that may
1110 * represent either an error or a success. If |null|, this function raises
1111 * an error holding ctypes.errno, otherwise it returns |result|.
1112 * @param {string=} path The path of the file.
1113 */
1114 function throw_on_null(operation, result, path) {
1115 if (result == null || (result.isNull && result.isNull())) {
1116 throw new File.Error(operation, ctypes.errno, path);
1117 }
1118 return result;
1119 }
1121 /**
1122 * Normalize and verify a Date or numeric date value.
1123 *
1124 * @param {string} fn Function name of the calling function.
1125 * @param {Date,number} date The date to normalize. If omitted or null,
1126 * then the current date will be used.
1127 *
1128 * @throws {TypeError} Invalid date provided.
1129 *
1130 * @return {number} Sanitized, numeric date in milliseconds since epoch.
1131 */
1132 function normalizeDate(fn, date) {
1133 if (typeof date !== "number" && !date) {
1134 // |date| was Omitted or null.
1135 date = Date.now();
1136 } else if (typeof date.getTime === "function") {
1137 // Input might be a date or date-like object.
1138 date = date.getTime();
1139 }
1141 if (isNaN(date)) {
1142 throw new TypeError("|date| parameter of " + fn + " must be a " +
1143 "|Date| instance or number");
1144 }
1145 return date;
1146 };
1148 /**
1149 * Helper used by both versions of setPermissions.
1150 */
1151 function unixMode(options) {
1152 let mode = 438; /* 0666 */
1153 let unixHonorUmask = true;
1154 if ("unixMode" in options) {
1155 unixHonorUmask = false;
1156 mode = options.unixMode;
1157 }
1158 if ("unixHonorUmask" in options) {
1159 unixHonorUmask = options.unixHonorUmask;
1160 }
1161 if (unixHonorUmask) {
1162 mode &= ~SharedAll.Constants.Sys.umask;
1163 }
1164 return mode;
1165 }
1167 File.Unix = exports.OS.Unix.File;
1168 File.Error = SysAll.Error;
1169 exports.OS.File = File;
1170 exports.OS.Shared.Type = Type;
1172 Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
1173 Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
1174 Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
1175 })(this);
1176 }