toolkit/components/osfile/modules/osfile_unix_front.jsm

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

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.
  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.
  1012       * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
  1013       *         not a directory.
  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;
  1027          throw e;
  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;
  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();
  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);
  1084        return new File(maybe, path);
  1087      /**
  1088       * Utility function to sort errors represented as "-1" from successes.
  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);
  1101        return result;
  1104      /**
  1105       * Utility function to sort errors represented as |null| from successes.
  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);
  1118        return result;
  1121      /**
  1122       * Normalize and verify a Date or numeric date value.
  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.
  1128       * @throws {TypeError} Invalid date provided.
  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();
  1141        if (isNaN(date)) {
  1142          throw new TypeError("|date| parameter of " + fn + " must be a " +
  1143                              "|Date| instance or number");
  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;
  1158        if ("unixHonorUmask" in options) {
  1159          unixHonorUmask = options.unixHonorUmask;
  1161        if (unixHonorUmask) {
  1162          mode &= ~SharedAll.Constants.Sys.umask;
  1164        return mode;
  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);

mercurial