summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/modules/osfile_unix_front.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/osfile/modules/osfile_unix_front.jsm')
-rw-r--r--toolkit/components/osfile/modules/osfile_unix_front.jsm1193
1 files changed, 1193 insertions, 0 deletions
diff --git a/toolkit/components/osfile/modules/osfile_unix_front.jsm b/toolkit/components/osfile/modules/osfile_unix_front.jsm
new file mode 100644
index 000000000..19a27ae1a
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -0,0 +1,1193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Unix implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_unix_front.jsm to be used directly as a main thread
+ // module yet.
+
+ throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet");
+ }
+ (function(exports) {
+ "use strict";
+
+ // exports.OS.Unix is created by osfile_unix_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
+ exports.OS.Unix.File._init();
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end");
+ let Const = SharedAll.Constants.libc;
+ let UnixFile = exports.OS.Unix.File;
+ let Type = UnixFile.Type;
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = UnixFile._close(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.errno, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options = {}) {
+ // Populate the page cache with data from a file so the subsequent reads
+ // from that file will not block on disk I/O.
+ if (typeof(UnixFile.posix_fadvise) === 'function' &&
+ (options.sequential || !("sequential" in options))) {
+ UnixFile.posix_fadvise(this.fd, 0, nbytes,
+ OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
+ }
+ return throw_on_negative("read",
+ UnixFile.read(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options = {}) {
+ return throw_on_negative("write",
+ UnixFile.write(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.SEEK_SET;
+ }
+ return throw_on_negative("setPosition",
+ UnixFile.lseek(this.fd, pos, whence),
+ this._path
+ );
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr),
+ this._path);
+ return new File.Info(gStatData, this._path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.fchmod(this.fd, unixMode(options)),
+ this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * WARNING: This method is not implemented on Android/B2G. On Android/B2G,
+ * you should use File.setDates instead.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ if (SharedAll.Constants.Sys.Name != "Android") {
+ File.prototype.setDates = function(accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.futimes(this.fd, ptr),
+ this._path);
+ };
+ }
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_negative("flush", UnixFile.fsync(this.fd), this._path);
+ };
+
+ // The default unix mode for opening (0600)
+ const DEFAULT_UNIX_MODE = 384;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixFlags If specified, file opening flags, as
+ * per libc function |open|. Replaces |mode|.
+ * - {number} unixMode If specified, a file creation mode,
+ * as per libc function |open|. If unspecified, files are
+ * created with a default mode of 0600 (file is private to the
+ * user, the user can read and write).
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Unix_open(path, mode, options = {}) {
+ // We don't need to filter for the umask because "open" does this for us.
+ let omode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let flags;
+ if (options.unixFlags !== undefined) {
+ flags = options.unixFlags;
+ } else {
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+ // Handle read/write
+ if (!mode.write) {
+ flags = Const.O_RDONLY;
+ } else if (mode.read) {
+ flags = Const.O_RDWR;
+ } else {
+ flags = Const.O_WRONLY;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ flags |= Const.O_TRUNC;
+ } else {
+ flags |= Const.O_CREAT | Const.O_TRUNC;
+ }
+ } else if (mode.create) {
+ flags |= Const.O_CREAT | Const.O_EXCL;
+ } else if (mode.read && !mode.write) {
+ // flags are sufficient
+ } else if (!mode.existing) {
+ flags |= Const.O_CREAT;
+ }
+ if (mode.append) {
+ flags |= Const.O_APPEND;
+ }
+ }
+ return error_or_file(UnixFile.open(path, flags, omode), path);
+ };
+
+ /**
+ * Checks if a file exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Unix_exists(path) {
+ if (UnixFile.access(path, Const.F_OK) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ let result = UnixFile.unlink(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("remove", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = UnixFile.rmdir(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) {
+ let fileSystemInfo = new Type.statvfs.implementation();
+ let fileSystemInfoPtr = fileSystemInfo.address();
+
+ throw_on_negative("statvfs", (UnixFile.statvfs || UnixFile.statfs)(sourcePath, fileSystemInfoPtr));
+
+ let bytes = new Type.uint64_t.implementation(
+ fileSystemInfo.f_bsize * fileSystemInfo.f_bavail);
+
+ return bytes.value;
+ };
+
+ /**
+ * Default mode for opening directories.
+ */
+ const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU;
+
+ /**
+ * Create a directory.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixMode If specified, a file creation mode,
+ * as per libc function |mkdir|. If unspecified, dirs are
+ * created with a default mode of 0700 (dir is private to
+ * the user, the user can read, write and execute).
+ * - {bool} ignoreExisting If |false|, throw error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR;
+ let result = UnixFile.mkdir(path, omode);
+ if (result == -1) {
+ if ((!("ignoreExisting" in options) || options.ignoreExisting) &&
+ (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) {
+ return;
+ }
+ throw new File.Error("makeDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = null;
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same device.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = null;
+
+ if (UnixFile.copyfile) {
+ // This implementation uses |copyfile(3)|, from the BSD library.
+ // Adding copying of hierarchies and/or attributes is just a flag
+ // away.
+ File.copy = function copyfile(sourcePath, destPath, options = {}) {
+ let flags = Const.COPYFILE_DATA;
+ if (options.noOverwrite) {
+ flags |= Const.COPYFILE_EXCL;
+ }
+ throw_on_negative("copy",
+ UnixFile.copyfile(sourcePath, destPath, null, flags),
+ sourcePath
+ );
+ };
+ } else {
+ // If the OS does not implement file copying for us, we need to
+ // implement it ourselves. For this purpose, we need to define
+ // a pumping function.
+
+ /**
+ * Copy bytes from one file to another one.
+ *
+ * @param {File} source The file containing the data to be copied. It
+ * should be opened for reading.
+ * @param {File} dest The file to which the data should be written. It
+ * should be opened for writing.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {number} nbytes The maximal number of bytes to
+ * copy. If unspecified, copy everything from the current
+ * position.
+ * @option {number} bufSize A hint regarding the size of the
+ * buffer to use for copying. The implementation may decide to
+ * ignore this hint.
+ * @option {bool} unixUserland Will force the copy operation to be
+ * caried out in user land, instead of using optimized syscalls such
+ * as splice(2).
+ *
+ * @throws {OS.File.Error} In case of error.
+ */
+ let pump;
+
+ // A buffer used by |pump_userland|
+ let pump_buffer = null;
+
+ // An implementation of |pump| using |read|/|write|
+ let pump_userland = function pump_userland(source, dest, options = {}) {
+ let bufSize = options.bufSize > 0 ? options.bufSize : 4096;
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ if (!pump_buffer || pump_buffer.length < bufSize) {
+ pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize);
+ }
+ let read = source._read.bind(source);
+ let write = dest._write.bind(dest);
+ // Perform actual copy
+ let total_read = 0;
+ while (true) {
+ let chunk_size = Math.min(nbytes, bufSize);
+ let bytes_just_read = read(pump_buffer, bufSize);
+ if (bytes_just_read == 0) {
+ return total_read;
+ }
+ total_read += bytes_just_read;
+ let bytes_written = 0;
+ do {
+ bytes_written += write(
+ pump_buffer.addressOfElement(bytes_written),
+ bytes_just_read - bytes_written
+ );
+ } while (bytes_written < bytes_just_read);
+ nbytes -= bytes_written;
+ if (nbytes <= 0) {
+ return total_read;
+ }
+ }
+ };
+
+ // Fortunately, under Linux, that pumping function can be optimized.
+ if (UnixFile.splice) {
+ const BUFSIZE = 1 << 17;
+
+ // An implementation of |pump| using |splice| (for Linux/Android)
+ pump = function pump_splice(source, dest, options = {}) {
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ let pipe = [];
+ throw_on_negative("pump", UnixFile.pipe(pipe));
+ let pipe_read = pipe[0];
+ let pipe_write = pipe[1];
+ let source_fd = source.fd;
+ let dest_fd = dest.fd;
+ let total_read = 0;
+ let total_written = 0;
+ try {
+ while (true) {
+ let chunk_size = Math.min(nbytes, BUFSIZE);
+ let bytes_read = throw_on_negative("pump",
+ UnixFile.splice(source_fd, null,
+ pipe_write, null, chunk_size, 0)
+ );
+ if (!bytes_read) {
+ break;
+ }
+ total_read += bytes_read;
+ let bytes_written = throw_on_negative(
+ "pump",
+ UnixFile.splice(pipe_read, null,
+ dest_fd, null, bytes_read,
+ (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0
+ ));
+ if (!bytes_written) {
+ // This should never happen
+ throw new Error("Internal error: pipe disconnected");
+ }
+ total_written += bytes_written;
+ nbytes -= bytes_read;
+ if (!nbytes) {
+ break;
+ }
+ }
+ return total_written;
+ } catch (x) {
+ if (x.unixErrno == Const.EINVAL) {
+ // We *might* be on a file system that does not support splice.
+ // Try again with a fallback pump.
+ if (total_read) {
+ source.setPosition(-total_read, File.POS_CURRENT);
+ }
+ if (total_written) {
+ dest.setPosition(-total_written, File.POS_CURRENT);
+ }
+ return pump_userland(source, dest, options);
+ }
+ throw x;
+ } finally {
+ pipe_read.dispose();
+ pipe_write.dispose();
+ }
+ };
+ } else {
+ // Fallback implementation of pump for other Unix platforms.
+ pump = pump_userland;
+ }
+
+ // Implement |copy| using |pump|.
+ // This implementation would require some work before being able to
+ // copy directories
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ let source, dest;
+ let result;
+ try {
+ source = File.open(sourcePath);
+ // Need to open the output file with |append:false|, or else |splice|
+ // won't work.
+ if (options.noOverwrite) {
+ dest = File.open(destPath, {create:true, append:false});
+ } else {
+ dest = File.open(destPath, {trunc:true, append:false});
+ }
+ if (options.unixUserland) {
+ result = pump_userland(source, dest, options);
+ } else {
+ result = pump(source, dest, options);
+ }
+ } catch (x) {
+ if (dest) {
+ dest.close();
+ }
+ if (source) {
+ source.close();
+ }
+ throw x;
+ }
+ };
+ } // End of definition of copy
+
+ // Implement |move| using |rename| (wherever possible) or |copy|
+ // (if files are on distinct devices).
+ File.move = function move(sourcePath, destPath, options = {}) {
+ // An implementation using |rename| whenever possible or
+ // |File.pump| when required, for other Unices.
+ // It can move directories on one file system, not
+ // across file systems
+
+ // If necessary, fail if the destination file exists
+ if (options.noOverwrite) {
+ let fd = UnixFile.open(destPath, Const.O_RDONLY, 0);
+ if (fd != -1) {
+ fd.dispose();
+ // The file exists and we have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ } else if (ctypes.errno == Const.EACCESS) {
+ // The file exists and we don't have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ }
+ }
+
+ // If we can, rename the file
+ let result = UnixFile.rename(sourcePath, destPath);
+ if (result != -1)
+ return;
+
+ // If the error is not EXDEV ("not on the same device"),
+ // or if the error is EXDEV and we have passed an option
+ // that prevents us from crossing devices, throw the
+ // error.
+ if (ctypes.errno != Const.EXDEV || options.noCopy) {
+ throw new File.Error("move", ctypes.errno, sourcePath);
+ }
+
+ // Otherwise, copy and remove.
+ File.copy(sourcePath, destPath, options);
+ // FIXME: Clean-up in case of copy error?
+ File.remove(sourcePath);
+ };
+
+ File.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath),
+ sourcePath);
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options Ignored in this implementation.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ this._path = path;
+ this._dir = UnixFile.opendir(this._path);
+ if (this._dir == null) {
+ let error = ctypes.errno;
+ if (error != Const.ENOENT) {
+ throw new File.Error("DirectoryIterator", error, path);
+ }
+ this._exists = false;
+ this._closed = true;
+ } else {
+ this._exists = true;
+ this._closed = false;
+ }
+ };
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ if (this._closed) {
+ throw StopIteration;
+ }
+ for (let entry = UnixFile.readdir(this._dir);
+ entry != null && !entry.isNull();
+ entry = UnixFile.readdir(this._dir)) {
+ let contents = entry.contents;
+ let name = contents.d_name.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+
+ let isDir, isSymLink;
+ if (!("d_type" in contents)) {
+ // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
+ let path = Path.join(this._path, name);
+ throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path);
+ isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ } else {
+ isDir = contents.d_type == Const.DT_DIR;
+ isSymLink = contents.d_type == Const.DT_LNK;
+ }
+
+ return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path);
+ }
+ this.close();
+ throw StopIteration;
+ };
+
+ /**
+ * Close the iterator and recover all resources.
+ * You should call this once you have finished iterating on a directory.
+ */
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) return;
+ this._closed = true;
+ UnixFile.closedir(this._dir);
+ this._dir = null;
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ /**
+ * Return directory as |File|
+ */
+ File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() {
+ if (!this._dir) throw File.Error.closed("unixAsFile", this._path);
+ return error_or_file(UnixFile.dirfd(this._dir), this._path);
+ };
+
+ /**
+ * An entry in a directory.
+ */
+ File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) {
+ // Copy the relevant part of |unix_entry| to ensure that
+ // our data is not overwritten prematurely.
+ this._parent = parent;
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!value instanceof File.DirectoryIterator.Entry) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+ let gStatData = new Type.stat.implementation();
+ let gStatDataPtr = gStatData.address();
+
+ let MODE_MASK = 4095 /*= 07777*/;
+ File.Info = function Info(stat, path) {
+ let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ let size = Type.off_t.importFromC(stat.st_size);
+
+ let lastAccessDate = new Date(stat.st_atime * 1000);
+ let lastModificationDate = new Date(stat.st_mtime * 1000);
+ let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000);
+
+ let unixOwner = Type.uid_t.importFromC(stat.st_uid);
+ let unixGroup = Type.gid_t.importFromC(stat.st_gid);
+ let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK);
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ lastAccessDate, lastModificationDate, unixLastStatusChangeDate,
+ unixOwner, unixGroup, unixMode);
+
+ // Some platforms (e.g. MacOS X, some BSDs) store a file creation date
+ if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
+ let date = new Date(stat.st_birthtime * 1000);
+
+ /**
+ * The date of creation of this file.
+ *
+ * Note that the date returned by this method is not always
+ * reliable. Not all file systems are able to provide this
+ * information.
+ *
+ * @type {Date}
+ */
+ this.macBirthDate = date;
+ }
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ // Deprecated, use macBirthDate/winBirthDate instead
+ Object.defineProperty(File.Info.prototype, "creationDate", {
+ get: function creationDate() {
+ // On the Macintosh, returns the birth date if available.
+ // On other Unix, as the birth date is not available,
+ // returns the epoch.
+ return this.macBirthDate || new Date(0);
+ }
+ });
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!stat instanceof File.Info) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+ /**
+ * Fetch the information on a file.
+ *
+ * @param {string} path The full name of the file to open.
+ * @param {*=} options Additional options. In this implementation:
+ *
+ * - {bool} unixNoFollowingLinks If set and |true|, if |path|
+ * represents a symbolic link, the call will return the information
+ * of the link itself, rather than that of the target file.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path, options = {}) {
+ if (options.unixNoFollowingLinks) {
+ throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path);
+ } else {
+ throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path);
+ }
+ return new File.Info(gStatData, path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {string} path The name of the file to reset the permissions of.
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.chmod(path, unixMode(options)),
+ path);
+ };
+
+ /**
+ * Convert an access date and a modification date to an array
+ * of two |timeval|.
+ */
+ function datesToTimevals(accessDate, modificationDate) {
+ accessDate = normalizeDate("File.setDates", accessDate);
+ modificationDate = normalizeDate("File.setDates", modificationDate);
+
+ let timevals = new Type.timevals.implementation();
+ let timevalsPtr = timevals.address();
+
+ timevals[0].tv_sec = (accessDate / 1000) | 0;
+ timevals[0].tv_usec = 0;
+ timevals[1].tv_sec = (modificationDate / 1000) | 0;
+ timevals[1].tv_usec = 0;
+
+ return { value: timevals, ptr: timevalsPtr };
+ }
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.utimes(path, ptr),
+ path);
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ *
+ * Note: This function will remove a symlink even if it points a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ let isSymLink;
+ try {
+ let info = File.stat(path, {unixNoFollowingLinks: true});
+ isSymLink = info.isSymLink;
+ } catch (e) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw e;
+ }
+ if (isSymLink) {
+ // A Unix symlink itself is not a directory even if it points
+ // a directory.
+ File.remove(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ let path, buf;
+ if (UnixFile.get_current_dir_name) {
+ path = UnixFile.get_current_dir_name();
+ } else if (UnixFile.getwd_auto) {
+ path = UnixFile.getwd_auto(null);
+ } else {
+ for (let length = Const.PATH_MAX; !path; length *= 2) {
+ buf = new (ctypes.char.array(length));
+ path = UnixFile.getcwd(buf, length);
+ };
+ }
+ throw_on_null("getCurrentDirectory", path);
+ return path.readString();
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_negative("setCurrentDirectory",
+ UnixFile.chdir(path),
+ path
+ );
+ };
+
+ /**
+ * Get/set the current directory.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == -1) {
+ throw new File.Error("open", ctypes.errno, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Normalize and verify a Date or numeric date value.
+ *
+ * @param {string} fn Function name of the calling function.
+ * @param {Date,number} date The date to normalize. If omitted or null,
+ * then the current date will be used.
+ *
+ * @throws {TypeError} Invalid date provided.
+ *
+ * @return {number} Sanitized, numeric date in milliseconds since epoch.
+ */
+ function normalizeDate(fn, date) {
+ if (typeof date !== "number" && !date) {
+ // |date| was Omitted or null.
+ date = Date.now();
+ } else if (typeof date.getTime === "function") {
+ // Input might be a date or date-like object.
+ date = date.getTime();
+ }
+
+ if (typeof date !== "number" || Number.isNaN(date)) {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ return date;
+ };
+
+ /**
+ * Helper used by both versions of setPermissions.
+ */
+ function unixMode(options) {
+ let mode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let unixHonorUmask = true;
+ if ("unixHonorUmask" in options) {
+ unixHonorUmask = options.unixHonorUmask;
+ }
+ if (unixHonorUmask) {
+ mode &= ~SharedAll.Constants.Sys.umask;
+ }
+ return mode;
+ }
+
+ File.Unix = exports.OS.Unix.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}