summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/modules/osfile_win_front.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/osfile/modules/osfile_win_front.jsm')
-rw-r--r--toolkit/components/osfile/modules/osfile_win_front.jsm1266
1 files changed, 1266 insertions, 0 deletions
diff --git a/toolkit/components/osfile/modules/osfile_win_front.jsm b/toolkit/components/osfile/modules/osfile_win_front.jsm
new file mode 100644
index 000000000..387dd08b5
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -0,0 +1,1266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Windows implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_win_front.jsm to be used directly as a main thread
+ // module yet.
+ throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
+ }
+
+ (function(exports) {
+ "use strict";
+
+
+ // exports.OS.Win is created by osfile_win_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
+ exports.OS.Win.File._init();
+ let Const = exports.OS.Constants.Win;
+ let WinFile = exports.OS.Win.File;
+ let Type = WinFile.Type;
+
+ // Mutable thread-global data
+ // In the Windows implementation, methods |read| and |write|
+ // require passing a pointer to an uint32 to determine how many
+ // bytes have been read/written. In C, this is a benigne operation,
+ // but in js-ctypes, this has a cost. Rather than re-allocating a
+ // C uint32 and a C uint32* for each |read|/|write|, we take advantage
+ // of the fact that the state is thread-private -- hence that two
+ // |read|/|write| operations cannot take place at the same time --
+ // and we use the following global mutable values:
+ let gBytesRead = new ctypes.uint32_t(0);
+ let gBytesReadPtr = gBytesRead.address();
+ let gBytesWritten = new ctypes.uint32_t(0);
+ let gBytesWrittenPtr = gBytesWritten.address();
+
+ // Same story for GetFileInformationByHandle
+ let gFileInfo = new Type.FILE_INFORMATION.implementation();
+ let gFileInfoPtr = gFileInfo.address();
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = WinFile._CloseHandle(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.winLastError, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options) {
+ // |gBytesReadPtr| is a pointer to |gBytesRead|.
+ throw_on_zero("read",
+ WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null),
+ this._path
+ );
+ return gBytesRead.value;
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options) {
+ if (this._appendMode) {
+ // Need to manually seek on Windows, as O_APPEND is not supported.
+ // This is, of course, a race, but there is no real way around this.
+ this.setPosition(0, File.POS_END);
+ }
+ // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
+ throw_on_zero("write",
+ WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null),
+ this._path
+ );
+ return gBytesWritten.value;
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.FILE_BEGIN;
+ }
+ let pos64 = ctypes.Int64(pos);
+ // Per MSDN, while |lDistanceToMove| (low) is declared as int32_t, when
+ // providing |lDistanceToMoveHigh| as well, it should countain the
+ // bottom 32 bits of the 64-bit integer. Hence the following |posLo|
+ // cast is OK.
+ let posLo = new ctypes.uint32_t(ctypes.Int64.lo(pos64));
+ posLo = ctypes.cast(posLo, ctypes.int32_t);
+ let posHi = new ctypes.int32_t(ctypes.Int64.hi(pos64));
+ let result = WinFile.SetFilePointer(
+ this.fd, posLo.value, posHi.address(), whence);
+ // INVALID_SET_FILE_POINTER might be still a valid result, as it
+ // represents the lower 32 bit of the int64 result. MSDN says to check
+ // both, INVALID_SET_FILE_POINTER and a non-zero winLastError.
+ if (result == Const.INVALID_SET_FILE_POINTER && ctypes.winLastError) {
+ throw new File.Error("setPosition", ctypes.winLastError, this._path);
+ }
+ pos64 = ctypes.Int64.join(posHi.value, result);
+ return Type.int64_t.project(pos64);
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_zero("stat",
+ WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr),
+ this._path);
+ return new File.Info(gFileInfo, this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.setDates = function setDates(accessDate, modificationDate) {
+ accessDate = Date_to_FILETIME("File.prototype.setDates", accessDate, this._path);
+ modificationDate = Date_to_FILETIME("File.prototype.setDates",
+ modificationDate,
+ this._path);
+ throw_on_zero("setDates",
+ WinFile.SetFileTime(this.fd, null, accessDate.address(),
+ modificationDate.address()),
+ this._path);
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(this._path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, this._path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(this._path, newAttributes),
+ this._path);
+ };
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd), this._path);
+ };
+
+ // The default sharing mode for opening files: files are not
+ // locked against being reopened for reading/writing or against
+ // being deleted by the same process or another process.
+ // This is consistent with the default Unix policy.
+ const DEFAULT_SHARE = Const.FILE_SHARE_READ |
+ Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;
+
+ // The default flags for opening files.
+ const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} winShare If specified, a share mode, as per
+ * Windows function |CreateFile|. You can build it from
+ * constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
+ * the file uses the default sharing policy: it can be opened
+ * for reading and/or writing and it can be removed by other
+ * processes and by the same process.
+ * - {number} winSecurity If specified, Windows security
+ * attributes, as per Windows function |CreateFile|. If unspecified,
+ * no security attributes.
+ * - {number} winAccess If specified, Windows access mode, as
+ * per Windows function |CreateFile|. This also requires option
+ * |winDisposition| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ * - {number} winDisposition If specified, Windows disposition mode,
+ * as per Windows function |CreateFile|. This also requires option
+ * |winAccess| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Win_open(path, mode = {}, options = {}) {
+ let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
+ let security = options.winSecurity || null;
+ let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
+ let template = options.winTemplate ? options.winTemplate._fd : null;
+ let access;
+ let disposition;
+
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+
+ // The following option isn't a generic implementation of access to paths
+ // of arbitrary lengths. It allows for the specific case of writing to an
+ // Alternate Data Stream on a file whose path length is already close to
+ // MAX_PATH. This implementation is safe with a full path as input, if
+ // the first part of the path comes from local configuration and the
+ // file without the ADS was successfully opened before, so we know the
+ // path is valid.
+ if (options.winAllowLengthBeyondMaxPathWithCaveats) {
+ // Use the \\?\ syntax to allow lengths beyond MAX_PATH. This limited
+ // implementation only supports a DOS local path or UNC path as input.
+ let isUNC = path.length >= 2 && (path[0] == "\\" || path[0] == "/") &&
+ (path[1] == "\\" || path[1] == "/");
+ let pathToUse = "\\\\?\\" + (isUNC ? "UNC\\" + path.slice(2) : path);
+ // Use GetFullPathName to normalize slashes into backslashes. This is
+ // required because CreateFile won't do this for the \\?\ syntax.
+ let buffer_size = 512;
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("open",
+ WinFile.GetFullPathName(pathToUse, buffer_size, array, 0)
+ );
+ if (expected_size > buffer_size) {
+ // We don't need to allow an arbitrary path length for now.
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ path = array.readString();
+ }
+
+ if ("winAccess" in options && "winDisposition" in options) {
+ access = options.winAccess;
+ disposition = options.winDisposition;
+ } else if (("winAccess" in options && !("winDisposition" in options))
+ ||(!("winAccess" in options) && "winDisposition" in options)) {
+ throw new TypeError("OS.File.open requires either both options " +
+ "winAccess and winDisposition or neither");
+ } else {
+ if (mode.read) {
+ access |= Const.GENERIC_READ;
+ }
+ if (mode.write) {
+ access |= Const.GENERIC_WRITE;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ // It seems that Const.TRUNCATE_EXISTING is broken
+ // in presence of links (source, anyone?). We need
+ // to open normally, then perform truncation manually.
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.CREATE_ALWAYS;
+ }
+ } else if (mode.create) {
+ disposition = Const.CREATE_NEW;
+ } else if (mode.read && !mode.write) {
+ disposition = Const.OPEN_EXISTING;
+ } else if (mode.existing) {
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.OPEN_ALWAYS;
+ }
+ }
+
+ let file = error_or_file(WinFile.CreateFile(path,
+ access, share, security, disposition, flags, template), path);
+
+ file._appendMode = !!mode.append;
+
+ if (!(mode.trunc && mode.existing)) {
+ return file;
+ }
+ // Now, perform manual truncation
+ file.setPosition(0, File.POS_START);
+ throw_on_zero("open",
+ WinFile.SetEndOfFile(file.fd),
+ path);
+ return file;
+ };
+
+ /**
+ * Checks if a file or directory exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Win_exists(path) {
+ try {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ file.close();
+ return true;
+ } catch (x) {
+ return false;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ if (WinFile.DeleteFile(path)) {
+ return;
+ }
+
+ if (ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND ||
+ ctypes.winLastError == Const.ERROR_PATH_NOT_FOUND) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent)) {
+ return;
+ }
+ } else if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED) {
+ // Save winLastError before another ctypes call.
+ let lastError = ctypes.winLastError;
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes != Const.INVALID_FILE_ATTRIBUTES) {
+ if (!(attributes & Const.FILE_ATTRIBUTE_READONLY)) {
+ throw new File.Error("remove", lastError, path);
+ }
+ let newAttributes = attributes & ~Const.FILE_ATTRIBUTE_READONLY;
+ if (WinFile.SetFileAttributes(path, newAttributes) &&
+ WinFile.DeleteFile(path)) {
+ return;
+ }
+ }
+ }
+
+ throw new File.Error("remove", ctypes.winLastError, path);
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = WinFile.RemoveDirectory(path);
+ if (!result) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ };
+
+ /**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {C pointer} winSecurity If specified, security attributes
+ * as per winapi function |CreateDirectory|. If unspecified,
+ * use the default security descriptor, inherited from the
+ * parent directory.
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let security = options.winSecurity || null;
+ let result = WinFile.CreateDirectory(path, security);
+
+ if (result) {
+ return;
+ }
+
+ if (("ignoreExisting" in options) && !options.ignoreExisting) {
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ }
+
+ if (ctypes.winLastError == Const.ERROR_ALREADY_EXISTS) {
+ return;
+ }
+
+ // If the user has no access, but it's a root directory, no error should be thrown
+ let splitPath = OS.Path.split(path);
+ // Removing last component if it's empty
+ // An empty last component is caused by trailing slashes in path
+ // This is always the case with root directories
+ if( splitPath.components[splitPath.components.length - 1].length === 0 ) {
+ splitPath.components.pop();
+ }
+ // One component consisting of a drive letter implies a directory root.
+ if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED &&
+ splitPath.winDrive &&
+ splitPath.components.length === 1 ) {
+ return;
+ }
+
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If true, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ throw_on_zero("copy",
+ WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false),
+ sourcePath
+ );
+ };
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same drive.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = function move(sourcePath, destPath, options = {}) {
+ let flags = 0;
+ if (!options.noCopy) {
+ flags = Const.MOVEFILE_COPY_ALLOWED;
+ }
+ if (!options.noOverwrite) {
+ flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
+ }
+ throw_on_zero("move",
+ WinFile.MoveFileEx(sourcePath, destPath, flags),
+ sourcePath
+ );
+
+ // Inherit NTFS permissions from the destination directory
+ // if possible.
+ if (Path.dirname(sourcePath) === Path.dirname(destPath)) {
+ // Skip if the move operation was the simple rename,
+ return;
+ }
+ // The function may fail for various reasons (e.g. not all
+ // filesystems support NTFS permissions or the user may not
+ // have the enough rights to read/write permissions).
+ // However we can safely ignore errors. The file was already
+ // moved. Setting permissions is not mandatory.
+ let dacl = new ctypes.voidptr_t();
+ let sd = new ctypes.voidptr_t();
+ WinFile.GetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl.address(), null /*sacl*/,
+ sd.address());
+ // dacl will be set only if the function succeeds.
+ if (!dacl.isNull()) {
+ WinFile.SetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION |
+ Const.UNPROTECTED_DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl, null /*sacl*/);
+ }
+ // sd will be set only if the function succeeds.
+ if (!sd.isNull()) {
+ WinFile.LocalFree(Type.HLOCAL.cast(sd));
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Win_getAvailableFreeSpace(sourcePath) {
+ let freeBytesAvailableToUser = new Type.uint64_t.implementation(0);
+ let freeBytesAvailableToUserPtr = freeBytesAvailableToUser.address();
+
+ throw_on_zero("getAvailableFreeSpace",
+ WinFile.GetDiskFreeSpaceEx(sourcePath, freeBytesAvailableToUserPtr, null, null)
+ );
+
+ return freeBytesAvailableToUser.value;
+ };
+
+ /**
+ * A global value used to receive data during time conversions.
+ */
+ let gSystemTime = new Type.SystemTime.implementation();
+ let gSystemTimePtr = gSystemTime.address();
+
+ /**
+ * Utility function: convert a FILETIME to a JavaScript Date.
+ */
+ let FILETIME_to_Date = function FILETIME_to_Date(fileTime, path) {
+ if (fileTime == null) {
+ throw new TypeError("Expecting a non-null filetime");
+ }
+ throw_on_zero("FILETIME_to_Date",
+ WinFile.FileTimeToSystemTime(fileTime.address(),
+ gSystemTimePtr),
+ path);
+ // Windows counts hours, minutes, seconds from UTC,
+ // JS counts from local time, so we need to go through UTC.
+ let utc = Date.UTC(gSystemTime.wYear,
+ gSystemTime.wMonth - 1
+ /*Windows counts months from 1, JS from 0*/,
+ gSystemTime.wDay, gSystemTime.wHour,
+ gSystemTime.wMinute, gSystemTime.wSecond,
+ gSystemTime.wMilliSeconds);
+ return new Date(utc);
+ };
+
+ /**
+ * Utility function: convert Javascript Date to FileTime.
+ *
+ * @param {string} fn Name of the calling function.
+ * @param {Date,number} date The date to be converted. If omitted or null,
+ * then the current date will be used. If numeric, assumed to be the date
+ * in milliseconds since epoch.
+ */
+ let Date_to_FILETIME = function Date_to_FILETIME(fn, date, path) {
+ if (typeof date === "number") {
+ date = new Date(date);
+ } else if (!date) {
+ date = new Date();
+ } else if (typeof date.getUTCFullYear !== "function") {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ gSystemTime.wYear = date.getUTCFullYear();
+ // Windows counts months from 1, JS from 0.
+ gSystemTime.wMonth = date.getUTCMonth() + 1;
+ gSystemTime.wDay = date.getUTCDate();
+ gSystemTime.wHour = date.getUTCHours();
+ gSystemTime.wMinute = date.getUTCMinutes();
+ gSystemTime.wSecond = date.getUTCSeconds();
+ gSystemTime.wMilliseconds = date.getUTCMilliseconds();
+ let result = new OS.Shared.Type.FILETIME.implementation();
+ throw_on_zero("Date_to_FILETIME",
+ WinFile.SystemTimeToFileTime(gSystemTimePtr,
+ result.address()),
+ path);
+ return result;
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options An object that may contain the following field:
+ * @option {string} winPattern Windows file name pattern; if set,
+ * only files matching this pattern are returned.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ if (options && options.winPattern) {
+ this._pattern = path + "\\" + options.winPattern;
+ } else {
+ this._pattern = path + "\\*";
+ }
+ this._path = path;
+
+ // Pre-open the first item.
+ this._first = true;
+ this._findData = new Type.FindData.implementation();
+ this._findDataPtr = this._findData.address();
+ this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
+ if (this._handle == Const.INVALID_HANDLE_VALUE) {
+ let error = ctypes.winLastError;
+ this._findData = null;
+ this._findDataPtr = null;
+ if (error == Const.ERROR_FILE_NOT_FOUND) {
+ // Directory is empty, let's behave as if it were closed
+ SharedAll.LOG("Directory is empty");
+ this._closed = true;
+ this._exists = true;
+ } else if (error == Const.ERROR_PATH_NOT_FOUND) {
+ // Directory does not exist, let's throw if we attempt to walk it
+ SharedAll.LOG("Directory does not exist");
+ this._closed = true;
+ this._exists = false;
+ } else {
+ throw new File.Error("DirectoryIterator", error, this._path);
+ }
+ } else {
+ this._closed = false;
+ this._exists = true;
+ }
+ };
+
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+
+ /**
+ * Fetch the next entry in the directory.
+ *
+ * @return null If we have reached the end of the directory.
+ */
+ File.DirectoryIterator.prototype._next = function _next() {
+ // Bailout if the directory does not exist
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ // Bailout if the iterator is closed.
+ if (this._closed) {
+ return null;
+ }
+ // If this is the first entry, we have obtained it already
+ // during construction.
+ if (this._first) {
+ this._first = false;
+ return this._findData;
+ }
+
+ if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
+ return this._findData;
+ } else {
+ let error = ctypes.winLastError;
+ this.close();
+ if (error == Const.ERROR_NO_MORE_FILES) {
+ return null;
+ } else {
+ throw new File.Error("iter (FindNextFile)", error, this._path);
+ }
+ }
+ },
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ // FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
+ // that "." and ".." are absolutely normal file names if _path starts
+ // with such prefix
+ for (let entry = this._next(); entry != null; entry = this._next()) {
+ let name = entry.cFileName.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+ return new File.DirectoryIterator.Entry(entry, this._path);
+ }
+ throw StopIteration;
+ };
+
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) {
+ return;
+ }
+ this._closed = true;
+ if (this._handle) {
+ // We might not have a handle if the iterator is closed
+ // before being used.
+ throw_on_zero("FindClose",
+ WinFile.FindClose(this._handle),
+ this._path);
+ this._handle = null;
+ }
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
+ if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
+ !win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
+ throw new TypeError();
+
+ // Copy the relevant part of |win_entry| to ensure that
+ // our data is not overwritten prematurely.
+ let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime, this._path);
+ let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime, this._path);
+ let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime, this._path);
+
+ let name = win_entry.cFileName.readString();
+ if (!name) {
+ throw new TypeError("Empty name");
+ }
+
+ if (!parent) {
+ throw new TypeError("Empty parent");
+ }
+ this._parent = parent;
+
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name,
+ winCreationDate, winLastWriteDate,
+ winLastAccessDate, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!value instanceof File.DirectoryIterator.Entry) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Information on a file.
+ *
+ * To obtain the latest information on a file, use |File.stat|
+ * (for an unopened file) or |File.prototype.stat| (for an
+ * already opened file).
+ *
+ * @constructor
+ */
+ File.Info = function Info(stat, path) {
+ let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winBirthDate = FILETIME_to_Date(stat.ftCreationTime, this._path);
+ let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime, this._path);
+ let lastWriteDate = FILETIME_to_Date(stat.ftLastWriteTime, this._path);
+
+ let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
+ let size = Type.uint64_t.importFromC(value);
+ let winAttributes = {
+ readOnly: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_READONLY),
+ system: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_SYSTEM),
+ hidden: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_HIDDEN),
+ };
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ winBirthDate, lastAccessDate, lastWriteDate, winAttributes);
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!stat instanceof File.Info) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Fetch the information on a file.
+ *
+ * Performance note: if you have opened the file already,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the read capability,
+ * this function will fail.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path) {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ try {
+ return file.stat();
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.stat
+ // also works on directories.
+ const FILE_STAT_MODE = {
+ read: true
+ };
+ const FILE_STAT_OPTIONS = {
+ // Directories can be opened neither for reading(!) nor for writing
+ winAccess: 0,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(path, newAttributes),
+ path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * Performance note: if you have opened the file already in write mode,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the write capability,
+ * this function will fail.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let file = File.open(path, FILE_SETDATES_MODE, FILE_SETDATES_OPTIONS);
+ try {
+ return file.setDates(accessDate, modificationDate);
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.setDates
+ // also works on directories.
+ const FILE_SETDATES_MODE = {
+ write: true
+ };
+ const FILE_SETDATES_OPTIONS = {
+ winAccess: Const.GENERIC_WRITE,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ // We can't use File.stat here because it will follow the symlink.
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes == Const.INVALID_FILE_ATTRIBUTES) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ if (attributes & Const.FILE_ATTRIBUTE_REPARSE_POINT) {
+ // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
+ // directories are directories themselves. OS.File.remove()
+ // will not work for them.
+ OS.File.removeEmptyDir(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ // This function is more complicated than one could hope.
+ //
+ // This is due to two facts:
+ // - the maximal length of a path under Windows is not completely
+ // specified (there is a constant MAX_PATH, but it is quite possible
+ // to create paths that are much larger, see bug 744413);
+ // - if we attempt to call |GetCurrentDirectory| with a buffer that
+ // is too short, it returns the length of the current directory, but
+ // this length might be insufficient by the time we can call again
+ // the function with a larger buffer, in the (unlikely but possible)
+ // case in which the process changes directory to a directory with
+ // a longer name between both calls.
+ //
+ let buffer_size = 4096;
+ while (true) {
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("getCurrentDirectory",
+ WinFile.GetCurrentDirectory(buffer_size, array)
+ );
+ if (expected_size <= buffer_size) {
+ return array.readString();
+ }
+ // At this point, we are in a case in which our buffer was not
+ // large enough to hold the name of the current directory.
+ // Consequently, we need to increase the size of the buffer.
+ // Note that, even in crazy scenarios, the loop will eventually
+ // converge, as the length of the paths cannot increase infinitely.
+ buffer_size = expected_size + 1 /* to store \0 */;
+ }
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_zero("setCurrentDirectory",
+ WinFile.SetCurrentDirectory(path),
+ path);
+ };
+
+ /**
+ * Get/set the current directory by |curDir|.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions, used for error-handling
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == Const.INVALID_HANDLE_VALUE) {
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "0" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If 0, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_zero(operation, result, path) {
+ if (result == 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Helper used by both versions of setPermissions
+ */
+ function toFileAttributes(winAttributes, oldDwAttrs) {
+ if ("readOnly" in winAttributes) {
+ if (winAttributes.readOnly) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_READONLY;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_READONLY;
+ }
+ }
+ if ("system" in winAttributes) {
+ if (winAttributes.system) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_SYSTEM;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_SYSTEM;
+ }
+ }
+ if ("hidden" in winAttributes) {
+ if (winAttributes.hidden) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_HIDDEN;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_HIDDEN;
+ }
+ }
+ return oldDwAttrs;
+ }
+
+ File.Win = exports.OS.Win.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}