Sat, 03 Jan 2015 20:18:00 +0100
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 | } |