|
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 * Unix 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_unix_front.jsm to be used directly as a main thread |
|
15 // module yet. |
|
16 |
|
17 throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet"); |
|
18 } |
|
19 (function(exports) { |
|
20 "use strict"; |
|
21 |
|
22 // exports.OS.Unix is created by osfile_unix_back.jsm |
|
23 if (exports.OS && exports.OS.File) { |
|
24 return; // Avoid double-initialization |
|
25 } |
|
26 |
|
27 let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); |
|
28 let Path = require("resource://gre/modules/osfile/ospath.jsm"); |
|
29 let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm"); |
|
30 exports.OS.Unix.File._init(); |
|
31 let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end"); |
|
32 let Const = SharedAll.Constants.libc; |
|
33 let UnixFile = exports.OS.Unix.File; |
|
34 let Type = UnixFile.Type; |
|
35 |
|
36 /** |
|
37 * Representation of a file. |
|
38 * |
|
39 * You generally do not need to call this constructor yourself. Rather, |
|
40 * to open a file, use function |OS.File.open|. |
|
41 * |
|
42 * @param fd A OS-specific file descriptor. |
|
43 * @param {string} path File path of the file handle, used for error-reporting. |
|
44 * @constructor |
|
45 */ |
|
46 let File = function File(fd, path) { |
|
47 exports.OS.Shared.AbstractFile.call(this, fd, path); |
|
48 this._closeResult = null; |
|
49 }; |
|
50 File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype); |
|
51 |
|
52 /** |
|
53 * Close the file. |
|
54 * |
|
55 * This method has no effect if the file is already closed. However, |
|
56 * if the first call to |close| has thrown an error, further calls |
|
57 * will throw the same error. |
|
58 * |
|
59 * @throws File.Error If closing the file revealed an error that could |
|
60 * not be reported earlier. |
|
61 */ |
|
62 File.prototype.close = function close() { |
|
63 if (this._fd) { |
|
64 let fd = this._fd; |
|
65 this._fd = null; |
|
66 // Call |close(fd)|, detach finalizer if any |
|
67 // (|fd| may not be a CDataFinalizer if it has been |
|
68 // instantiated from a controller thread). |
|
69 let result = UnixFile._close(fd); |
|
70 if (typeof fd == "object" && "forget" in fd) { |
|
71 fd.forget(); |
|
72 } |
|
73 if (result == -1) { |
|
74 this._closeResult = new File.Error("close", ctypes.errno, this._path); |
|
75 } |
|
76 } |
|
77 if (this._closeResult) { |
|
78 throw this._closeResult; |
|
79 } |
|
80 return; |
|
81 }; |
|
82 |
|
83 /** |
|
84 * Read some bytes from a file. |
|
85 * |
|
86 * @param {C pointer} buffer A buffer for holding the data |
|
87 * once it is read. |
|
88 * @param {number} nbytes The number of bytes to read. It must not |
|
89 * exceed the size of |buffer| in bytes but it may exceed the number |
|
90 * of bytes unread in the file. |
|
91 * @param {*=} options Additional options for reading. Ignored in |
|
92 * this implementation. |
|
93 * |
|
94 * @return {number} The number of bytes effectively read. If zero, |
|
95 * the end of the file has been reached. |
|
96 * @throws {OS.File.Error} In case of I/O error. |
|
97 */ |
|
98 File.prototype._read = function _read(buffer, nbytes, options = {}) { |
|
99 // Populate the page cache with data from a file so the subsequent reads |
|
100 // from that file will not block on disk I/O. |
|
101 if (typeof(UnixFile.posix_fadvise) === 'function' && |
|
102 (options.sequential || !("sequential" in options))) { |
|
103 UnixFile.posix_fadvise(this.fd, 0, nbytes, |
|
104 OS.Constants.libc.POSIX_FADV_SEQUENTIAL); |
|
105 } |
|
106 return throw_on_negative("read", |
|
107 UnixFile.read(this.fd, buffer, nbytes), |
|
108 this._path |
|
109 ); |
|
110 }; |
|
111 |
|
112 /** |
|
113 * Write some bytes to a file. |
|
114 * |
|
115 * @param {C pointer} buffer A buffer holding the data that must be |
|
116 * written. |
|
117 * @param {number} nbytes The number of bytes to write. It must not |
|
118 * exceed the size of |buffer| in bytes. |
|
119 * @param {*=} options Additional options for writing. Ignored in |
|
120 * this implementation. |
|
121 * |
|
122 * @return {number} The number of bytes effectively written. |
|
123 * @throws {OS.File.Error} In case of I/O error. |
|
124 */ |
|
125 File.prototype._write = function _write(buffer, nbytes, options = {}) { |
|
126 return throw_on_negative("write", |
|
127 UnixFile.write(this.fd, buffer, nbytes), |
|
128 this._path |
|
129 ); |
|
130 }; |
|
131 |
|
132 /** |
|
133 * Return the current position in the file. |
|
134 */ |
|
135 File.prototype.getPosition = function getPosition(pos) { |
|
136 return this.setPosition(0, File.POS_CURRENT); |
|
137 }; |
|
138 |
|
139 /** |
|
140 * Change the current position in the file. |
|
141 * |
|
142 * @param {number} pos The new position. Whether this position |
|
143 * is considered from the current position, from the start of |
|
144 * the file or from the end of the file is determined by |
|
145 * argument |whence|. Note that |pos| may exceed the length of |
|
146 * the file. |
|
147 * @param {number=} whence The reference position. If omitted |
|
148 * or |OS.File.POS_START|, |pos| is relative to the start of the |
|
149 * file. If |OS.File.POS_CURRENT|, |pos| is relative to the |
|
150 * current position in the file. If |OS.File.POS_END|, |pos| is |
|
151 * relative to the end of the file. |
|
152 * |
|
153 * @return The new position in the file. |
|
154 */ |
|
155 File.prototype.setPosition = function setPosition(pos, whence) { |
|
156 if (whence === undefined) { |
|
157 whence = Const.SEEK_SET; |
|
158 } |
|
159 return throw_on_negative("setPosition", |
|
160 UnixFile.lseek(this.fd, pos, whence), |
|
161 this._path |
|
162 ); |
|
163 }; |
|
164 |
|
165 /** |
|
166 * Fetch the information on the file. |
|
167 * |
|
168 * @return File.Info The information on |this| file. |
|
169 */ |
|
170 File.prototype.stat = function stat() { |
|
171 throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr), |
|
172 this._path); |
|
173 return new File.Info(gStatData, this._path); |
|
174 }; |
|
175 |
|
176 /** |
|
177 * Set the file's access permissions. Without any options, the |
|
178 * permissions are set to an approximation of what they would |
|
179 * have been if the file had been created in its current |
|
180 * directory in the "most typical" fashion for the operating |
|
181 * system. In the current implementation, this means we set |
|
182 * the POSIX file mode to (0666 & ~umask). |
|
183 * |
|
184 * This operation is likely to fail if applied to a file that was |
|
185 * not created by the currently running program (more precisely, |
|
186 * if it was created by a program running under a different OS-level |
|
187 * user account). It may also fail, or silently do nothing, if the |
|
188 * filesystem containing the file does not support access permissions. |
|
189 * |
|
190 * @param {*=} options |
|
191 * - {number} unixMode If present, the POSIX file mode is set to |
|
192 * exactly this value, unless |unixHonorUmask| is |
|
193 * also present. |
|
194 * - {bool} unixHonorUmask If true, any |unixMode| value is modified by |
|
195 * the process umask, as open() would have done. |
|
196 */ |
|
197 File.prototype.setPermissions = function setPermissions(options = {}) { |
|
198 throw_on_negative("setPermissions", |
|
199 UnixFile.fchmod(this.fd, unixMode(options)), |
|
200 this._path); |
|
201 }; |
|
202 |
|
203 /** |
|
204 * Set the last access and modification date of the file. |
|
205 * The time stamp resolution is 1 second at best, but might be worse |
|
206 * depending on the platform. |
|
207 * |
|
208 * @param {Date,number=} accessDate The last access date. If numeric, |
|
209 * milliseconds since epoch. If omitted or null, then the current date |
|
210 * will be used. |
|
211 * @param {Date,number=} modificationDate The last modification date. If |
|
212 * numeric, milliseconds since epoch. If omitted or null, then the current |
|
213 * date will be used. |
|
214 * |
|
215 * @throws {TypeError} In case of invalid parameters. |
|
216 * @throws {OS.File.Error} In case of I/O error. |
|
217 */ |
|
218 File.prototype.setDates = function setDates(accessDate, modificationDate) { |
|
219 accessDate = normalizeDate("File.prototype.setDates", accessDate); |
|
220 modificationDate = normalizeDate("File.prototype.setDates", |
|
221 modificationDate); |
|
222 gTimevals[0].tv_sec = (accessDate / 1000) | 0; |
|
223 gTimevals[0].tv_usec = 0; |
|
224 gTimevals[1].tv_sec = (modificationDate / 1000) | 0; |
|
225 gTimevals[1].tv_usec = 0; |
|
226 throw_on_negative("setDates", |
|
227 UnixFile.futimes(this.fd, gTimevalsPtr), |
|
228 this._path); |
|
229 }; |
|
230 |
|
231 /** |
|
232 * Flushes the file's buffers and causes all buffered data |
|
233 * to be written. |
|
234 * Disk flushes are very expensive and therefore should be used carefully, |
|
235 * sparingly and only in scenarios where it is vital that data survives |
|
236 * system crashes. Even though the function will be executed off the |
|
237 * main-thread, it might still affect the overall performance of any |
|
238 * running application. |
|
239 * |
|
240 * @throws {OS.File.Error} In case of I/O error. |
|
241 */ |
|
242 File.prototype.flush = function flush() { |
|
243 throw_on_negative("flush", UnixFile.fsync(this.fd), this._path); |
|
244 }; |
|
245 |
|
246 // The default unix mode for opening (0600) |
|
247 const DEFAULT_UNIX_MODE = 384; |
|
248 |
|
249 /** |
|
250 * Open a file |
|
251 * |
|
252 * @param {string} path The path to the file. |
|
253 * @param {*=} mode The opening mode for the file, as |
|
254 * an object that may contain the following fields: |
|
255 * |
|
256 * - {bool} truncate If |true|, the file will be opened |
|
257 * for writing. If the file does not exist, it will be |
|
258 * created. If the file exists, its contents will be |
|
259 * erased. Cannot be specified with |create|. |
|
260 * - {bool} create If |true|, the file will be opened |
|
261 * for writing. If the file exists, this function fails. |
|
262 * If the file does not exist, it will be created. Cannot |
|
263 * be specified with |truncate| or |existing|. |
|
264 * - {bool} existing. If the file does not exist, this function |
|
265 * fails. Cannot be specified with |create|. |
|
266 * - {bool} read If |true|, the file will be opened for |
|
267 * reading. The file may also be opened for writing, depending |
|
268 * on the other fields of |mode|. |
|
269 * - {bool} write If |true|, the file will be opened for |
|
270 * writing. The file may also be opened for reading, depending |
|
271 * on the other fields of |mode|. |
|
272 * - {bool} append If |true|, the file will be opened for appending, |
|
273 * meaning the equivalent of |.setPosition(0, POS_END)| is executed |
|
274 * before each write. The default is |true|, i.e. opening a file for |
|
275 * appending. Specify |append: false| to open the file in regular mode. |
|
276 * |
|
277 * If neither |truncate|, |create| or |write| is specified, the file |
|
278 * is opened for reading. |
|
279 * |
|
280 * Note that |false|, |null| or |undefined| flags are simply ignored. |
|
281 * |
|
282 * @param {*=} options Additional options for file opening. This |
|
283 * implementation interprets the following fields: |
|
284 * |
|
285 * - {number} unixFlags If specified, file opening flags, as |
|
286 * per libc function |open|. Replaces |mode|. |
|
287 * - {number} unixMode If specified, a file creation mode, |
|
288 * as per libc function |open|. If unspecified, files are |
|
289 * created with a default mode of 0600 (file is private to the |
|
290 * user, the user can read and write). |
|
291 * |
|
292 * @return {File} A file object. |
|
293 * @throws {OS.File.Error} If the file could not be opened. |
|
294 */ |
|
295 File.open = function Unix_open(path, mode, options = {}) { |
|
296 let omode = options.unixMode !== undefined ? |
|
297 options.unixMode : DEFAULT_UNIX_MODE; |
|
298 let flags; |
|
299 if (options.unixFlags !== undefined) { |
|
300 flags = options.unixFlags; |
|
301 } else { |
|
302 mode = OS.Shared.AbstractFile.normalizeOpenMode(mode); |
|
303 // Handle read/write |
|
304 if (!mode.write) { |
|
305 flags = Const.O_RDONLY; |
|
306 } else if (mode.read) { |
|
307 flags = Const.O_RDWR; |
|
308 } else { |
|
309 flags = Const.O_WRONLY; |
|
310 } |
|
311 // Finally, handle create/existing/trunc |
|
312 if (mode.trunc) { |
|
313 if (mode.existing) { |
|
314 flags |= Const.O_TRUNC; |
|
315 } else { |
|
316 flags |= Const.O_CREAT | Const.O_TRUNC; |
|
317 } |
|
318 } else if (mode.create) { |
|
319 flags |= Const.O_CREAT | Const.O_EXCL; |
|
320 } else if (mode.read && !mode.write) { |
|
321 // flags are sufficient |
|
322 } else if (!mode.existing) { |
|
323 flags |= Const.O_CREAT; |
|
324 } |
|
325 if (mode.append) { |
|
326 flags |= Const.O_APPEND; |
|
327 } |
|
328 } |
|
329 return error_or_file(UnixFile.open(path, flags, omode), path); |
|
330 }; |
|
331 |
|
332 /** |
|
333 * Checks if a file exists |
|
334 * |
|
335 * @param {string} path The path to the file. |
|
336 * |
|
337 * @return {bool} true if the file exists, false otherwise. |
|
338 */ |
|
339 File.exists = function Unix_exists(path) { |
|
340 if (UnixFile.access(path, Const.F_OK) == -1) { |
|
341 return false; |
|
342 } else { |
|
343 return true; |
|
344 } |
|
345 }; |
|
346 |
|
347 /** |
|
348 * Remove an existing file. |
|
349 * |
|
350 * @param {string} path The name of the file. |
|
351 * @param {*=} options Additional options. |
|
352 * - {bool} ignoreAbsent If |false|, throw an error if the file does |
|
353 * not exist. |true| by default. |
|
354 * |
|
355 * @throws {OS.File.Error} In case of I/O error. |
|
356 */ |
|
357 File.remove = function remove(path, options = {}) { |
|
358 let result = UnixFile.unlink(path); |
|
359 if (result == -1) { |
|
360 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
|
361 ctypes.errno == Const.ENOENT) { |
|
362 return; |
|
363 } |
|
364 throw new File.Error("remove", ctypes.errno, path); |
|
365 } |
|
366 }; |
|
367 |
|
368 /** |
|
369 * Remove an empty directory. |
|
370 * |
|
371 * @param {string} path The name of the directory to remove. |
|
372 * @param {*=} options Additional options. |
|
373 * - {bool} ignoreAbsent If |false|, throw an error if the directory |
|
374 * does not exist. |true| by default |
|
375 */ |
|
376 File.removeEmptyDir = function removeEmptyDir(path, options = {}) { |
|
377 let result = UnixFile.rmdir(path); |
|
378 if (result == -1) { |
|
379 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
|
380 ctypes.errno == Const.ENOENT) { |
|
381 return; |
|
382 } |
|
383 throw new File.Error("removeEmptyDir", ctypes.errno, path); |
|
384 } |
|
385 }; |
|
386 |
|
387 /** |
|
388 * Gets the number of bytes available on disk to the current user. |
|
389 * |
|
390 * @param {string} sourcePath Platform-specific path to a directory on |
|
391 * the disk to query for free available bytes. |
|
392 * |
|
393 * @return {number} The number of bytes available for the current user. |
|
394 * @throws {OS.File.Error} In case of any error. |
|
395 */ |
|
396 File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) { |
|
397 let fileSystemInfo = new Type.statvfs.implementation(); |
|
398 let fileSystemInfoPtr = fileSystemInfo.address(); |
|
399 |
|
400 throw_on_negative("statvfs", UnixFile.statvfs(sourcePath, fileSystemInfoPtr)); |
|
401 |
|
402 let bytes = new Type.uint64_t.implementation( |
|
403 fileSystemInfo.f_bsize * fileSystemInfo.f_bavail); |
|
404 |
|
405 return bytes.value; |
|
406 }; |
|
407 |
|
408 /** |
|
409 * Default mode for opening directories. |
|
410 */ |
|
411 const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU; |
|
412 |
|
413 /** |
|
414 * Create a directory. |
|
415 * |
|
416 * @param {string} path The name of the directory. |
|
417 * @param {*=} options Additional options. This |
|
418 * implementation interprets the following fields: |
|
419 * |
|
420 * - {number} unixMode If specified, a file creation mode, |
|
421 * as per libc function |mkdir|. If unspecified, dirs are |
|
422 * created with a default mode of 0700 (dir is private to |
|
423 * the user, the user can read, write and execute). |
|
424 * - {bool} ignoreExisting If |false|, throw error if the directory |
|
425 * already exists. |true| by default |
|
426 * - {string} from If specified, the call to |makeDir| creates all the |
|
427 * ancestors of |path| that are descendants of |from|. Note that |from| |
|
428 * and its existing descendants must be user-writeable and that |path| |
|
429 * must be a descendant of |from|. |
|
430 * Example: |
|
431 * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir }); |
|
432 * creates directories profileDir/foo, profileDir/foo/bar |
|
433 */ |
|
434 File._makeDir = function makeDir(path, options = {}) { |
|
435 let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR; |
|
436 let result = UnixFile.mkdir(path, omode); |
|
437 if (result == -1) { |
|
438 if ((!("ignoreExisting" in options) || options.ignoreExisting) && |
|
439 (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) { |
|
440 return; |
|
441 } |
|
442 throw new File.Error("makeDir", ctypes.errno, path); |
|
443 } |
|
444 }; |
|
445 |
|
446 /** |
|
447 * Copy a file to a destination. |
|
448 * |
|
449 * @param {string} sourcePath The platform-specific path at which |
|
450 * the file may currently be found. |
|
451 * @param {string} destPath The platform-specific path at which the |
|
452 * file should be copied. |
|
453 * @param {*=} options An object which may contain the following fields: |
|
454 * |
|
455 * @option {bool} noOverwrite - If set, this function will fail if |
|
456 * a file already exists at |destPath|. Otherwise, if this file exists, |
|
457 * it will be erased silently. |
|
458 * |
|
459 * @throws {OS.File.Error} In case of any error. |
|
460 * |
|
461 * General note: The behavior of this function is defined only when |
|
462 * it is called on a single file. If it is called on a directory, the |
|
463 * behavior is undefined and may not be the same across all platforms. |
|
464 * |
|
465 * General note: The behavior of this function with respect to metadata |
|
466 * is unspecified. Metadata may or may not be copied with the file. The |
|
467 * behavior may not be the same across all platforms. |
|
468 */ |
|
469 File.copy = null; |
|
470 |
|
471 /** |
|
472 * Move a file to a destination. |
|
473 * |
|
474 * @param {string} sourcePath The platform-specific path at which |
|
475 * the file may currently be found. |
|
476 * @param {string} destPath The platform-specific path at which the |
|
477 * file should be moved. |
|
478 * @param {*=} options An object which may contain the following fields: |
|
479 * |
|
480 * @option {bool} noOverwrite - If set, this function will fail if |
|
481 * a file already exists at |destPath|. Otherwise, if this file exists, |
|
482 * it will be erased silently. |
|
483 * @option {bool} noCopy - If set, this function will fail if the |
|
484 * operation is more sophisticated than a simple renaming, i.e. if |
|
485 * |sourcePath| and |destPath| are not situated on the same device. |
|
486 * |
|
487 * @throws {OS.File.Error} In case of any error. |
|
488 * |
|
489 * General note: The behavior of this function is defined only when |
|
490 * it is called on a single file. If it is called on a directory, the |
|
491 * behavior is undefined and may not be the same across all platforms. |
|
492 * |
|
493 * General note: The behavior of this function with respect to metadata |
|
494 * is unspecified. Metadata may or may not be moved with the file. The |
|
495 * behavior may not be the same across all platforms. |
|
496 */ |
|
497 File.move = null; |
|
498 |
|
499 if (UnixFile.copyfile) { |
|
500 // This implementation uses |copyfile(3)|, from the BSD library. |
|
501 // Adding copying of hierarchies and/or attributes is just a flag |
|
502 // away. |
|
503 File.copy = function copyfile(sourcePath, destPath, options = {}) { |
|
504 let flags = Const.COPYFILE_DATA; |
|
505 if (options.noOverwrite) { |
|
506 flags |= Const.COPYFILE_EXCL; |
|
507 } |
|
508 throw_on_negative("copy", |
|
509 UnixFile.copyfile(sourcePath, destPath, null, flags), |
|
510 sourcePath |
|
511 ); |
|
512 }; |
|
513 } else { |
|
514 // If the OS does not implement file copying for us, we need to |
|
515 // implement it ourselves. For this purpose, we need to define |
|
516 // a pumping function. |
|
517 |
|
518 /** |
|
519 * Copy bytes from one file to another one. |
|
520 * |
|
521 * @param {File} source The file containing the data to be copied. It |
|
522 * should be opened for reading. |
|
523 * @param {File} dest The file to which the data should be written. It |
|
524 * should be opened for writing. |
|
525 * @param {*=} options An object which may contain the following fields: |
|
526 * |
|
527 * @option {number} nbytes The maximal number of bytes to |
|
528 * copy. If unspecified, copy everything from the current |
|
529 * position. |
|
530 * @option {number} bufSize A hint regarding the size of the |
|
531 * buffer to use for copying. The implementation may decide to |
|
532 * ignore this hint. |
|
533 * @option {bool} unixUserland Will force the copy operation to be |
|
534 * caried out in user land, instead of using optimized syscalls such |
|
535 * as splice(2). |
|
536 * |
|
537 * @throws {OS.File.Error} In case of error. |
|
538 */ |
|
539 let pump; |
|
540 |
|
541 // A buffer used by |pump_userland| |
|
542 let pump_buffer = null; |
|
543 |
|
544 // An implementation of |pump| using |read|/|write| |
|
545 let pump_userland = function pump_userland(source, dest, options = {}) { |
|
546 let bufSize = options.bufSize > 0 ? options.bufSize : 4096; |
|
547 let nbytes = options.nbytes > 0 ? options.nbytes : Infinity; |
|
548 if (!pump_buffer || pump_buffer.length < bufSize) { |
|
549 pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize); |
|
550 } |
|
551 let read = source._read.bind(source); |
|
552 let write = dest._write.bind(dest); |
|
553 // Perform actual copy |
|
554 let total_read = 0; |
|
555 while (true) { |
|
556 let chunk_size = Math.min(nbytes, bufSize); |
|
557 let bytes_just_read = read(pump_buffer, bufSize); |
|
558 if (bytes_just_read == 0) { |
|
559 return total_read; |
|
560 } |
|
561 total_read += bytes_just_read; |
|
562 let bytes_written = 0; |
|
563 do { |
|
564 bytes_written += write( |
|
565 pump_buffer.addressOfElement(bytes_written), |
|
566 bytes_just_read - bytes_written |
|
567 ); |
|
568 } while (bytes_written < bytes_just_read); |
|
569 nbytes -= bytes_written; |
|
570 if (nbytes <= 0) { |
|
571 return total_read; |
|
572 } |
|
573 } |
|
574 }; |
|
575 |
|
576 // Fortunately, under Linux, that pumping function can be optimized. |
|
577 if (UnixFile.splice) { |
|
578 const BUFSIZE = 1 << 17; |
|
579 |
|
580 // An implementation of |pump| using |splice| (for Linux/Android) |
|
581 pump = function pump_splice(source, dest, options = {}) { |
|
582 let nbytes = options.nbytes > 0 ? options.nbytes : Infinity; |
|
583 let pipe = []; |
|
584 throw_on_negative("pump", UnixFile.pipe(pipe)); |
|
585 let pipe_read = pipe[0]; |
|
586 let pipe_write = pipe[1]; |
|
587 let source_fd = source.fd; |
|
588 let dest_fd = dest.fd; |
|
589 let total_read = 0; |
|
590 let total_written = 0; |
|
591 try { |
|
592 while (true) { |
|
593 let chunk_size = Math.min(nbytes, BUFSIZE); |
|
594 let bytes_read = throw_on_negative("pump", |
|
595 UnixFile.splice(source_fd, null, |
|
596 pipe_write, null, chunk_size, 0) |
|
597 ); |
|
598 if (!bytes_read) { |
|
599 break; |
|
600 } |
|
601 total_read += bytes_read; |
|
602 let bytes_written = throw_on_negative( |
|
603 "pump", |
|
604 UnixFile.splice(pipe_read, null, |
|
605 dest_fd, null, bytes_read, |
|
606 (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0 |
|
607 )); |
|
608 if (!bytes_written) { |
|
609 // This should never happen |
|
610 throw new Error("Internal error: pipe disconnected"); |
|
611 } |
|
612 total_written += bytes_written; |
|
613 nbytes -= bytes_read; |
|
614 if (!nbytes) { |
|
615 break; |
|
616 } |
|
617 } |
|
618 return total_written; |
|
619 } catch (x) { |
|
620 if (x.unixErrno == Const.EINVAL) { |
|
621 // We *might* be on a file system that does not support splice. |
|
622 // Try again with a fallback pump. |
|
623 if (total_read) { |
|
624 source.setPosition(-total_read, File.POS_CURRENT); |
|
625 } |
|
626 if (total_written) { |
|
627 dest.setPosition(-total_written, File.POS_CURRENT); |
|
628 } |
|
629 return pump_userland(source, dest, options); |
|
630 } |
|
631 throw x; |
|
632 } finally { |
|
633 pipe_read.dispose(); |
|
634 pipe_write.dispose(); |
|
635 } |
|
636 }; |
|
637 } else { |
|
638 // Fallback implementation of pump for other Unix platforms. |
|
639 pump = pump_userland; |
|
640 } |
|
641 |
|
642 // Implement |copy| using |pump|. |
|
643 // This implementation would require some work before being able to |
|
644 // copy directories |
|
645 File.copy = function copy(sourcePath, destPath, options = {}) { |
|
646 let source, dest; |
|
647 let result; |
|
648 try { |
|
649 source = File.open(sourcePath); |
|
650 // Need to open the output file with |append:false|, or else |splice| |
|
651 // won't work. |
|
652 if (options.noOverwrite) { |
|
653 dest = File.open(destPath, {create:true, append:false}); |
|
654 } else { |
|
655 dest = File.open(destPath, {trunc:true, append:false}); |
|
656 } |
|
657 if (options.unixUserland) { |
|
658 result = pump_userland(source, dest, options); |
|
659 } else { |
|
660 result = pump(source, dest, options); |
|
661 } |
|
662 } catch (x) { |
|
663 if (dest) { |
|
664 dest.close(); |
|
665 } |
|
666 if (source) { |
|
667 source.close(); |
|
668 } |
|
669 throw x; |
|
670 } |
|
671 }; |
|
672 } // End of definition of copy |
|
673 |
|
674 // Implement |move| using |rename| (wherever possible) or |copy| |
|
675 // (if files are on distinct devices). |
|
676 File.move = function move(sourcePath, destPath, options = {}) { |
|
677 // An implementation using |rename| whenever possible or |
|
678 // |File.pump| when required, for other Unices. |
|
679 // It can move directories on one file system, not |
|
680 // across file systems |
|
681 |
|
682 // If necessary, fail if the destination file exists |
|
683 if (options.noOverwrite) { |
|
684 let fd = UnixFile.open(destPath, Const.O_RDONLY, 0); |
|
685 if (fd != -1) { |
|
686 fd.dispose(); |
|
687 // The file exists and we have access |
|
688 throw new File.Error("move", Const.EEXIST, sourcePath); |
|
689 } else if (ctypes.errno == Const.EACCESS) { |
|
690 // The file exists and we don't have access |
|
691 throw new File.Error("move", Const.EEXIST, sourcePath); |
|
692 } |
|
693 } |
|
694 |
|
695 // If we can, rename the file |
|
696 let result = UnixFile.rename(sourcePath, destPath); |
|
697 if (result != -1) |
|
698 return; |
|
699 |
|
700 // If the error is not EXDEV ("not on the same device"), |
|
701 // or if the error is EXDEV and we have passed an option |
|
702 // that prevents us from crossing devices, throw the |
|
703 // error. |
|
704 if (ctypes.errno != Const.EXDEV || options.noCopy) { |
|
705 throw new File.Error("move", ctypes.errno, sourcePath); |
|
706 } |
|
707 |
|
708 // Otherwise, copy and remove. |
|
709 File.copy(sourcePath, destPath, options); |
|
710 // FIXME: Clean-up in case of copy error? |
|
711 File.remove(sourcePath); |
|
712 }; |
|
713 |
|
714 File.unixSymLink = function unixSymLink(sourcePath, destPath) { |
|
715 throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath), |
|
716 sourcePath); |
|
717 }; |
|
718 |
|
719 /** |
|
720 * Iterate on one directory. |
|
721 * |
|
722 * This iterator will not enter subdirectories. |
|
723 * |
|
724 * @param {string} path The directory upon which to iterate. |
|
725 * @param {*=} options Ignored in this implementation. |
|
726 * |
|
727 * @throws {File.Error} If |path| does not represent a directory or |
|
728 * if the directory cannot be iterated. |
|
729 * @constructor |
|
730 */ |
|
731 File.DirectoryIterator = function DirectoryIterator(path, options) { |
|
732 exports.OS.Shared.AbstractFile.AbstractIterator.call(this); |
|
733 this._path = path; |
|
734 this._dir = UnixFile.opendir(this._path); |
|
735 if (this._dir == null) { |
|
736 let error = ctypes.errno; |
|
737 if (error != Const.ENOENT) { |
|
738 throw new File.Error("DirectoryIterator", error, path); |
|
739 } |
|
740 this._exists = false; |
|
741 this._closed = true; |
|
742 } else { |
|
743 this._exists = true; |
|
744 this._closed = false; |
|
745 } |
|
746 }; |
|
747 File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype); |
|
748 |
|
749 /** |
|
750 * Return the next entry in the directory, if any such entry is |
|
751 * available. |
|
752 * |
|
753 * Skip special directories "." and "..". |
|
754 * |
|
755 * @return {File.Entry} The next entry in the directory. |
|
756 * @throws {StopIteration} Once all files in the directory have been |
|
757 * encountered. |
|
758 */ |
|
759 File.DirectoryIterator.prototype.next = function next() { |
|
760 if (!this._exists) { |
|
761 throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path); |
|
762 } |
|
763 if (this._closed) { |
|
764 throw StopIteration; |
|
765 } |
|
766 for (let entry = UnixFile.readdir(this._dir); |
|
767 entry != null && !entry.isNull(); |
|
768 entry = UnixFile.readdir(this._dir)) { |
|
769 let contents = entry.contents; |
|
770 let name = contents.d_name.readString(); |
|
771 if (name == "." || name == "..") { |
|
772 continue; |
|
773 } |
|
774 |
|
775 let isDir, isSymLink; |
|
776 if (!("d_type" in contents)) { |
|
777 // |dirent| doesn't have d_type on some platforms (e.g. Solaris). |
|
778 let path = Path.join(this._path, name); |
|
779 throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path); |
|
780 isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR; |
|
781 isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK; |
|
782 } else { |
|
783 isDir = contents.d_type == Const.DT_DIR; |
|
784 isSymLink = contents.d_type == Const.DT_LNK; |
|
785 } |
|
786 |
|
787 return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path); |
|
788 } |
|
789 this.close(); |
|
790 throw StopIteration; |
|
791 }; |
|
792 |
|
793 /** |
|
794 * Close the iterator and recover all resources. |
|
795 * You should call this once you have finished iterating on a directory. |
|
796 */ |
|
797 File.DirectoryIterator.prototype.close = function close() { |
|
798 if (this._closed) return; |
|
799 this._closed = true; |
|
800 UnixFile.closedir(this._dir); |
|
801 this._dir = null; |
|
802 }; |
|
803 |
|
804 /** |
|
805 * Determine whether the directory exists. |
|
806 * |
|
807 * @return {boolean} |
|
808 */ |
|
809 File.DirectoryIterator.prototype.exists = function exists() { |
|
810 return this._exists; |
|
811 }; |
|
812 |
|
813 /** |
|
814 * Return directory as |File| |
|
815 */ |
|
816 File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() { |
|
817 if (!this._dir) throw File.Error.closed("unixAsFile", this._path); |
|
818 return error_or_file(UnixFile.dirfd(this._dir), this._path); |
|
819 }; |
|
820 |
|
821 /** |
|
822 * An entry in a directory. |
|
823 */ |
|
824 File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) { |
|
825 // Copy the relevant part of |unix_entry| to ensure that |
|
826 // our data is not overwritten prematurely. |
|
827 this._parent = parent; |
|
828 let path = Path.join(this._parent, name); |
|
829 |
|
830 SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path); |
|
831 }; |
|
832 File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype); |
|
833 |
|
834 /** |
|
835 * Return a version of an instance of |
|
836 * File.DirectoryIterator.Entry that can be sent from a worker |
|
837 * thread to the main thread. Note that deserialization is |
|
838 * asymmetric and returns an object with a different |
|
839 * implementation. |
|
840 */ |
|
841 File.DirectoryIterator.Entry.toMsg = function toMsg(value) { |
|
842 if (!value instanceof File.DirectoryIterator.Entry) { |
|
843 throw new TypeError("parameter of " + |
|
844 "File.DirectoryIterator.Entry.toMsg must be a " + |
|
845 "File.DirectoryIterator.Entry"); |
|
846 } |
|
847 let serialized = {}; |
|
848 for (let key in File.DirectoryIterator.Entry.prototype) { |
|
849 serialized[key] = value[key]; |
|
850 } |
|
851 return serialized; |
|
852 }; |
|
853 |
|
854 let gStatData = new Type.stat.implementation(); |
|
855 let gStatDataPtr = gStatData.address(); |
|
856 let gTimevals = new Type.timevals.implementation(); |
|
857 let gTimevalsPtr = gTimevals.address(); |
|
858 let MODE_MASK = 4095 /*= 07777*/; |
|
859 File.Info = function Info(stat, path) { |
|
860 let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR; |
|
861 let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK; |
|
862 let size = Type.off_t.importFromC(stat.st_size); |
|
863 |
|
864 let lastAccessDate = new Date(stat.st_atime * 1000); |
|
865 let lastModificationDate = new Date(stat.st_mtime * 1000); |
|
866 let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000); |
|
867 |
|
868 let unixOwner = Type.uid_t.importFromC(stat.st_uid); |
|
869 let unixGroup = Type.gid_t.importFromC(stat.st_gid); |
|
870 let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK); |
|
871 |
|
872 SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size, |
|
873 lastAccessDate, lastModificationDate, unixLastStatusChangeDate, |
|
874 unixOwner, unixGroup, unixMode); |
|
875 |
|
876 // Some platforms (e.g. MacOS X, some BSDs) store a file creation date |
|
877 if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) { |
|
878 let date = new Date(stat.st_birthtime * 1000); |
|
879 |
|
880 /** |
|
881 * The date of creation of this file. |
|
882 * |
|
883 * Note that the date returned by this method is not always |
|
884 * reliable. Not all file systems are able to provide this |
|
885 * information. |
|
886 * |
|
887 * @type {Date} |
|
888 */ |
|
889 this.macBirthDate = date; |
|
890 } |
|
891 }; |
|
892 File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype); |
|
893 |
|
894 // Deprecated, use macBirthDate/winBirthDate instead |
|
895 Object.defineProperty(File.Info.prototype, "creationDate", { |
|
896 get: function creationDate() { |
|
897 // On the Macintosh, returns the birth date if available. |
|
898 // On other Unix, as the birth date is not available, |
|
899 // returns the epoch. |
|
900 return this.macBirthDate || new Date(0); |
|
901 } |
|
902 }); |
|
903 |
|
904 /** |
|
905 * Return a version of an instance of File.Info that can be sent |
|
906 * from a worker thread to the main thread. Note that deserialization |
|
907 * is asymmetric and returns an object with a different implementation. |
|
908 */ |
|
909 File.Info.toMsg = function toMsg(stat) { |
|
910 if (!stat instanceof File.Info) { |
|
911 throw new TypeError("parameter of File.Info.toMsg must be a File.Info"); |
|
912 } |
|
913 let serialized = {}; |
|
914 for (let key in File.Info.prototype) { |
|
915 serialized[key] = stat[key]; |
|
916 } |
|
917 return serialized; |
|
918 }; |
|
919 |
|
920 /** |
|
921 * Fetch the information on a file. |
|
922 * |
|
923 * @param {string} path The full name of the file to open. |
|
924 * @param {*=} options Additional options. In this implementation: |
|
925 * |
|
926 * - {bool} unixNoFollowingLinks If set and |true|, if |path| |
|
927 * represents a symbolic link, the call will return the information |
|
928 * of the link itself, rather than that of the target file. |
|
929 * |
|
930 * @return {File.Information} |
|
931 */ |
|
932 File.stat = function stat(path, options = {}) { |
|
933 if (options.unixNoFollowingLinks) { |
|
934 throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path); |
|
935 } else { |
|
936 throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path); |
|
937 } |
|
938 return new File.Info(gStatData, path); |
|
939 }; |
|
940 |
|
941 /** |
|
942 * Set the file's access permissions. Without any options, the |
|
943 * permissions are set to an approximation of what they would |
|
944 * have been if the file had been created in its current |
|
945 * directory in the "most typical" fashion for the operating |
|
946 * system. In the current implementation, this means we set |
|
947 * the POSIX file mode to (0666 & ~umask). |
|
948 * |
|
949 * This operation is likely to fail if applied to a file that was |
|
950 * not created by the currently running program (more precisely, |
|
951 * if it was created by a program running under a different OS-level |
|
952 * user account). It may also fail, or silently do nothing, if the |
|
953 * filesystem containing the file does not support access permissions. |
|
954 * |
|
955 * @param {string} path The name of the file to reset the permissions of. |
|
956 * @param {*=} options |
|
957 * - {number} unixMode If present, the POSIX file mode is set to |
|
958 * exactly this value, unless |unixHonorUmask| is |
|
959 * also present. |
|
960 * - {bool} unixHonorUmask If true, any |unixMode| value is modified by |
|
961 * the process umask, as open() would have done. |
|
962 */ |
|
963 File.setPermissions = function setPermissions(path, options = {}) { |
|
964 throw_on_negative("setPermissions", |
|
965 UnixFile.chmod(path, unixMode(options)), |
|
966 path); |
|
967 }; |
|
968 |
|
969 /** |
|
970 * Set the last access and modification date of the file. |
|
971 * The time stamp resolution is 1 second at best, but might be worse |
|
972 * depending on the platform. |
|
973 * |
|
974 * @param {string} path The full name of the file to set the dates for. |
|
975 * @param {Date,number=} accessDate The last access date. If numeric, |
|
976 * milliseconds since epoch. If omitted or null, then the current date |
|
977 * will be used. |
|
978 * @param {Date,number=} modificationDate The last modification date. If |
|
979 * numeric, milliseconds since epoch. If omitted or null, then the current |
|
980 * date will be used. |
|
981 * |
|
982 * @throws {TypeError} In case of invalid paramters. |
|
983 * @throws {OS.File.Error} In case of I/O error. |
|
984 */ |
|
985 File.setDates = function setDates(path, accessDate, modificationDate) { |
|
986 accessDate = normalizeDate("File.setDates", accessDate); |
|
987 modificationDate = normalizeDate("File.setDates", modificationDate); |
|
988 gTimevals[0].tv_sec = (accessDate / 1000) | 0; |
|
989 gTimevals[0].tv_usec = 0; |
|
990 gTimevals[1].tv_sec = (modificationDate / 1000) | 0; |
|
991 gTimevals[1].tv_usec = 0; |
|
992 throw_on_negative("setDates", |
|
993 UnixFile.utimes(path, gTimevalsPtr), |
|
994 path); |
|
995 }; |
|
996 |
|
997 File.read = exports.OS.Shared.AbstractFile.read; |
|
998 File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic; |
|
999 File.openUnique = exports.OS.Shared.AbstractFile.openUnique; |
|
1000 File.makeDir = exports.OS.Shared.AbstractFile.makeDir; |
|
1001 |
|
1002 /** |
|
1003 * Remove an existing directory and its contents. |
|
1004 * |
|
1005 * @param {string} path The name of the directory. |
|
1006 * @param {*=} options Additional options. |
|
1007 * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't |
|
1008 * exist. |true| by default. |
|
1009 * - {boolean} ignorePermissions If |true|, remove the file even when lacking write |
|
1010 * permission. |
|
1011 * |
|
1012 * @throws {OS.File.Error} In case of I/O error, in particular if |path| is |
|
1013 * not a directory. |
|
1014 * |
|
1015 * Note: This function will remove a symlink even if it points a directory. |
|
1016 */ |
|
1017 File.removeDir = function(path, options) { |
|
1018 let isSymLink; |
|
1019 try { |
|
1020 let info = File.stat(path, {unixNoFollowingLinks: true}); |
|
1021 isSymLink = info.isSymLink; |
|
1022 } catch (e) { |
|
1023 if ((!("ignoreAbsent" in options) || options.ignoreAbsent) && |
|
1024 ctypes.errno == Const.ENOENT) { |
|
1025 return; |
|
1026 } |
|
1027 throw e; |
|
1028 } |
|
1029 if (isSymLink) { |
|
1030 // A Unix symlink itself is not a directory even if it points |
|
1031 // a directory. |
|
1032 File.remove(path, options); |
|
1033 return; |
|
1034 } |
|
1035 exports.OS.Shared.AbstractFile.removeRecursive(path, options); |
|
1036 }; |
|
1037 |
|
1038 /** |
|
1039 * Get the current directory by getCurrentDirectory. |
|
1040 */ |
|
1041 File.getCurrentDirectory = function getCurrentDirectory() { |
|
1042 let path = UnixFile.get_current_dir_name?UnixFile.get_current_dir_name(): |
|
1043 UnixFile.getwd_auto(null); |
|
1044 throw_on_null("getCurrentDirectory", path); |
|
1045 return path.readString(); |
|
1046 }; |
|
1047 |
|
1048 /** |
|
1049 * Set the current directory by setCurrentDirectory. |
|
1050 */ |
|
1051 File.setCurrentDirectory = function setCurrentDirectory(path) { |
|
1052 throw_on_negative("setCurrentDirectory", |
|
1053 UnixFile.chdir(path), |
|
1054 path |
|
1055 ); |
|
1056 }; |
|
1057 |
|
1058 /** |
|
1059 * Get/set the current directory. |
|
1060 */ |
|
1061 Object.defineProperty(File, "curDir", { |
|
1062 set: function(path) { |
|
1063 this.setCurrentDirectory(path); |
|
1064 }, |
|
1065 get: function() { |
|
1066 return this.getCurrentDirectory(); |
|
1067 } |
|
1068 } |
|
1069 ); |
|
1070 |
|
1071 // Utility functions |
|
1072 |
|
1073 /** |
|
1074 * Turn the result of |open| into an Error or a File |
|
1075 * @param {number} maybe The result of the |open| operation that may |
|
1076 * represent either an error or a success. If -1, this function raises |
|
1077 * an error holding ctypes.errno, otherwise it returns the opened file. |
|
1078 * @param {string=} path The path of the file. |
|
1079 */ |
|
1080 function error_or_file(maybe, path) { |
|
1081 if (maybe == -1) { |
|
1082 throw new File.Error("open", ctypes.errno, path); |
|
1083 } |
|
1084 return new File(maybe, path); |
|
1085 } |
|
1086 |
|
1087 /** |
|
1088 * Utility function to sort errors represented as "-1" from successes. |
|
1089 * |
|
1090 * @param {string=} operation The name of the operation. If unspecified, |
|
1091 * the name of the caller function. |
|
1092 * @param {number} result The result of the operation that may |
|
1093 * represent either an error or a success. If -1, this function raises |
|
1094 * an error holding ctypes.errno, otherwise it returns |result|. |
|
1095 * @param {string=} path The path of the file. |
|
1096 */ |
|
1097 function throw_on_negative(operation, result, path) { |
|
1098 if (result < 0) { |
|
1099 throw new File.Error(operation, ctypes.errno, path); |
|
1100 } |
|
1101 return result; |
|
1102 } |
|
1103 |
|
1104 /** |
|
1105 * Utility function to sort errors represented as |null| from successes. |
|
1106 * |
|
1107 * @param {string=} operation The name of the operation. If unspecified, |
|
1108 * the name of the caller function. |
|
1109 * @param {pointer} result The result of the operation that may |
|
1110 * represent either an error or a success. If |null|, this function raises |
|
1111 * an error holding ctypes.errno, otherwise it returns |result|. |
|
1112 * @param {string=} path The path of the file. |
|
1113 */ |
|
1114 function throw_on_null(operation, result, path) { |
|
1115 if (result == null || (result.isNull && result.isNull())) { |
|
1116 throw new File.Error(operation, ctypes.errno, path); |
|
1117 } |
|
1118 return result; |
|
1119 } |
|
1120 |
|
1121 /** |
|
1122 * Normalize and verify a Date or numeric date value. |
|
1123 * |
|
1124 * @param {string} fn Function name of the calling function. |
|
1125 * @param {Date,number} date The date to normalize. If omitted or null, |
|
1126 * then the current date will be used. |
|
1127 * |
|
1128 * @throws {TypeError} Invalid date provided. |
|
1129 * |
|
1130 * @return {number} Sanitized, numeric date in milliseconds since epoch. |
|
1131 */ |
|
1132 function normalizeDate(fn, date) { |
|
1133 if (typeof date !== "number" && !date) { |
|
1134 // |date| was Omitted or null. |
|
1135 date = Date.now(); |
|
1136 } else if (typeof date.getTime === "function") { |
|
1137 // Input might be a date or date-like object. |
|
1138 date = date.getTime(); |
|
1139 } |
|
1140 |
|
1141 if (isNaN(date)) { |
|
1142 throw new TypeError("|date| parameter of " + fn + " must be a " + |
|
1143 "|Date| instance or number"); |
|
1144 } |
|
1145 return date; |
|
1146 }; |
|
1147 |
|
1148 /** |
|
1149 * Helper used by both versions of setPermissions. |
|
1150 */ |
|
1151 function unixMode(options) { |
|
1152 let mode = 438; /* 0666 */ |
|
1153 let unixHonorUmask = true; |
|
1154 if ("unixMode" in options) { |
|
1155 unixHonorUmask = false; |
|
1156 mode = options.unixMode; |
|
1157 } |
|
1158 if ("unixHonorUmask" in options) { |
|
1159 unixHonorUmask = options.unixHonorUmask; |
|
1160 } |
|
1161 if (unixHonorUmask) { |
|
1162 mode &= ~SharedAll.Constants.Sys.umask; |
|
1163 } |
|
1164 return mode; |
|
1165 } |
|
1166 |
|
1167 File.Unix = exports.OS.Unix.File; |
|
1168 File.Error = SysAll.Error; |
|
1169 exports.OS.File = File; |
|
1170 exports.OS.Shared.Type = Type; |
|
1171 |
|
1172 Object.defineProperty(File, "POS_START", { value: SysAll.POS_START }); |
|
1173 Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT }); |
|
1174 Object.defineProperty(File, "POS_END", { value: SysAll.POS_END }); |
|
1175 })(this); |
|
1176 } |