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