forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			607 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			607 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 | |
|  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * Code shared by OS.File front-ends.
 | |
|  *
 | |
|  * This code is meant to be included by another library. It is also meant to
 | |
|  * be executed only on a worker thread.
 | |
|  */
 | |
| 
 | |
| /* eslint-env node */
 | |
| /* global OS */
 | |
| 
 | |
| if (typeof Components != "undefined") {
 | |
|   throw new Error("osfile_shared_front.js cannot be used from the main thread");
 | |
| }
 | |
| (function(exports) {
 | |
|   var SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
 | |
|   var Path = require("resource://gre/modules/osfile/ospath.jsm");
 | |
|   var Lz4 = require("resource://gre/modules/lz4.js");
 | |
|   SharedAll.LOG.bind(SharedAll, "Shared front-end");
 | |
|   var clone = SharedAll.clone;
 | |
| 
 | |
|   /**
 | |
|    * Code shared by implementations of File.
 | |
|    *
 | |
|    * @param {*} fd An OS-specific file handle.
 | |
|    * @param {string} path File path of the file handle, used for error-reporting.
 | |
|    * @constructor
 | |
|    */
 | |
|   var AbstractFile = function AbstractFile(fd, path) {
 | |
|     this._fd = fd;
 | |
|     if (!path) {
 | |
|       throw new TypeError("path is expected");
 | |
|     }
 | |
|     this._path = path;
 | |
|   };
 | |
| 
 | |
|   AbstractFile.prototype = {
 | |
|     /**
 | |
|      * Return the file handle.
 | |
|      *
 | |
|      * @throw OS.File.Error if the file has been closed.
 | |
|      */
 | |
|     get fd() {
 | |
|       if (this._fd) {
 | |
|         return this._fd;
 | |
|       }
 | |
|       throw OS.File.Error.closed("accessing file", this._path);
 | |
|     },
 | |
|     /**
 | |
|      * Read bytes from this file to a new buffer.
 | |
|      *
 | |
|      * @param {number=} maybeBytes (deprecated, please use options.bytes)
 | |
|      * @param {JSON} options
 | |
|      * @return {Uint8Array} An array containing the bytes read.
 | |
|      */
 | |
|     read: function read(maybeBytes, options = {}) {
 | |
|       if (typeof maybeBytes === "object") {
 | |
|         // Caller has skipped `maybeBytes` and provided an options object.
 | |
|         options = clone(maybeBytes);
 | |
|         maybeBytes = null;
 | |
|       } else {
 | |
|         options = options || {};
 | |
|       }
 | |
|       let bytes = options.bytes || undefined;
 | |
|       if (bytes === undefined) {
 | |
|         bytes = maybeBytes == null ? this.stat().size : maybeBytes;
 | |
|       }
 | |
|       let buffer = new Uint8Array(bytes);
 | |
|       let pos = 0;
 | |
|       while (pos < bytes) {
 | |
|         let length = bytes - pos;
 | |
|         let view = new DataView(buffer.buffer, pos, length);
 | |
|         let chunkSize = this._read(view, length, options);
 | |
|         if (chunkSize == 0) {
 | |
|           break;
 | |
|         }
 | |
|         pos += chunkSize;
 | |
|       }
 | |
|       if (pos == bytes) {
 | |
|         return buffer;
 | |
|       }
 | |
|       return buffer.subarray(0, pos);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * Write bytes from a buffer to this file.
 | |
|      *
 | |
|      * Note that, by default, this function may perform several I/O
 | |
|      * operations to ensure that the buffer is fully written.
 | |
|      *
 | |
|      * @param {Typed array} buffer The buffer in which the the bytes are
 | |
|      * stored. The buffer must be large enough to accomodate |bytes| bytes.
 | |
|      * @param {*=} options Optionally, an object that may contain the
 | |
|      * following fields:
 | |
|      * - {number} bytes The number of |bytes| to write from the buffer. If
 | |
|      * unspecified, this is |buffer.byteLength|.
 | |
|      *
 | |
|      * @return {number} The number of bytes actually written.
 | |
|      */
 | |
|     write: function write(buffer, options = {}) {
 | |
|       let bytes = SharedAll.normalizeBufferArgs(
 | |
|         buffer,
 | |
|         "bytes" in options ? options.bytes : undefined
 | |
|       );
 | |
|       let pos = 0;
 | |
|       while (pos < bytes) {
 | |
|         let length = bytes - pos;
 | |
|         let view = new DataView(buffer.buffer, buffer.byteOffset + pos, length);
 | |
|         let chunkSize = this._write(view, length, options);
 | |
|         pos += chunkSize;
 | |
|       }
 | |
|       return pos;
 | |
|     },
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Creates and opens a file with a unique name. By default, generate a random HEX number and use it to create a unique new file name.
 | |
|    *
 | |
|    * @param {string} path The path to the file.
 | |
|    * @param {*=} options Additional options for file opening. This
 | |
|    * implementation interprets the following fields:
 | |
|    *
 | |
|    * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
 | |
|    *  If |false| use HEX numbers ie: filename-A65BC0.ext
 | |
|    * - {number} maxReadableNumber Used to limit the amount of tries after a failed
 | |
|    *  file creation. Default is 20.
 | |
|    *
 | |
|    * @return {Object} contains A file object{file} and the path{path}.
 | |
|    * @throws {OS.File.Error} If the file could not be opened.
 | |
|    */
 | |
|   AbstractFile.openUnique = function openUnique(path, options = {}) {
 | |
|     let mode = {
 | |
|       create: true,
 | |
|     };
 | |
| 
 | |
|     let dirName = Path.dirname(path);
 | |
|     let leafName = Path.basename(path);
 | |
|     let lastDotCharacter = leafName.lastIndexOf(".");
 | |
|     let fileName = leafName.substring(
 | |
|       0,
 | |
|       lastDotCharacter != -1 ? lastDotCharacter : leafName.length
 | |
|     );
 | |
|     let suffix =
 | |
|       lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "";
 | |
|     let uniquePath = "";
 | |
|     let maxAttempts = options.maxAttempts || 99;
 | |
|     let humanReadable = !!options.humanReadable;
 | |
|     const HEX_RADIX = 16;
 | |
|     // We produce HEX numbers between 0 and 2^24 - 1.
 | |
|     const MAX_HEX_NUMBER = 16777215;
 | |
| 
 | |
|     try {
 | |
|       return {
 | |
|         path,
 | |
|         file: OS.File.open(path, mode),
 | |
|       };
 | |
|     } catch (ex) {
 | |
|       if (ex instanceof OS.File.Error && ex.becauseExists) {
 | |
|         for (let i = 0; i < maxAttempts; ++i) {
 | |
|           try {
 | |
|             if (humanReadable) {
 | |
|               uniquePath = Path.join(
 | |
|                 dirName,
 | |
|                 fileName + "-" + (i + 1) + suffix
 | |
|               );
 | |
|             } else {
 | |
|               let hexNumber = Math.floor(
 | |
|                 Math.random() * MAX_HEX_NUMBER
 | |
|               ).toString(HEX_RADIX);
 | |
|               uniquePath = Path.join(
 | |
|                 dirName,
 | |
|                 fileName + "-" + hexNumber + suffix
 | |
|               );
 | |
|             }
 | |
|             return {
 | |
|               path: uniquePath,
 | |
|               file: OS.File.open(uniquePath, mode),
 | |
|             };
 | |
|           } catch (ex) {
 | |
|             if (ex instanceof OS.File.Error && ex.becauseExists) {
 | |
|               // keep trying ...
 | |
|             } else {
 | |
|               throw ex;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         throw OS.File.Error.exists("could not find an unused file name.", path);
 | |
|       }
 | |
|       throw ex;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Code shared by iterators.
 | |
|    */
 | |
|   AbstractFile.AbstractIterator = function AbstractIterator() {};
 | |
|   AbstractFile.AbstractIterator.prototype = {
 | |
|     /**
 | |
|      * Allow iterating with |for-of|
 | |
|      */
 | |
|     [Symbol.iterator]() {
 | |
|       return this;
 | |
|     },
 | |
|     /**
 | |
|      * Apply a function to all elements of the directory sequentially.
 | |
|      *
 | |
|      * @param {Function} cb This function will be applied to all entries
 | |
|      * of the directory. It receives as arguments
 | |
|      *  - the OS.File.Entry corresponding to the entry;
 | |
|      *  - the index of the entry in the enumeration;
 | |
|      *  - the iterator itself - calling |close| on the iterator stops
 | |
|      *   the loop.
 | |
|      */
 | |
|     forEach: function forEach(cb) {
 | |
|       let index = 0;
 | |
|       for (let entry of this) {
 | |
|         cb(entry, index++, this);
 | |
|       }
 | |
|     },
 | |
|     /**
 | |
|      * Return several entries at once.
 | |
|      *
 | |
|      * Entries are returned in the same order as a walk with |forEach| or
 | |
|      * |for(...)|.
 | |
|      *
 | |
|      * @param {number=} length If specified, the number of entries
 | |
|      * to return. If unspecified, return all remaining entries.
 | |
|      * @return {Array} An array containing the next |length| entries, or
 | |
|      * less if the iteration contains less than |length| entries left.
 | |
|      */
 | |
|     nextBatch: function nextBatch(length) {
 | |
|       let array = [];
 | |
|       let i = 0;
 | |
|       for (let entry of this) {
 | |
|         array.push(entry);
 | |
|         if (++i >= length) {
 | |
|           return array;
 | |
|         }
 | |
|       }
 | |
|       return array;
 | |
|     },
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Utility function shared by implementations of |OS.File.open|:
 | |
|    * extract read/write/trunc/create/existing flags from a |mode|
 | |
|    * object.
 | |
|    *
 | |
|    * @param {*=} mode An object that may contain fields |read|,
 | |
|    * |write|, |truncate|, |create|, |existing|. These fields
 | |
|    * are interpreted only if true-ish.
 | |
|    * @return {{read:bool, write:bool, trunc:bool, create:bool,
 | |
|    * existing:bool}} an object recapitulating the options set
 | |
|    * by |mode|.
 | |
|    * @throws {TypeError} If |mode| contains other fields, or
 | |
|    * if it contains both |create| and |truncate|, or |create|
 | |
|    * and |existing|.
 | |
|    */
 | |
|   AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
 | |
|     let result = {
 | |
|       read: false,
 | |
|       write: false,
 | |
|       trunc: false,
 | |
|       create: false,
 | |
|       existing: false,
 | |
|       append: true,
 | |
|     };
 | |
|     for (let key in mode) {
 | |
|       let val = !!mode[key]; // bool cast.
 | |
|       switch (key) {
 | |
|         case "read":
 | |
|           result.read = val;
 | |
|           break;
 | |
|         case "write":
 | |
|           result.write = val;
 | |
|           break;
 | |
|         case "truncate": // fallthrough
 | |
|         case "trunc":
 | |
|           result.trunc = val;
 | |
|           result.write |= val;
 | |
|           break;
 | |
|         case "create":
 | |
|           result.create = val;
 | |
|           result.write |= val;
 | |
|           break;
 | |
|         case "existing": // fallthrough
 | |
|         case "exist":
 | |
|           result.existing = val;
 | |
|           break;
 | |
|         case "append":
 | |
|           result.append = val;
 | |
|           break;
 | |
|         default:
 | |
|           throw new TypeError("Mode " + key + " not understood");
 | |
|       }
 | |
|     }
 | |
|     // Reject opposite modes
 | |
|     if (result.existing && result.create) {
 | |
|       throw new TypeError("Cannot specify both existing:true and create:true");
 | |
|     }
 | |
|     if (result.trunc && result.create) {
 | |
|       throw new TypeError("Cannot specify both trunc:true and create:true");
 | |
|     }
 | |
|     // Handle read/write
 | |
|     if (!result.write) {
 | |
|       result.read = true;
 | |
|     }
 | |
|     return result;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Return the contents of a file.
 | |
|    *
 | |
|    * @param {string} path The path to the file.
 | |
|    * @param {number=} bytes Optionally, an upper bound to the number of bytes
 | |
|    * to read. DEPRECATED - please use options.bytes instead.
 | |
|    * @param {object=} options Optionally, an object with some of the following
 | |
|    * fields:
 | |
|    * - {number} bytes An upper bound to the number of bytes to read.
 | |
|    * - {string} compression If "lz4" and if the file is compressed using the lz4
 | |
|    *   compression algorithm, decompress the file contents on the fly.
 | |
|    *
 | |
|    * @return {Uint8Array} A buffer holding the bytes
 | |
|    * and the number of bytes read from the file.
 | |
|    */
 | |
|   AbstractFile.read = function read(path, bytes, options = {}) {
 | |
|     if (bytes && typeof bytes == "object") {
 | |
|       options = bytes;
 | |
|       bytes = options.bytes || null;
 | |
|     }
 | |
|     if ("encoding" in options && typeof options.encoding != "string") {
 | |
|       throw new TypeError("Invalid type for option encoding");
 | |
|     }
 | |
|     if ("compression" in options && typeof options.compression != "string") {
 | |
|       throw new TypeError(
 | |
|         "Invalid type for option compression: " + options.compression
 | |
|       );
 | |
|     }
 | |
|     if ("bytes" in options && typeof options.bytes != "number") {
 | |
|       throw new TypeError("Invalid type for option bytes");
 | |
|     }
 | |
|     let file = exports.OS.File.open(path);
 | |
|     try {
 | |
|       let buffer = file.read(bytes, options);
 | |
|       if ("compression" in options) {
 | |
|         if (options.compression == "lz4") {
 | |
|           options = Object.create(options);
 | |
|           options.path = path;
 | |
|           buffer = Lz4.decompressFileContent(buffer, options);
 | |
|         } else {
 | |
|           throw OS.File.Error.invalidArgument("Compression");
 | |
|         }
 | |
|       }
 | |
|       if (!("encoding" in options)) {
 | |
|         return buffer;
 | |
|       }
 | |
|       let decoder;
 | |
|       try {
 | |
|         decoder = new TextDecoder(options.encoding);
 | |
|       } catch (ex) {
 | |
|         if (ex instanceof RangeError) {
 | |
|           throw OS.File.Error.invalidArgument("Decode");
 | |
|         } else {
 | |
|           throw ex;
 | |
|         }
 | |
|       }
 | |
|       return decoder.decode(buffer);
 | |
|     } finally {
 | |
|       file.close();
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Write a file, atomically.
 | |
|    *
 | |
|    * By opposition to a regular |write|, this operation ensures that,
 | |
|    * until the contents are fully written, the destination file is
 | |
|    * not modified.
 | |
|    *
 | |
|    * Limitation: In a few extreme cases (hardware failure during the
 | |
|    * write, user unplugging disk during the write, etc.), data may be
 | |
|    * corrupted. If your data is user-critical (e.g. preferences,
 | |
|    * application data, etc.), you may wish to consider adding options
 | |
|    * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
 | |
|    * detailed below. Note that no combination of options can be
 | |
|    * guaranteed to totally eliminate the risk of corruption.
 | |
|    *
 | |
|    * @param {string} path The path of the file to modify.
 | |
|    * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
 | |
|    * @param {*=} options Optionally, an object determining the behavior
 | |
|    * of this function. This object may contain the following fields:
 | |
|    * - {number} bytes The number of bytes to write. If unspecified,
 | |
|    * |buffer.byteLength|. Required if |buffer| is a C pointer.
 | |
|    * - {string} tmpPath If |null| or unspecified, write all data directly
 | |
|    * to |path|. If specified, write all data to a temporary file called
 | |
|    * |tmpPath| and, once this write is complete, rename the file to
 | |
|    * replace |path|. Performing this additional operation is a little
 | |
|    * slower but also a little safer.
 | |
|    * - {bool} noOverwrite - If set, this function will fail if a file already
 | |
|    * exists at |path|.
 | |
|    * - {bool} flush - If |false| or unspecified, return immediately once the
 | |
|    * write is complete. If |true|, before writing, force the operating system
 | |
|    * to write its internal disk buffers to the disk. This is considerably slower
 | |
|    * (not just for the application but for the whole system) but also safer:
 | |
|    * if the system shuts down improperly (typically due to a kernel freeze
 | |
|    * or a power failure) or if the device is disconnected before the buffer
 | |
|    * is flushed, the file has more chances of not being corrupted.
 | |
|    * - {string} compression - If empty or unspecified, do not compress the file.
 | |
|    * If "lz4", compress the contents of the file atomically using lz4. For the
 | |
|    * time being, the container format is specific to Mozilla and cannot be read
 | |
|    * by means other than OS.File.read(..., { compression: "lz4"})
 | |
|    * - {string} backupTo - If specified, backup the destination file as |backupTo|.
 | |
|    * Note that this function renames the destination file before overwriting it.
 | |
|    * If the process or the operating system freezes or crashes
 | |
|    * during the short window between these operations,
 | |
|    * the destination file will have been moved to its backup.
 | |
|    *
 | |
|    * @return {number} The number of bytes actually written.
 | |
|    */
 | |
|   AbstractFile.writeAtomic = function writeAtomic(path, buffer, options = {}) {
 | |
|     // Verify that path is defined and of the correct type
 | |
|     if (typeof path != "string" || path == "") {
 | |
|       throw new TypeError("File path should be a (non-empty) string");
 | |
|     }
 | |
|     let noOverwrite = options.noOverwrite;
 | |
|     if (noOverwrite && OS.File.exists(path)) {
 | |
|       throw OS.File.Error.exists("writeAtomic", path);
 | |
|     }
 | |
| 
 | |
|     if (typeof buffer == "string") {
 | |
|       // Normalize buffer to a C buffer by encoding it
 | |
|       buffer = new TextEncoder().encode(buffer);
 | |
|     }
 | |
| 
 | |
|     if ("compression" in options && options.compression == "lz4") {
 | |
|       buffer = Lz4.compressFileContent(buffer, options);
 | |
|       options = Object.create(options);
 | |
|       options.bytes = buffer.byteLength;
 | |
|     }
 | |
| 
 | |
|     let bytesWritten = 0;
 | |
| 
 | |
|     if (!options.tmpPath) {
 | |
|       if (options.backupTo) {
 | |
|         try {
 | |
|           OS.File.move(path, options.backupTo, { noCopy: true });
 | |
|         } catch (ex) {
 | |
|           if (ex.becauseNoSuchFile) {
 | |
|             // The file doesn't exist, nothing to backup.
 | |
|           } else {
 | |
|             throw ex;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       // Just write, without any renaming trick
 | |
|       let dest = OS.File.open(path, { write: true, truncate: true });
 | |
|       try {
 | |
|         bytesWritten = dest.write(buffer, options);
 | |
|         if (options.flush) {
 | |
|           dest.flush();
 | |
|         }
 | |
|       } finally {
 | |
|         dest.close();
 | |
|       }
 | |
|       return bytesWritten;
 | |
|     }
 | |
| 
 | |
|     let tmpFile = OS.File.open(options.tmpPath, {
 | |
|       write: true,
 | |
|       truncate: true,
 | |
|     });
 | |
|     try {
 | |
|       bytesWritten = tmpFile.write(buffer, options);
 | |
|       if (options.flush) {
 | |
|         tmpFile.flush();
 | |
|       }
 | |
|     } catch (x) {
 | |
|       OS.File.remove(options.tmpPath);
 | |
|       throw x;
 | |
|     } finally {
 | |
|       tmpFile.close();
 | |
|     }
 | |
| 
 | |
|     if (options.backupTo) {
 | |
|       try {
 | |
|         OS.File.move(path, options.backupTo, { noCopy: true });
 | |
|       } catch (ex) {
 | |
|         if (ex.becauseNoSuchFile) {
 | |
|           // The file doesn't exist, nothing to backup.
 | |
|         } else {
 | |
|           throw ex;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     OS.File.move(options.tmpPath, path, { noCopy: true });
 | |
|     return bytesWritten;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * This function is used by removeDir to avoid calling lstat for each
 | |
|    * files under the specified directory. External callers should not call
 | |
|    * this function directly.
 | |
|    */
 | |
|   AbstractFile.removeRecursive = function(path, options = {}) {
 | |
|     let iterator = new OS.File.DirectoryIterator(path);
 | |
|     if (!iterator.exists()) {
 | |
|       if (!("ignoreAbsent" in options) || options.ignoreAbsent) {
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     try {
 | |
|       for (let entry of iterator) {
 | |
|         if (entry.isDir) {
 | |
|           if (entry.isLink) {
 | |
|             // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
 | |
|             // directories are directories themselves. OS.File.remove()
 | |
|             // will not work for them.
 | |
|             OS.File.removeEmptyDir(entry.path, options);
 | |
|           } else {
 | |
|             // Normal directories.
 | |
|             AbstractFile.removeRecursive(entry.path, options);
 | |
|           }
 | |
|         } else {
 | |
|           // NTFS symlinks to files, Unix symlinks, or regular files.
 | |
|           OS.File.remove(entry.path, options);
 | |
|         }
 | |
|       }
 | |
|     } finally {
 | |
|       iterator.close();
 | |
|     }
 | |
| 
 | |
|     OS.File.removeEmptyDir(path);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * Create a directory and, optionally, its parent directories.
 | |
|    *
 | |
|    * @param {string} path The name of the directory.
 | |
|    * @param {*=} options Additional options.
 | |
|    *
 | |
|    * - {string} from If specified, the call to |makeDir| creates all the
 | |
|    * ancestors of |path| that are descendants of |from|. Note that |path|
 | |
|    * must be a descendant of |from|, and that |from| and its existing
 | |
|    * subdirectories present in |path|  must be user-writeable.
 | |
|    * Example:
 | |
|    *   makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
 | |
|    *  creates directories profileDir/foo, profileDir/foo/bar
 | |
|    * - {bool} ignoreExisting If |false|, throw an error if the directory
 | |
|    * already exists. |true| by default. Ignored if |from| is specified.
 | |
|    * - {number} unixMode Under Unix, if specified, a file creation mode,
 | |
|    * as per libc function |mkdir|. If unspecified, dirs are
 | |
|    * created with a default mode of 0700 (dir is private to
 | |
|    * the user, the user can read, write and execute). Ignored under Windows
 | |
|    * or if the file system does not support file creation modes.
 | |
|    * - {C pointer} winSecurity Under Windows, if specified, security
 | |
|    * attributes as per winapi function |CreateDirectory|. If
 | |
|    * unspecified, use the default security descriptor, inherited from
 | |
|    * the parent directory. Ignored under Unix or if the file system
 | |
|    * does not support security descriptors.
 | |
|    */
 | |
|   AbstractFile.makeDir = function(path, options = {}) {
 | |
|     let from = options.from;
 | |
|     if (!from) {
 | |
|       OS.File._makeDir(path, options);
 | |
|       return;
 | |
|     }
 | |
|     if (!path.startsWith(from)) {
 | |
|       // Apparently, `from` is not a parent of `path`. However, we may
 | |
|       // have false negatives due to non-normalized paths, e.g.
 | |
|       // "foo//bar" is a parent of "foo/bar/sna".
 | |
|       path = Path.normalize(path);
 | |
|       from = Path.normalize(from);
 | |
|       if (!path.startsWith(from)) {
 | |
|         throw new Error(
 | |
|           "Incorrect use of option |from|: " +
 | |
|             path +
 | |
|             " is not a descendant of " +
 | |
|             from
 | |
|         );
 | |
|       }
 | |
|     }
 | |
|     let innerOptions = Object.create(options, {
 | |
|       ignoreExisting: {
 | |
|         value: true,
 | |
|       },
 | |
|     });
 | |
|     // Compute the elements that appear in |path| but not in |from|.
 | |
|     let items = Path.split(path).components.slice(
 | |
|       Path.split(from).components.length
 | |
|     );
 | |
|     let current = from;
 | |
|     for (let item of items) {
 | |
|       current = Path.join(current, item);
 | |
|       OS.File._makeDir(current, innerOptions);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   if (!exports.OS.Shared) {
 | |
|     exports.OS.Shared = {};
 | |
|   }
 | |
|   exports.OS.Shared.AbstractFile = AbstractFile;
 | |
| })(this);
 | 
