toolkit/components/osfile/modules/osfile_win_front.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     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  * Windows 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_win_front.jsm to be used directly as a main thread
    15     // module yet.
    16     throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
    17   }
    19   (function(exports) {
    20      "use strict";
    23       // exports.OS.Win is created by osfile_win_back.jsm
    24      if (exports.OS && exports.OS.File) {
    25         return; // Avoid double-initialization
    26      }
    28      let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
    29      let Path = require("resource://gre/modules/osfile/ospath.jsm");
    30      let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
    31      exports.OS.Win.File._init();
    32      let Const = exports.OS.Constants.Win;
    33      let WinFile = exports.OS.Win.File;
    34      let Type = WinFile.Type;
    36      // Mutable thread-global data
    37      // In the Windows implementation, methods |read| and |write|
    38      // require passing a pointer to an uint32 to determine how many
    39      // bytes have been read/written. In C, this is a benigne operation,
    40      // but in js-ctypes, this has a cost. Rather than re-allocating a
    41      // C uint32 and a C uint32* for each |read|/|write|, we take advantage
    42      // of the fact that the state is thread-private -- hence that two
    43      // |read|/|write| operations cannot take place at the same time --
    44      // and we use the following global mutable values:
    45      let gBytesRead = new ctypes.uint32_t(0);
    46      let gBytesReadPtr = gBytesRead.address();
    47      let gBytesWritten = new ctypes.uint32_t(0);
    48      let gBytesWrittenPtr = gBytesWritten.address();
    50      // Same story for GetFileInformationByHandle
    51      let gFileInfo = new Type.FILE_INFORMATION.implementation();
    52      let gFileInfoPtr = gFileInfo.address();
    54      /**
    55       * Representation of a file.
    56       *
    57       * You generally do not need to call this constructor yourself. Rather,
    58       * to open a file, use function |OS.File.open|.
    59       *
    60       * @param fd A OS-specific file descriptor.
    61       * @param {string} path File path of the file handle, used for error-reporting.
    62       * @constructor
    63       */
    64      let File = function File(fd, path) {
    65        exports.OS.Shared.AbstractFile.call(this, fd, path);
    66        this._closeResult = null;
    67      };
    68      File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
    70      /**
    71       * Close the file.
    72       *
    73       * This method has no effect if the file is already closed. However,
    74       * if the first call to |close| has thrown an error, further calls
    75       * will throw the same error.
    76       *
    77       * @throws File.Error If closing the file revealed an error that could
    78       * not be reported earlier.
    79       */
    80      File.prototype.close = function close() {
    81        if (this._fd) {
    82          let fd = this._fd;
    83          this._fd = null;
    84          // Call |close(fd)|, detach finalizer if any
    85          // (|fd| may not be a CDataFinalizer if it has been
    86          // instantiated from a controller thread).
    87          let result = WinFile._CloseHandle(fd);
    88          if (typeof fd == "object" && "forget" in fd) {
    89            fd.forget();
    90          }
    91          if (result == -1) {
    92            this._closeResult = new File.Error("close", ctypes.winLastError, this._path);
    93          }
    94        }
    95        if (this._closeResult) {
    96          throw this._closeResult;
    97        }
    98        return;
    99      };
   101      /**
   102       * Read some bytes from a file.
   103       *
   104       * @param {C pointer} buffer A buffer for holding the data
   105       * once it is read.
   106       * @param {number} nbytes The number of bytes to read. It must not
   107       * exceed the size of |buffer| in bytes but it may exceed the number
   108       * of bytes unread in the file.
   109       * @param {*=} options Additional options for reading. Ignored in
   110       * this implementation.
   111       *
   112       * @return {number} The number of bytes effectively read. If zero,
   113       * the end of the file has been reached.
   114       * @throws {OS.File.Error} In case of I/O error.
   115       */
   116      File.prototype._read = function _read(buffer, nbytes, options) {
   117        // |gBytesReadPtr| is a pointer to |gBytesRead|.
   118        throw_on_zero("read",
   119          WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null),
   120          this._path
   121        );
   122        return gBytesRead.value;
   123      };
   125      /**
   126       * Write some bytes to a file.
   127       *
   128       * @param {C pointer} buffer A buffer holding the data that must be
   129       * written.
   130       * @param {number} nbytes The number of bytes to write. It must not
   131       * exceed the size of |buffer| in bytes.
   132       * @param {*=} options Additional options for writing. Ignored in
   133       * this implementation.
   134       *
   135       * @return {number} The number of bytes effectively written.
   136       * @throws {OS.File.Error} In case of I/O error.
   137       */
   138      File.prototype._write = function _write(buffer, nbytes, options) {
   139        if (this._appendMode) {
   140          // Need to manually seek on Windows, as O_APPEND is not supported.
   141          // This is, of course, a race, but there is no real way around this.
   142          this.setPosition(0, File.POS_END);
   143        }
   144        // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
   145        throw_on_zero("write",
   146          WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null),
   147          this._path
   148        );
   149        return gBytesWritten.value;
   150      };
   152      /**
   153       * Return the current position in the file.
   154       */
   155      File.prototype.getPosition = function getPosition(pos) {
   156        return this.setPosition(0, File.POS_CURRENT);
   157      };
   159      /**
   160       * Change the current position in the file.
   161       *
   162       * @param {number} pos The new position. Whether this position
   163       * is considered from the current position, from the start of
   164       * the file or from the end of the file is determined by
   165       * argument |whence|.  Note that |pos| may exceed the length of
   166       * the file.
   167       * @param {number=} whence The reference position. If omitted
   168       * or |OS.File.POS_START|, |pos| is relative to the start of the
   169       * file.  If |OS.File.POS_CURRENT|, |pos| is relative to the
   170       * current position in the file. If |OS.File.POS_END|, |pos| is
   171       * relative to the end of the file.
   172       *
   173       * @return The new position in the file.
   174       */
   175      File.prototype.setPosition = function setPosition(pos, whence) {
   176        if (whence === undefined) {
   177          whence = Const.FILE_BEGIN;
   178        }
   179        let pos64 = ctypes.Int64(pos);
   180        // Per MSDN, while |lDistanceToMove| (low) is declared as int32_t, when
   181        // providing |lDistanceToMoveHigh| as well, it should countain the
   182        // bottom 32 bits of the 64-bit integer. Hence the following |posLo|
   183        // cast is OK.
   184        let posLo = new ctypes.uint32_t(ctypes.Int64.lo(pos64));
   185        posLo = ctypes.cast(posLo, ctypes.int32_t);
   186        let posHi = new ctypes.int32_t(ctypes.Int64.hi(pos64));
   187        let result = WinFile.SetFilePointer(
   188          this.fd, posLo.value, posHi.address(), whence);
   189        // INVALID_SET_FILE_POINTER might be still a valid result, as it
   190        // represents the lower 32 bit of the int64 result. MSDN says to check
   191        // both, INVALID_SET_FILE_POINTER and a non-zero winLastError.
   192        if (result == Const.INVALID_SET_FILE_POINTER && ctypes.winLastError) {
   193          throw new File.Error("setPosition", ctypes.winLastError, this._path);
   194        }
   195        pos64 = ctypes.Int64.join(posHi.value, result);
   196        return Type.int64_t.project(pos64);
   197      };
   199      /**
   200       * Fetch the information on the file.
   201       *
   202       * @return File.Info The information on |this| file.
   203       */
   204      File.prototype.stat = function stat() {
   205        throw_on_zero("stat",
   206          WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr),
   207          this._path);
   208        return new File.Info(gFileInfo, this._path);
   209      };
   211      /**
   212       * Set the last access and modification date of the file.
   213       * The time stamp resolution is 1 second at best, but might be worse
   214       * depending on the platform.
   215       *
   216       * @param {Date,number=} accessDate The last access date. If numeric,
   217       * milliseconds since epoch. If omitted or null, then the current date
   218       * will be used.
   219       * @param {Date,number=} modificationDate The last modification date. If
   220       * numeric, milliseconds since epoch. If omitted or null, then the current
   221       * date will be used.
   222       *
   223       * @throws {TypeError} In case of invalid parameters.
   224       * @throws {OS.File.Error} In case of I/O error.
   225       */
   226      File.prototype.setDates = function setDates(accessDate, modificationDate) {
   227        accessDate = Date_to_FILETIME("File.prototype.setDates", accessDate, this._path);
   228        modificationDate = Date_to_FILETIME("File.prototype.setDates",
   229                                            modificationDate,
   230                                            this._path);
   231        throw_on_zero("setDates",
   232                      WinFile.SetFileTime(this.fd, null, accessDate.address(),
   233                                          modificationDate.address()),
   234                      this._path);
   235      };
   237      /**
   238       * Set the file's access permission bits.
   239       * Not implemented for Windows (bug 1022816).
   240       */
   241      File.prototype.setPermissions = function setPermissions(options = {}) {
   242          // do nothing
   243      };
   245      /**
   246       * Flushes the file's buffers and causes all buffered data
   247       * to be written.
   248       * Disk flushes are very expensive and therefore should be used carefully,
   249       * sparingly and only in scenarios where it is vital that data survives
   250       * system crashes. Even though the function will be executed off the
   251       * main-thread, it might still affect the overall performance of any
   252       * running application.
   253       *
   254       * @throws {OS.File.Error} In case of I/O error.
   255       */
   256      File.prototype.flush = function flush() {
   257        throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd), this._path);
   258      };
   260      // The default sharing mode for opening files: files are not
   261      // locked against being reopened for reading/writing or against
   262      // being deleted by the same process or another process.
   263      // This is consistent with the default Unix policy.
   264      const DEFAULT_SHARE = Const.FILE_SHARE_READ |
   265        Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;
   267      // The default flags for opening files.
   268      const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;
   270      /**
   271       * Open a file
   272       *
   273       * @param {string} path The path to the file.
   274       * @param {*=} mode The opening mode for the file, as
   275       * an object that may contain the following fields:
   276       *
   277       * - {bool} truncate If |true|, the file will be opened
   278       *  for writing. If the file does not exist, it will be
   279       *  created. If the file exists, its contents will be
   280       *  erased. Cannot be specified with |create|.
   281       * - {bool} create If |true|, the file will be opened
   282       *  for writing. If the file exists, this function fails.
   283       *  If the file does not exist, it will be created. Cannot
   284       *  be specified with |truncate| or |existing|.
   285       * - {bool} existing. If the file does not exist, this function
   286       *  fails. Cannot be specified with |create|.
   287       * - {bool} read If |true|, the file will be opened for
   288       *  reading. The file may also be opened for writing, depending
   289       *  on the other fields of |mode|.
   290       * - {bool} write If |true|, the file will be opened for
   291       *  writing. The file may also be opened for reading, depending
   292       *  on the other fields of |mode|.
   293       * - {bool} append If |true|, the file will be opened for appending,
   294       *  meaning the equivalent of |.setPosition(0, POS_END)| is executed
   295       *  before each write. The default is |true|, i.e. opening a file for
   296       *  appending. Specify |append: false| to open the file in regular mode.
   297       *
   298       * If neither |truncate|, |create| or |write| is specified, the file
   299       * is opened for reading.
   300       *
   301       * Note that |false|, |null| or |undefined| flags are simply ignored.
   302       *
   303       * @param {*=} options Additional options for file opening. This
   304       * implementation interprets the following fields:
   305       *
   306       * - {number} winShare If specified, a share mode, as per
   307       * Windows function |CreateFile|. You can build it from
   308       * constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
   309       * the file uses the default sharing policy: it can be opened
   310       * for reading and/or writing and it can be removed by other
   311       * processes and by the same process.
   312       * - {number} winSecurity If specified, Windows security
   313       * attributes, as per Windows function |CreateFile|. If unspecified,
   314       * no security attributes.
   315       * - {number} winAccess If specified, Windows access mode, as
   316       * per Windows function |CreateFile|. This also requires option
   317       * |winDisposition| and this replaces argument |mode|. If unspecified,
   318       * uses the string |mode|.
   319       * - {number} winDisposition If specified, Windows disposition mode,
   320       * as per Windows function |CreateFile|. This also requires option
   321       * |winAccess| and this replaces argument |mode|. If unspecified,
   322       * uses the string |mode|.
   323       *
   324       * @return {File} A file object.
   325       * @throws {OS.File.Error} If the file could not be opened.
   326       */
   327      File.open = function Win_open(path, mode = {}, options = {}) {
   328        let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
   329        let security = options.winSecurity || null;
   330        let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
   331        let template = options.winTemplate ? options.winTemplate._fd : null;
   332        let access;
   333        let disposition;
   335        mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
   337        if ("winAccess" in options && "winDisposition" in options) {
   338          access = options.winAccess;
   339          disposition = options.winDisposition;
   340        } else if (("winAccess" in options && !("winDisposition" in options))
   341                  ||(!("winAccess" in options) && "winDisposition" in options)) {
   342          throw new TypeError("OS.File.open requires either both options " +
   343            "winAccess and winDisposition or neither");
   344        } else {
   345          if (mode.read) {
   346            access |= Const.GENERIC_READ;
   347          }
   348          if (mode.write) {
   349            access |= Const.GENERIC_WRITE;
   350          }
   351          // Finally, handle create/existing/trunc
   352          if (mode.trunc) {
   353            if (mode.existing) {
   354              // It seems that Const.TRUNCATE_EXISTING is broken
   355              // in presence of links (source, anyone?). We need
   356              // to open normally, then perform truncation manually.
   357              disposition = Const.OPEN_EXISTING;
   358            } else {
   359              disposition = Const.CREATE_ALWAYS;
   360            }
   361          } else if (mode.create) {
   362            disposition = Const.CREATE_NEW;
   363          } else if (mode.read && !mode.write) {
   364            disposition = Const.OPEN_EXISTING;
   365          } else if (mode.existing) {
   366            disposition = Const.OPEN_EXISTING;
   367          } else {
   368            disposition = Const.OPEN_ALWAYS;
   369          }
   370        }
   372        let file = error_or_file(WinFile.CreateFile(path,
   373          access, share, security, disposition, flags, template), path);
   375        file._appendMode = !!mode.append;
   377        if (!(mode.trunc && mode.existing)) {
   378          return file;
   379        }
   380        // Now, perform manual truncation
   381        file.setPosition(0, File.POS_START);
   382        throw_on_zero("open",
   383          WinFile.SetEndOfFile(file.fd),
   384          path);
   385        return file;
   386      };
   388      /**
   389       * Checks if a file or directory exists
   390       *
   391       * @param {string} path The path to the file.
   392       *
   393       * @return {bool} true if the file exists, false otherwise.
   394       */
   395      File.exists = function Win_exists(path) {
   396        try {
   397          let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
   398          file.close();
   399          return true;
   400        } catch (x) {
   401          return false;
   402        }
   403      };
   405      /**
   406       * Remove an existing file.
   407       *
   408       * @param {string} path The name of the file.
   409       * @param {*=} options Additional options.
   410       *   - {bool} ignoreAbsent If |false|, throw an error if the file does
   411       *     not exist. |true| by default.
   412       *
   413       * @throws {OS.File.Error} In case of I/O error.
   414       */
   415      File.remove = function remove(path, options = {}) {
   416        if (WinFile.DeleteFile(path)) {
   417          return;
   418        }
   420        if (ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
   421          if ((!("ignoreAbsent" in options) || options.ignoreAbsent)) {
   422            return;
   423          }
   424        } else if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED) {
   425          let attributes = WinFile.GetFileAttributes(path);
   426          if (attributes != Const.INVALID_FILE_ATTRIBUTES &&
   427              attributes & Const.FILE_ATTRIBUTE_READONLY) {
   428            let newAttributes = attributes & ~Const.FILE_ATTRIBUTE_READONLY;
   429            if (WinFile.SetFileAttributes(path, newAttributes) &&
   430                WinFile.DeleteFile(path)) {
   431              return;
   432            }
   433          }
   434        }
   436        throw new File.Error("remove", ctypes.winLastError, path);
   437      };
   439      /**
   440       * Remove an empty directory.
   441       *
   442       * @param {string} path The name of the directory to remove.
   443       * @param {*=} options Additional options.
   444       *   - {bool} ignoreAbsent If |false|, throw an error if the directory
   445       *     does not exist. |true| by default
   446       */
   447      File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
   448        let result = WinFile.RemoveDirectory(path);
   449        if (!result) {
   450          if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
   451              ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
   452            return;
   453          }
   454          throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
   455        }
   456      };
   458      /**
   459       * Create a directory and, optionally, its parent directories.
   460       *
   461       * @param {string} path The name of the directory.
   462       * @param {*=} options Additional options. This
   463       * implementation interprets the following fields:
   464       *
   465       * - {C pointer} winSecurity If specified, security attributes
   466       * as per winapi function |CreateDirectory|. If unspecified,
   467       * use the default security descriptor, inherited from the
   468       * parent directory.
   469       * - {bool} ignoreExisting If |false|, throw an error if the directory
   470       * already exists. |true| by default
   471       * - {string} from If specified, the call to |makeDir| creates all the
   472       * ancestors of |path| that are descendants of |from|. Note that |from|
   473       * and its existing descendants must be user-writeable and that |path|
   474       * must be a descendant of |from|.
   475       * Example:
   476       *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
   477       *  creates directories profileDir/foo, profileDir/foo/bar
   478        */
   479      File._makeDir = function makeDir(path, options = {}) {
   480        let security = options.winSecurity || null;
   481        let result = WinFile.CreateDirectory(path, security);
   483        if (result) {
   484          return;
   485        }
   487        if (("ignoreExisting" in options) && !options.ignoreExisting) {
   488          throw new File.Error("makeDir", ctypes.winLastError, path);
   489        }
   491        if (ctypes.winLastError == Const.ERROR_ALREADY_EXISTS) {
   492          return;
   493        }
   495        // If the user has no access, but it's a root directory, no error should be thrown
   496        let splitPath = OS.Path.split(path);
   497        // Removing last component if it's empty
   498        // An empty last component is caused by trailing slashes in path
   499        // This is always the case with root directories
   500        if( splitPath.components[splitPath.components.length - 1].length === 0 ) {
   501          splitPath.components.pop();
   502        }
   503        // One component consisting of a drive letter implies a directory root.
   504        if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED &&
   505            splitPath.winDrive &&
   506            splitPath.components.length === 1 ) {
   507          return;
   508        }
   510        throw new File.Error("makeDir", ctypes.winLastError, path);
   511      };
   513      /**
   514       * Copy a file to a destination.
   515       *
   516       * @param {string} sourcePath The platform-specific path at which
   517       * the file may currently be found.
   518       * @param {string} destPath The platform-specific path at which the
   519       * file should be copied.
   520       * @param {*=} options An object which may contain the following fields:
   521       *
   522       * @option {bool} noOverwrite - If true, this function will fail if
   523       * a file already exists at |destPath|. Otherwise, if this file exists,
   524       * it will be erased silently.
   525       *
   526       * @throws {OS.File.Error} In case of any error.
   527       *
   528       * General note: The behavior of this function is defined only when
   529       * it is called on a single file. If it is called on a directory, the
   530       * behavior is undefined and may not be the same across all platforms.
   531       *
   532       * General note: The behavior of this function with respect to metadata
   533       * is unspecified. Metadata may or may not be copied with the file. The
   534       * behavior may not be the same across all platforms.
   535      */
   536      File.copy = function copy(sourcePath, destPath, options = {}) {
   537        throw_on_zero("copy",
   538          WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false),
   539          sourcePath
   540        );
   541      };
   543      /**
   544       * Move a file to a destination.
   545       *
   546       * @param {string} sourcePath The platform-specific path at which
   547       * the file may currently be found.
   548       * @param {string} destPath The platform-specific path at which the
   549       * file should be moved.
   550       * @param {*=} options An object which may contain the following fields:
   551       *
   552       * @option {bool} noOverwrite - If set, this function will fail if
   553       * a file already exists at |destPath|. Otherwise, if this file exists,
   554       * it will be erased silently.
   555       * @option {bool} noCopy - If set, this function will fail if the
   556       * operation is more sophisticated than a simple renaming, i.e. if
   557       * |sourcePath| and |destPath| are not situated on the same drive.
   558       *
   559       * @throws {OS.File.Error} In case of any error.
   560       *
   561       * General note: The behavior of this function is defined only when
   562       * it is called on a single file. If it is called on a directory, the
   563       * behavior is undefined and may not be the same across all platforms.
   564       *
   565       * General note: The behavior of this function with respect to metadata
   566       * is unspecified. Metadata may or may not be moved with the file. The
   567       * behavior may not be the same across all platforms.
   568       */
   569      File.move = function move(sourcePath, destPath, options = {}) {
   570        let flags = 0;
   571        if (!options.noCopy) {
   572          flags = Const.MOVEFILE_COPY_ALLOWED;
   573        }
   574        if (!options.noOverwrite) {
   575          flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
   576        }
   577        throw_on_zero("move",
   578          WinFile.MoveFileEx(sourcePath, destPath, flags),
   579          sourcePath
   580        );
   582        // Inherit NTFS permissions from the destination directory
   583        // if possible.
   584        if (Path.dirname(sourcePath) === Path.dirname(destPath)) {
   585          // Skip if the move operation was the simple rename,
   586          return;
   587        }
   588        // The function may fail for various reasons (e.g. not all
   589        // filesystems support NTFS permissions or the user may not
   590        // have the enough rights to read/write permissions).
   591        // However we can safely ignore errors. The file was already
   592        // moved. Setting permissions is not mandatory.
   593        let dacl = new ctypes.voidptr_t();
   594        let sd = new ctypes.voidptr_t();
   595        WinFile.GetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
   596                                     Const.DACL_SECURITY_INFORMATION,
   597                                     null /*sidOwner*/, null /*sidGroup*/,
   598                                     dacl.address(), null /*sacl*/,
   599                                     sd.address());
   600        // dacl will be set only if the function succeeds.
   601        if (!dacl.isNull()) {
   602          WinFile.SetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
   603                                       Const.DACL_SECURITY_INFORMATION |
   604                                       Const.UNPROTECTED_DACL_SECURITY_INFORMATION,
   605                                       null /*sidOwner*/, null /*sidGroup*/,
   606                                       dacl, null /*sacl*/);
   607        }
   608        // sd will be set only if the function succeeds.
   609        if (!sd.isNull()) {
   610            WinFile.LocalFree(Type.HLOCAL.cast(sd));
   611        }
   612      };
   614      /**
   615       * Gets the number of bytes available on disk to the current user.
   616       *
   617       * @param {string} sourcePath Platform-specific path to a directory on 
   618       * the disk to query for free available bytes.
   619       *
   620       * @return {number} The number of bytes available for the current user.
   621       * @throws {OS.File.Error} In case of any error.
   622       */
   623      File.getAvailableFreeSpace = function Win_getAvailableFreeSpace(sourcePath) {
   624        let freeBytesAvailableToUser = new Type.uint64_t.implementation(0);
   625        let freeBytesAvailableToUserPtr = freeBytesAvailableToUser.address();
   627        throw_on_zero("getAvailableFreeSpace",
   628          WinFile.GetDiskFreeSpaceEx(sourcePath, freeBytesAvailableToUserPtr, null, null)
   629        );
   631        return freeBytesAvailableToUser.value;
   632      };
   634      /**
   635       * A global value used to receive data during time conversions.
   636       */
   637      let gSystemTime = new Type.SystemTime.implementation();
   638      let gSystemTimePtr = gSystemTime.address();
   640      /**
   641       * Utility function: convert a FILETIME to a JavaScript Date.
   642       */
   643      let FILETIME_to_Date = function FILETIME_to_Date(fileTime, path) {
   644        if (fileTime == null) {
   645          throw new TypeError("Expecting a non-null filetime");
   646        }
   647        throw_on_zero("FILETIME_to_Date",
   648                      WinFile.FileTimeToSystemTime(fileTime.address(),
   649                                                   gSystemTimePtr),
   650                      path);
   651        // Windows counts hours, minutes, seconds from UTC,
   652        // JS counts from local time, so we need to go through UTC.
   653        let utc = Date.UTC(gSystemTime.wYear,
   654                           gSystemTime.wMonth - 1
   655                           /*Windows counts months from 1, JS from 0*/,
   656                           gSystemTime.wDay, gSystemTime.wHour,
   657                           gSystemTime.wMinute, gSystemTime.wSecond,
   658                           gSystemTime.wMilliSeconds);
   659        return new Date(utc);
   660      };
   662      /**
   663       * Utility function: convert Javascript Date to FileTime.
   664       *
   665       * @param {string} fn Name of the calling function.
   666       * @param {Date,number} date The date to be converted. If omitted or null,
   667       * then the current date will be used. If numeric, assumed to be the date
   668       * in milliseconds since epoch.
   669       */
   670      let Date_to_FILETIME = function Date_to_FILETIME(fn, date, path) {
   671        if (typeof date === "number") {
   672          date = new Date(date);
   673        } else if (!date) {
   674          date = new Date();
   675        } else if (typeof date.getUTCFullYear !== "function") {
   676          throw new TypeError("|date| parameter of " + fn + " must be a " +
   677                              "|Date| instance or number");
   678        }
   679        gSystemTime.wYear = date.getUTCFullYear();
   680        // Windows counts months from 1, JS from 0.
   681        gSystemTime.wMonth = date.getUTCMonth() + 1;
   682        gSystemTime.wDay = date.getUTCDate();
   683        gSystemTime.wHour = date.getUTCHours();
   684        gSystemTime.wMinute = date.getUTCMinutes();
   685        gSystemTime.wSecond = date.getUTCSeconds();
   686        gSystemTime.wMilliseconds = date.getUTCMilliseconds();
   687        let result = new OS.Shared.Type.FILETIME.implementation();
   688        throw_on_zero("Date_to_FILETIME",
   689                      WinFile.SystemTimeToFileTime(gSystemTimePtr,
   690                                                   result.address()),
   691                      path);
   692        return result;
   693      };
   695      /**
   696       * Iterate on one directory.
   697       *
   698       * This iterator will not enter subdirectories.
   699       *
   700       * @param {string} path The directory upon which to iterate.
   701       * @param {*=} options An object that may contain the following field:
   702       * @option {string} winPattern Windows file name pattern; if set,
   703       * only files matching this pattern are returned.
   704       *
   705       * @throws {File.Error} If |path| does not represent a directory or
   706       * if the directory cannot be iterated.
   707       * @constructor
   708       */
   709      File.DirectoryIterator = function DirectoryIterator(path, options) {
   710        exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
   711        if (options && options.winPattern) {
   712          this._pattern = path + "\\" + options.winPattern;
   713        } else {
   714          this._pattern = path + "\\*";
   715        }
   716        this._path = path;
   718        // Pre-open the first item.
   719        this._first = true;
   720        this._findData = new Type.FindData.implementation();
   721        this._findDataPtr = this._findData.address();
   722        this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
   723        if (this._handle == Const.INVALID_HANDLE_VALUE) {
   724          let error = ctypes.winLastError;
   725          this._findData = null;
   726          this._findDataPtr = null;
   727          if (error == Const.ERROR_FILE_NOT_FOUND) {
   728            // Directory is empty, let's behave as if it were closed
   729            SharedAll.LOG("Directory is empty");
   730            this._closed = true;
   731            this._exists = true;
   732          } else if (error == Const.ERROR_PATH_NOT_FOUND) {
   733            // Directory does not exist, let's throw if we attempt to walk it
   734            SharedAll.LOG("Directory does not exist");
   735            this._closed = true;
   736            this._exists = false;
   737          } else {
   738            throw new File.Error("DirectoryIterator", error, this._path);
   739          }
   740        } else {
   741          this._closed = false;
   742          this._exists = true;
   743        }
   744      };
   746      File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
   749      /**
   750       * Fetch the next entry in the directory.
   751       *
   752       * @return null If we have reached the end of the directory.
   753       */
   754      File.DirectoryIterator.prototype._next = function _next() {
   755        // Bailout if the directory does not exist
   756        if (!this._exists) {
   757          throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
   758        }
   759        // Bailout if the iterator is closed.
   760        if (this._closed) {
   761          return null;
   762        }
   763        // If this is the first entry, we have obtained it already
   764        // during construction.
   765        if (this._first) {
   766          this._first = false;
   767          return this._findData;
   768        }
   770        if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
   771          return this._findData;
   772        } else {
   773          let error = ctypes.winLastError;
   774          this.close();
   775          if (error == Const.ERROR_NO_MORE_FILES) {
   776             return null;
   777          } else {
   778             throw new File.Error("iter (FindNextFile)", error, this._path);
   779          }
   780        }
   781      },
   783      /**
   784       * Return the next entry in the directory, if any such entry is
   785       * available.
   786       *
   787       * Skip special directories "." and "..".
   788       *
   789       * @return {File.Entry} The next entry in the directory.
   790       * @throws {StopIteration} Once all files in the directory have been
   791       * encountered.
   792       */
   793      File.DirectoryIterator.prototype.next = function next() {
   794          // FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
   795          // that "." and ".." are absolutely normal file names if _path starts
   796          // with such prefix
   797          for (let entry = this._next(); entry != null; entry = this._next()) {
   798            let name = entry.cFileName.readString();
   799            if (name == "." || name == "..") {
   800              continue;
   801            }
   802            return new File.DirectoryIterator.Entry(entry, this._path);
   803          }
   804          throw StopIteration;
   805      };
   807      File.DirectoryIterator.prototype.close = function close() {
   808        if (this._closed) {
   809          return;
   810        }
   811        this._closed = true;
   812        if (this._handle) {
   813          // We might not have a handle if the iterator is closed
   814          // before being used.
   815          throw_on_zero("FindClose",
   816            WinFile.FindClose(this._handle),
   817            this._path);
   818          this._handle = null;
   819        }
   820      };
   822     /**
   823      * Determine whether the directory exists.
   824      *
   825      * @return {boolean}
   826      */
   827      File.DirectoryIterator.prototype.exists = function exists() {
   828        return this._exists;
   829      };
   831      File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
   832        if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
   833            !win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
   834         throw new TypeError();
   836        // Copy the relevant part of |win_entry| to ensure that
   837        // our data is not overwritten prematurely.
   838        let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
   839        let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
   841        let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime, this._path);
   842        let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime, this._path);
   843        let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime, this._path);
   845        let name = win_entry.cFileName.readString();
   846        if (!name) {
   847          throw new TypeError("Empty name");
   848        }
   850        if (!parent) {
   851          throw new TypeError("Empty parent");
   852        }
   853        this._parent = parent;
   855        let path = Path.join(this._parent, name);
   857        SysAll.AbstractEntry.call(this, isDir, isSymLink, name,
   858          winCreationDate, winLastWriteDate,
   859          winLastAccessDate, path);
   860      };
   861      File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
   863      /**
   864       * Return a version of an instance of
   865       * File.DirectoryIterator.Entry that can be sent from a worker
   866       * thread to the main thread. Note that deserialization is
   867       * asymmetric and returns an object with a different
   868       * implementation.
   869       */
   870      File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
   871        if (!value instanceof File.DirectoryIterator.Entry) {
   872          throw new TypeError("parameter of " +
   873            "File.DirectoryIterator.Entry.toMsg must be a " +
   874            "File.DirectoryIterator.Entry");
   875        }
   876        let serialized = {};
   877        for (let key in File.DirectoryIterator.Entry.prototype) {
   878          serialized[key] = value[key];
   879        }
   880        return serialized;
   881      };
   884      /**
   885       * Information on a file.
   886       *
   887       * To obtain the latest information on a file, use |File.stat|
   888       * (for an unopened file) or |File.prototype.stat| (for an
   889       * already opened file).
   890       *
   891       * @constructor
   892       */
   893      File.Info = function Info(stat, path) {
   894        let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
   895        let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
   897        let winBirthDate = FILETIME_to_Date(stat.ftCreationTime, this._path);
   898        let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime, this._path);
   899        let lastWriteDate = FILETIME_to_Date(stat.ftLastWriteTime, this._path);
   901        let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
   902        let size = Type.uint64_t.importFromC(value);
   904        SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
   905          winBirthDate, lastAccessDate, lastWriteDate);
   906      };
   907      File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
   909      /**
   910       * Return a version of an instance of File.Info that can be sent
   911       * from a worker thread to the main thread. Note that deserialization
   912       * is asymmetric and returns an object with a different implementation.
   913       */
   914      File.Info.toMsg = function toMsg(stat) {
   915        if (!stat instanceof File.Info) {
   916          throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
   917        }
   918        let serialized = {};
   919        for (let key in File.Info.prototype) {
   920          serialized[key] = stat[key];
   921        }
   922        return serialized;
   923      };
   926      /**
   927       * Fetch the information on a file.
   928       *
   929       * Performance note: if you have opened the file already,
   930       * method |File.prototype.stat| is generally much faster
   931       * than method |File.stat|.
   932       *
   933       * Platform-specific note: under Windows, if the file is
   934       * already opened without sharing of the read capability,
   935       * this function will fail.
   936       *
   937       * @return {File.Information}
   938       */
   939      File.stat = function stat(path) {
   940        let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
   941        try {
   942          return file.stat();
   943        } finally {
   944          file.close();
   945        }
   946      };
   947      // All of the following is required to ensure that File.stat
   948      // also works on directories.
   949      const FILE_STAT_MODE = {
   950        read: true
   951      };
   952      const FILE_STAT_OPTIONS = {
   953        // Directories can be opened neither for reading(!) nor for writing
   954        winAccess: 0,
   955        // Directories can only be opened with backup semantics(!)
   956        winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
   957        winDisposition: Const.OPEN_EXISTING
   958      };
   960      /**
   961       * Set the file's access permission bits.
   962       * Not implemented for Windows (bug 1022816).
   963       */
   964      File.setPermissions = function setPermissions(path, options = {}) {
   965          // do nothing
   966      };
   968      /**
   969       * Set the last access and modification date of the file.
   970       * The time stamp resolution is 1 second at best, but might be worse
   971       * depending on the platform.
   972       *
   973       * Performance note: if you have opened the file already in write mode,
   974       * method |File.prototype.stat| is generally much faster
   975       * than method |File.stat|.
   976       *
   977       * Platform-specific note: under Windows, if the file is
   978       * already opened without sharing of the write capability,
   979       * this function will fail.
   980       *
   981       * @param {string} path The full name of the file to set the dates for.
   982       * @param {Date,number=} accessDate The last access date. If numeric,
   983       * milliseconds since epoch. If omitted or null, then the current date
   984       * will be used.
   985       * @param {Date,number=} modificationDate The last modification date. If
   986       * numeric, milliseconds since epoch. If omitted or null, then the current
   987       * date will be used.
   988       *
   989       * @throws {TypeError} In case of invalid paramters.
   990       * @throws {OS.File.Error} In case of I/O error.
   991       */
   992      File.setDates = function setDates(path, accessDate, modificationDate) {
   993        let file = File.open(path, FILE_SETDATES_MODE, FILE_SETDATES_OPTIONS);
   994        try {
   995          return file.setDates(accessDate, modificationDate);
   996        } finally {
   997          file.close();
   998        }
   999      };
  1000      // All of the following is required to ensure that File.setDates
  1001      // also works on directories.
  1002      const FILE_SETDATES_MODE = {
  1003        write: true
  1004      };
  1005      const FILE_SETDATES_OPTIONS = {
  1006        winAccess: Const.GENERIC_WRITE,
  1007        // Directories can only be opened with backup semantics(!)
  1008        winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
  1009        winDisposition: Const.OPEN_EXISTING
  1010      };
  1012      File.read = exports.OS.Shared.AbstractFile.read;
  1013      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
  1014      File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
  1015      File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
  1017      /**
  1018       * Remove an existing directory and its contents.
  1020       * @param {string} path The name of the directory.
  1021       * @param {*=} options Additional options.
  1022       *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
  1023       *     exist. |true| by default.
  1024       *   - {boolean} ignorePermissions If |true|, remove the file even when lacking write
  1025       *     permission.
  1027       * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
  1028       *         not a directory.
  1029       */
  1030      File.removeDir = function(path, options) {
  1031        // We can't use File.stat here because it will follow the symlink.
  1032        let attributes = WinFile.GetFileAttributes(path);
  1033        if (attributes == Const.INVALID_FILE_ATTRIBUTES) {
  1034          if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
  1035              ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
  1036            return;
  1038          throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
  1040        if (attributes & Const.FILE_ATTRIBUTE_REPARSE_POINT) {
  1041          // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
  1042          // directories are directories themselves. OS.File.remove()
  1043          // will not work for them.
  1044          OS.File.removeEmptyDir(path, options);
  1045          return;
  1047        exports.OS.Shared.AbstractFile.removeRecursive(path, options);
  1048      };
  1050      /**
  1051       * Get the current directory by getCurrentDirectory.
  1052       */
  1053      File.getCurrentDirectory = function getCurrentDirectory() {
  1054        // This function is more complicated than one could hope.
  1055        //
  1056        // This is due to two facts:
  1057        // - the maximal length of a path under Windows is not completely
  1058        //  specified (there is a constant MAX_PATH, but it is quite possible
  1059        //  to create paths that are much larger, see bug 744413);
  1060        // - if we attempt to call |GetCurrentDirectory| with a buffer that
  1061        //  is too short, it returns the length of the current directory, but
  1062        //  this length might be insufficient by the time we can call again
  1063        //  the function with a larger buffer, in the (unlikely but possible)
  1064        //  case in which the process changes directory to a directory with
  1065        //  a longer name between both calls.
  1066        //
  1067        let buffer_size = 4096;
  1068        while (true) {
  1069          let array = new (ctypes.ArrayType(ctypes.jschar, buffer_size))();
  1070          let expected_size = throw_on_zero("getCurrentDirectory",
  1071            WinFile.GetCurrentDirectory(buffer_size, array)
  1072          );
  1073          if (expected_size <= buffer_size) {
  1074            return array.readString();
  1076          // At this point, we are in a case in which our buffer was not
  1077          // large enough to hold the name of the current directory.
  1078          // Consequently, we need to increase the size of the buffer.
  1079          // Note that, even in crazy scenarios, the loop will eventually
  1080          // converge, as the length of the paths cannot increase infinitely.
  1081          buffer_size = expected_size + 1 /* to store \0 */;
  1083      };
  1085      /**
  1086       * Set the current directory by setCurrentDirectory.
  1087       */
  1088      File.setCurrentDirectory = function setCurrentDirectory(path) {
  1089        throw_on_zero("setCurrentDirectory",
  1090          WinFile.SetCurrentDirectory(path),
  1091          path);
  1092      };
  1094      /**
  1095       * Get/set the current directory by |curDir|.
  1096       */
  1097      Object.defineProperty(File, "curDir", {
  1098          set: function(path) {
  1099            this.setCurrentDirectory(path);
  1100          },
  1101          get: function() {
  1102            return this.getCurrentDirectory();
  1105      );
  1107      // Utility functions, used for error-handling
  1109      /**
  1110       * Turn the result of |open| into an Error or a File
  1111       * @param {number} maybe The result of the |open| operation that may
  1112       * represent either an error or a success. If -1, this function raises
  1113       * an error holding ctypes.winLastError, otherwise it returns the opened file.
  1114       * @param {string=} path The path of the file.
  1115       */
  1116      function error_or_file(maybe, path) {
  1117        if (maybe == Const.INVALID_HANDLE_VALUE) {
  1118          throw new File.Error("open", ctypes.winLastError, path);
  1120        return new File(maybe, path);
  1123      /**
  1124       * Utility function to sort errors represented as "0" from successes.
  1126       * @param {string=} operation The name of the operation. If unspecified,
  1127       * the name of the caller function.
  1128       * @param {number} result The result of the operation that may
  1129       * represent either an error or a success. If 0, this function raises
  1130       * an error holding ctypes.winLastError, otherwise it returns |result|.
  1131       * @param {string=} path The path of the file.
  1132       */
  1133      function throw_on_zero(operation, result, path) {
  1134        if (result == 0) {
  1135          throw new File.Error(operation, ctypes.winLastError, path);
  1137        return result;
  1140      /**
  1141       * Utility function to sort errors represented as "-1" from successes.
  1143       * @param {string=} operation The name of the operation. If unspecified,
  1144       * the name of the caller function.
  1145       * @param {number} result The result of the operation that may
  1146       * represent either an error or a success. If -1, this function raises
  1147       * an error holding ctypes.winLastError, otherwise it returns |result|.
  1148       * @param {string=} path The path of the file.
  1149       */
  1150      function throw_on_negative(operation, result, path) {
  1151        if (result < 0) {
  1152          throw new File.Error(operation, ctypes.winLastError, path);
  1154        return result;
  1157      /**
  1158       * Utility function to sort errors represented as |null| from successes.
  1160       * @param {string=} operation The name of the operation. If unspecified,
  1161       * the name of the caller function.
  1162       * @param {pointer} result The result of the operation that may
  1163       * represent either an error or a success. If |null|, this function raises
  1164       * an error holding ctypes.winLastError, otherwise it returns |result|.
  1165       * @param {string=} path The path of the file.
  1166       */
  1167      function throw_on_null(operation, result, path) {
  1168        if (result == null || (result.isNull && result.isNull())) {
  1169          throw new File.Error(operation, ctypes.winLastError, path);
  1171        return result;
  1174      File.Win = exports.OS.Win.File;
  1175      File.Error = SysAll.Error;
  1176      exports.OS.File = File;
  1177      exports.OS.Shared.Type = Type;
  1179      Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
  1180      Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
  1181      Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
  1182    })(this);

mercurial