toolkit/components/osfile/modules/osfile_win_front.jsm

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

mercurial