toolkit/components/osfile/modules/osfile_win_front.jsm

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

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

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

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

mercurial