toolkit/components/osfile/modules/osfile_shared_front.jsm

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

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

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

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 /**
michael@0 6 * Code shared by OS.File front-ends.
michael@0 7 *
michael@0 8 * This code is meant to be included by another library. It is also meant to
michael@0 9 * be executed only on a worker thread.
michael@0 10 */
michael@0 11
michael@0 12 if (typeof Components != "undefined") {
michael@0 13 throw new Error("osfile_shared_front.jsm cannot be used from the main thread");
michael@0 14 }
michael@0 15 (function(exports) {
michael@0 16
michael@0 17 let SharedAll =
michael@0 18 require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
michael@0 19 let Path = require("resource://gre/modules/osfile/ospath.jsm");
michael@0 20 let Lz4 =
michael@0 21 require("resource://gre/modules/workers/lz4.js");
michael@0 22 let LOG = SharedAll.LOG.bind(SharedAll, "Shared front-end");
michael@0 23 let clone = SharedAll.clone;
michael@0 24
michael@0 25 /**
michael@0 26 * Code shared by implementations of File.
michael@0 27 *
michael@0 28 * @param {*} fd An OS-specific file handle.
michael@0 29 * @param {string} path File path of the file handle, used for error-reporting.
michael@0 30 * @constructor
michael@0 31 */
michael@0 32 let AbstractFile = function AbstractFile(fd, path) {
michael@0 33 this._fd = fd;
michael@0 34 if (!path) {
michael@0 35 throw new TypeError("path is expected");
michael@0 36 }
michael@0 37 this._path = path;
michael@0 38 };
michael@0 39
michael@0 40 AbstractFile.prototype = {
michael@0 41 /**
michael@0 42 * Return the file handle.
michael@0 43 *
michael@0 44 * @throw OS.File.Error if the file has been closed.
michael@0 45 */
michael@0 46 get fd() {
michael@0 47 if (this._fd) {
michael@0 48 return this._fd;
michael@0 49 }
michael@0 50 throw OS.File.Error.closed("accessing file", this._path);
michael@0 51 },
michael@0 52 /**
michael@0 53 * Read bytes from this file to a new buffer.
michael@0 54 *
michael@0 55 * @param {number=} bytes If unspecified, read all the remaining bytes from
michael@0 56 * this file. If specified, read |bytes| bytes, or less if the file does notclone
michael@0 57 * contain that many bytes.
michael@0 58 * @param {JSON} options
michael@0 59 * @return {Uint8Array} An array containing the bytes read.
michael@0 60 */
michael@0 61 read: function read(bytes, options = {}) {
michael@0 62 options = clone(options);
michael@0 63 options.bytes = bytes == null ? this.stat().size : bytes;
michael@0 64 let buffer = new Uint8Array(options.bytes);
michael@0 65 let size = this.readTo(buffer, options);
michael@0 66 if (size == options.bytes) {
michael@0 67 return buffer;
michael@0 68 } else {
michael@0 69 return buffer.subarray(0, size);
michael@0 70 }
michael@0 71 },
michael@0 72
michael@0 73 /**
michael@0 74 * Read bytes from this file to an existing buffer.
michael@0 75 *
michael@0 76 * Note that, by default, this function may perform several I/O
michael@0 77 * operations to ensure that the buffer is as full as possible.
michael@0 78 *
michael@0 79 * @param {Typed Array | C pointer} buffer The buffer in which to
michael@0 80 * store the bytes. The buffer must be large enough to
michael@0 81 * accomodate |bytes| bytes.
michael@0 82 * @param {*=} options Optionally, an object that may contain the
michael@0 83 * following fields:
michael@0 84 * - {number} bytes The number of |bytes| to write from the buffer. If
michael@0 85 * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
michael@0 86 * if |buffer| is a C pointer.
michael@0 87 *
michael@0 88 * @return {number} The number of bytes actually read, which may be
michael@0 89 * less than |bytes| if the file did not contain that many bytes left.
michael@0 90 */
michael@0 91 readTo: function readTo(buffer, options = {}) {
michael@0 92 let {ptr, bytes} = SharedAll.normalizeToPointer(buffer, options.bytes);
michael@0 93 let pos = 0;
michael@0 94 while (pos < bytes) {
michael@0 95 let chunkSize = this._read(ptr, bytes - pos, options);
michael@0 96 if (chunkSize == 0) {
michael@0 97 break;
michael@0 98 }
michael@0 99 pos += chunkSize;
michael@0 100 ptr = SharedAll.offsetBy(ptr, chunkSize);
michael@0 101 }
michael@0 102
michael@0 103 return pos;
michael@0 104 },
michael@0 105
michael@0 106 /**
michael@0 107 * Write bytes from a buffer to this file.
michael@0 108 *
michael@0 109 * Note that, by default, this function may perform several I/O
michael@0 110 * operations to ensure that the buffer is fully written.
michael@0 111 *
michael@0 112 * @param {Typed array | C pointer} buffer The buffer in which the
michael@0 113 * the bytes are stored. The buffer must be large enough to
michael@0 114 * accomodate |bytes| bytes.
michael@0 115 * @param {*=} options Optionally, an object that may contain the
michael@0 116 * following fields:
michael@0 117 * - {number} bytes The number of |bytes| to write from the buffer. If
michael@0 118 * unspecified, this is |buffer.byteLength|. Note that |bytes| is required
michael@0 119 * if |buffer| is a C pointer.
michael@0 120 *
michael@0 121 * @return {number} The number of bytes actually written.
michael@0 122 */
michael@0 123 write: function write(buffer, options = {}) {
michael@0 124
michael@0 125 let {ptr, bytes} =
michael@0 126 SharedAll.normalizeToPointer(buffer, options.bytes || undefined);
michael@0 127
michael@0 128 let pos = 0;
michael@0 129 while (pos < bytes) {
michael@0 130 let chunkSize = this._write(ptr, bytes - pos, options);
michael@0 131 pos += chunkSize;
michael@0 132 ptr = SharedAll.offsetBy(ptr, chunkSize);
michael@0 133 }
michael@0 134 return pos;
michael@0 135 }
michael@0 136 };
michael@0 137
michael@0 138 /**
michael@0 139 * 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.
michael@0 140 *
michael@0 141 * @param {string} path The path to the file.
michael@0 142 * @param {*=} options Additional options for file opening. This
michael@0 143 * implementation interprets the following fields:
michael@0 144 *
michael@0 145 * - {number} humanReadable If |true|, create a new filename appending a decimal number. ie: filename-1.ext, filename-2.ext.
michael@0 146 * If |false| use HEX numbers ie: filename-A65BC0.ext
michael@0 147 * - {number} maxReadableNumber Used to limit the amount of tries after a failed
michael@0 148 * file creation. Default is 20.
michael@0 149 *
michael@0 150 * @return {Object} contains A file object{file} and the path{path}.
michael@0 151 * @throws {OS.File.Error} If the file could not be opened.
michael@0 152 */
michael@0 153 AbstractFile.openUnique = function openUnique(path, options = {}) {
michael@0 154 let mode = {
michael@0 155 create : true
michael@0 156 };
michael@0 157
michael@0 158 let dirName = Path.dirname(path);
michael@0 159 let leafName = Path.basename(path);
michael@0 160 let lastDotCharacter = leafName.lastIndexOf('.');
michael@0 161 let fileName = leafName.substring(0, lastDotCharacter != -1 ? lastDotCharacter : leafName.length);
michael@0 162 let suffix = (lastDotCharacter != -1 ? leafName.substring(lastDotCharacter) : "");
michael@0 163 let uniquePath = "";
michael@0 164 let maxAttempts = options.maxAttempts || 99;
michael@0 165 let humanReadable = !!options.humanReadable;
michael@0 166 const HEX_RADIX = 16;
michael@0 167 // We produce HEX numbers between 0 and 2^24 - 1.
michael@0 168 const MAX_HEX_NUMBER = 16777215;
michael@0 169
michael@0 170 try {
michael@0 171 return {
michael@0 172 path: path,
michael@0 173 file: OS.File.open(path, mode)
michael@0 174 };
michael@0 175 } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
michael@0 176 for (let i = 0; i < maxAttempts; ++i) {
michael@0 177 try {
michael@0 178 if (humanReadable) {
michael@0 179 uniquePath = Path.join(dirName, fileName + "-" + (i + 1) + suffix);
michael@0 180 } else {
michael@0 181 let hexNumber = Math.floor(Math.random() * MAX_HEX_NUMBER).toString(HEX_RADIX);
michael@0 182 uniquePath = Path.join(dirName, fileName + "-" + hexNumber + suffix);
michael@0 183 }
michael@0 184 return {
michael@0 185 path: uniquePath,
michael@0 186 file: OS.File.open(uniquePath, mode)
michael@0 187 };
michael@0 188 } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) {
michael@0 189 // keep trying ...
michael@0 190 }
michael@0 191 }
michael@0 192 throw OS.File.Error.exists("could not find an unused file name.", path);
michael@0 193 }
michael@0 194 };
michael@0 195
michael@0 196 /**
michael@0 197 * Code shared by iterators.
michael@0 198 */
michael@0 199 AbstractFile.AbstractIterator = function AbstractIterator() {
michael@0 200 };
michael@0 201 AbstractFile.AbstractIterator.prototype = {
michael@0 202 /**
michael@0 203 * Allow iterating with |for|
michael@0 204 */
michael@0 205 __iterator__: function __iterator__() {
michael@0 206 return this;
michael@0 207 },
michael@0 208 /**
michael@0 209 * Apply a function to all elements of the directory sequentially.
michael@0 210 *
michael@0 211 * @param {Function} cb This function will be applied to all entries
michael@0 212 * of the directory. It receives as arguments
michael@0 213 * - the OS.File.Entry corresponding to the entry;
michael@0 214 * - the index of the entry in the enumeration;
michael@0 215 * - the iterator itself - calling |close| on the iterator stops
michael@0 216 * the loop.
michael@0 217 */
michael@0 218 forEach: function forEach(cb) {
michael@0 219 let index = 0;
michael@0 220 for (let entry in this) {
michael@0 221 cb(entry, index++, this);
michael@0 222 }
michael@0 223 },
michael@0 224 /**
michael@0 225 * Return several entries at once.
michael@0 226 *
michael@0 227 * Entries are returned in the same order as a walk with |forEach| or
michael@0 228 * |for(...)|.
michael@0 229 *
michael@0 230 * @param {number=} length If specified, the number of entries
michael@0 231 * to return. If unspecified, return all remaining entries.
michael@0 232 * @return {Array} An array containing the next |length| entries, or
michael@0 233 * less if the iteration contains less than |length| entries left.
michael@0 234 */
michael@0 235 nextBatch: function nextBatch(length) {
michael@0 236 let array = [];
michael@0 237 let i = 0;
michael@0 238 for (let entry in this) {
michael@0 239 array.push(entry);
michael@0 240 if (++i >= length) {
michael@0 241 return array;
michael@0 242 }
michael@0 243 }
michael@0 244 return array;
michael@0 245 }
michael@0 246 };
michael@0 247
michael@0 248 /**
michael@0 249 * Utility function shared by implementations of |OS.File.open|:
michael@0 250 * extract read/write/trunc/create/existing flags from a |mode|
michael@0 251 * object.
michael@0 252 *
michael@0 253 * @param {*=} mode An object that may contain fields |read|,
michael@0 254 * |write|, |truncate|, |create|, |existing|. These fields
michael@0 255 * are interpreted only if true-ish.
michael@0 256 * @return {{read:bool, write:bool, trunc:bool, create:bool,
michael@0 257 * existing:bool}} an object recapitulating the options set
michael@0 258 * by |mode|.
michael@0 259 * @throws {TypeError} If |mode| contains other fields, or
michael@0 260 * if it contains both |create| and |truncate|, or |create|
michael@0 261 * and |existing|.
michael@0 262 */
michael@0 263 AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
michael@0 264 let result = {
michael@0 265 read: false,
michael@0 266 write: false,
michael@0 267 trunc: false,
michael@0 268 create: false,
michael@0 269 existing: false,
michael@0 270 append: true
michael@0 271 };
michael@0 272 for (let key in mode) {
michael@0 273 let val = !!mode[key]; // bool cast.
michael@0 274 switch (key) {
michael@0 275 case "read":
michael@0 276 result.read = val;
michael@0 277 break;
michael@0 278 case "write":
michael@0 279 result.write = val;
michael@0 280 break;
michael@0 281 case "truncate": // fallthrough
michael@0 282 case "trunc":
michael@0 283 result.trunc = val;
michael@0 284 result.write |= val;
michael@0 285 break;
michael@0 286 case "create":
michael@0 287 result.create = val;
michael@0 288 result.write |= val;
michael@0 289 break;
michael@0 290 case "existing": // fallthrough
michael@0 291 case "exist":
michael@0 292 result.existing = val;
michael@0 293 break;
michael@0 294 case "append":
michael@0 295 result.append = val;
michael@0 296 break;
michael@0 297 default:
michael@0 298 throw new TypeError("Mode " + key + " not understood");
michael@0 299 }
michael@0 300 }
michael@0 301 // Reject opposite modes
michael@0 302 if (result.existing && result.create) {
michael@0 303 throw new TypeError("Cannot specify both existing:true and create:true");
michael@0 304 }
michael@0 305 if (result.trunc && result.create) {
michael@0 306 throw new TypeError("Cannot specify both trunc:true and create:true");
michael@0 307 }
michael@0 308 // Handle read/write
michael@0 309 if (!result.write) {
michael@0 310 result.read = true;
michael@0 311 }
michael@0 312 return result;
michael@0 313 };
michael@0 314
michael@0 315 /**
michael@0 316 * Return the contents of a file.
michael@0 317 *
michael@0 318 * @param {string} path The path to the file.
michael@0 319 * @param {number=} bytes Optionally, an upper bound to the number of bytes
michael@0 320 * to read. DEPRECATED - please use options.bytes instead.
michael@0 321 * @param {object=} options Optionally, an object with some of the following
michael@0 322 * fields:
michael@0 323 * - {number} bytes An upper bound to the number of bytes to read.
michael@0 324 * - {string} compression If "lz4" and if the file is compressed using the lz4
michael@0 325 * compression algorithm, decompress the file contents on the fly.
michael@0 326 *
michael@0 327 * @return {Uint8Array} A buffer holding the bytes
michael@0 328 * and the number of bytes read from the file.
michael@0 329 */
michael@0 330 AbstractFile.read = function read(path, bytes, options = {}) {
michael@0 331 if (bytes && typeof bytes == "object") {
michael@0 332 options = bytes;
michael@0 333 bytes = options.bytes || null;
michael@0 334 }
michael@0 335 if ("encoding" in options && typeof options.encoding != "string") {
michael@0 336 throw new TypeError("Invalid type for option encoding");
michael@0 337 }
michael@0 338 if ("compression" in options && typeof options.compression != "string") {
michael@0 339 throw new TypeError("Invalid type for option compression: " + options.compression);
michael@0 340 }
michael@0 341 if ("bytes" in options && typeof options.bytes != "number") {
michael@0 342 throw new TypeError("Invalid type for option bytes");
michael@0 343 }
michael@0 344 let file = exports.OS.File.open(path);
michael@0 345 try {
michael@0 346 let buffer = file.read(bytes, options);
michael@0 347 if ("compression" in options) {
michael@0 348 if (options.compression == "lz4") {
michael@0 349 buffer = Lz4.decompressFileContent(buffer, options);
michael@0 350 } else {
michael@0 351 throw OS.File.Error.invalidArgument("Compression");
michael@0 352 }
michael@0 353 }
michael@0 354 if (!("encoding" in options)) {
michael@0 355 return buffer;
michael@0 356 }
michael@0 357 let decoder;
michael@0 358 try {
michael@0 359 decoder = new TextDecoder(options.encoding);
michael@0 360 } catch (ex if ex instanceof TypeError) {
michael@0 361 throw OS.File.Error.invalidArgument("Decode");
michael@0 362 }
michael@0 363 return decoder.decode(buffer);
michael@0 364 } finally {
michael@0 365 file.close();
michael@0 366 }
michael@0 367 };
michael@0 368
michael@0 369 /**
michael@0 370 * Write a file, atomically.
michael@0 371 *
michael@0 372 * By opposition to a regular |write|, this operation ensures that,
michael@0 373 * until the contents are fully written, the destination file is
michael@0 374 * not modified.
michael@0 375 *
michael@0 376 * Limitation: In a few extreme cases (hardware failure during the
michael@0 377 * write, user unplugging disk during the write, etc.), data may be
michael@0 378 * corrupted. If your data is user-critical (e.g. preferences,
michael@0 379 * application data, etc.), you may wish to consider adding options
michael@0 380 * |tmpPath| and/or |flush| to reduce the likelihood of corruption, as
michael@0 381 * detailed below. Note that no combination of options can be
michael@0 382 * guaranteed to totally eliminate the risk of corruption.
michael@0 383 *
michael@0 384 * @param {string} path The path of the file to modify.
michael@0 385 * @param {Typed Array | C pointer} buffer A buffer containing the bytes to write.
michael@0 386 * @param {*=} options Optionally, an object determining the behavior
michael@0 387 * of this function. This object may contain the following fields:
michael@0 388 * - {number} bytes The number of bytes to write. If unspecified,
michael@0 389 * |buffer.byteLength|. Required if |buffer| is a C pointer.
michael@0 390 * - {string} tmpPath If |null| or unspecified, write all data directly
michael@0 391 * to |path|. If specified, write all data to a temporary file called
michael@0 392 * |tmpPath| and, once this write is complete, rename the file to
michael@0 393 * replace |path|. Performing this additional operation is a little
michael@0 394 * slower but also a little safer.
michael@0 395 * - {bool} noOverwrite - If set, this function will fail if a file already
michael@0 396 * exists at |path|.
michael@0 397 * - {bool} flush - If |false| or unspecified, return immediately once the
michael@0 398 * write is complete. If |true|, before writing, force the operating system
michael@0 399 * to write its internal disk buffers to the disk. This is considerably slower
michael@0 400 * (not just for the application but for the whole system) but also safer:
michael@0 401 * if the system shuts down improperly (typically due to a kernel freeze
michael@0 402 * or a power failure) or if the device is disconnected before the buffer
michael@0 403 * is flushed, the file has more chances of not being corrupted.
michael@0 404 * - {string} compression - If empty or unspecified, do not compress the file.
michael@0 405 * If "lz4", compress the contents of the file atomically using lz4. For the
michael@0 406 * time being, the container format is specific to Mozilla and cannot be read
michael@0 407 * by means other than OS.File.read(..., { compression: "lz4"})
michael@0 408 * - {string} backupTo - If specified, backup the destination file as |backupTo|.
michael@0 409 * Note that this function renames the destination file before overwriting it.
michael@0 410 * If the process or the operating system freezes or crashes
michael@0 411 * during the short window between these operations,
michael@0 412 * the destination file will have been moved to its backup.
michael@0 413 *
michael@0 414 * @return {number} The number of bytes actually written.
michael@0 415 */
michael@0 416 AbstractFile.writeAtomic =
michael@0 417 function writeAtomic(path, buffer, options = {}) {
michael@0 418
michael@0 419 // Verify that path is defined and of the correct type
michael@0 420 if (typeof path != "string" || path == "") {
michael@0 421 throw new TypeError("File path should be a (non-empty) string");
michael@0 422 }
michael@0 423 let noOverwrite = options.noOverwrite;
michael@0 424 if (noOverwrite && OS.File.exists(path)) {
michael@0 425 throw OS.File.Error.exists("writeAtomic", path);
michael@0 426 }
michael@0 427
michael@0 428 if (typeof buffer == "string") {
michael@0 429 // Normalize buffer to a C buffer by encoding it
michael@0 430 let encoding = options.encoding || "utf-8";
michael@0 431 buffer = new TextEncoder(encoding).encode(buffer);
michael@0 432 }
michael@0 433
michael@0 434 if ("compression" in options && options.compression == "lz4") {
michael@0 435 buffer = Lz4.compressFileContent(buffer, options);
michael@0 436 options = Object.create(options);
michael@0 437 options.bytes = buffer.byteLength;
michael@0 438 }
michael@0 439
michael@0 440 let bytesWritten = 0;
michael@0 441
michael@0 442 if (!options.tmpPath) {
michael@0 443 if (options.backupTo) {
michael@0 444 try {
michael@0 445 OS.File.move(path, options.backupTo, {noCopy: true});
michael@0 446 } catch (ex if ex.becauseNoSuchFile) {
michael@0 447 // The file doesn't exist, nothing to backup.
michael@0 448 }
michael@0 449 }
michael@0 450 // Just write, without any renaming trick
michael@0 451 let dest = OS.File.open(path, {write: true, truncate: true});
michael@0 452 try {
michael@0 453 bytesWritten = dest.write(buffer, options);
michael@0 454 if (options.flush) {
michael@0 455 dest.flush();
michael@0 456 }
michael@0 457 } finally {
michael@0 458 dest.close();
michael@0 459 }
michael@0 460 return bytesWritten;
michael@0 461 }
michael@0 462
michael@0 463 let tmpFile = OS.File.open(options.tmpPath, {write: true, truncate: true});
michael@0 464 try {
michael@0 465 bytesWritten = tmpFile.write(buffer, options);
michael@0 466 if (options.flush) {
michael@0 467 tmpFile.flush();
michael@0 468 }
michael@0 469 } catch (x) {
michael@0 470 OS.File.remove(options.tmpPath);
michael@0 471 throw x;
michael@0 472 } finally {
michael@0 473 tmpFile.close();
michael@0 474 }
michael@0 475
michael@0 476 if (options.backupTo) {
michael@0 477 try {
michael@0 478 OS.File.move(path, options.backupTo, {noCopy: true});
michael@0 479 } catch (ex if ex.becauseNoSuchFile) {
michael@0 480 // The file doesn't exist, nothing to backup.
michael@0 481 }
michael@0 482 }
michael@0 483
michael@0 484 OS.File.move(options.tmpPath, path, {noCopy: true});
michael@0 485 return bytesWritten;
michael@0 486 };
michael@0 487
michael@0 488 /**
michael@0 489 * This function is used by removeDir to avoid calling lstat for each
michael@0 490 * files under the specified directory. External callers should not call
michael@0 491 * this function directly.
michael@0 492 */
michael@0 493 AbstractFile.removeRecursive = function(path, options = {}) {
michael@0 494 let iterator = new OS.File.DirectoryIterator(path);
michael@0 495 if (!iterator.exists()) {
michael@0 496 if (!("ignoreAbsent" in options) || options.ignoreAbsent) {
michael@0 497 return;
michael@0 498 }
michael@0 499 }
michael@0 500
michael@0 501 try {
michael@0 502 for (let entry in iterator) {
michael@0 503 if (entry.isDir) {
michael@0 504 if (entry.isLink) {
michael@0 505 // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
michael@0 506 // directories are directories themselves. OS.File.remove()
michael@0 507 // will not work for them.
michael@0 508 OS.File.removeEmptyDir(entry.path, options);
michael@0 509 } else {
michael@0 510 // Normal directories.
michael@0 511 AbstractFile.removeRecursive(entry.path, options);
michael@0 512 }
michael@0 513 } else {
michael@0 514 // NTFS symlinks to files, Unix symlinks, or regular files.
michael@0 515 OS.File.remove(entry.path, options);
michael@0 516 }
michael@0 517 }
michael@0 518 } finally {
michael@0 519 iterator.close();
michael@0 520 }
michael@0 521
michael@0 522 OS.File.removeEmptyDir(path);
michael@0 523 };
michael@0 524
michael@0 525 /**
michael@0 526 * Create a directory and, optionally, its parent directories.
michael@0 527 *
michael@0 528 * @param {string} path The name of the directory.
michael@0 529 * @param {*=} options Additional options.
michael@0 530 *
michael@0 531 * - {string} from If specified, the call to |makeDir| creates all the
michael@0 532 * ancestors of |path| that are descendants of |from|. Note that |path|
michael@0 533 * must be a descendant of |from|, and that |from| and its existing
michael@0 534 * subdirectories present in |path| must be user-writeable.
michael@0 535 * Example:
michael@0 536 * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
michael@0 537 * creates directories profileDir/foo, profileDir/foo/bar
michael@0 538 * - {bool} ignoreExisting If |false|, throw an error if the directory
michael@0 539 * already exists. |true| by default. Ignored if |from| is specified.
michael@0 540 * - {number} unixMode Under Unix, if specified, a file creation mode,
michael@0 541 * as per libc function |mkdir|. If unspecified, dirs are
michael@0 542 * created with a default mode of 0700 (dir is private to
michael@0 543 * the user, the user can read, write and execute). Ignored under Windows
michael@0 544 * or if the file system does not support file creation modes.
michael@0 545 * - {C pointer} winSecurity Under Windows, if specified, security
michael@0 546 * attributes as per winapi function |CreateDirectory|. If
michael@0 547 * unspecified, use the default security descriptor, inherited from
michael@0 548 * the parent directory. Ignored under Unix or if the file system
michael@0 549 * does not support security descriptors.
michael@0 550 */
michael@0 551 AbstractFile.makeDir = function(path, options = {}) {
michael@0 552 if (!options.from) {
michael@0 553 return OS.File._makeDir(path, options);
michael@0 554 }
michael@0 555 if (!path.startsWith(options.from)) {
michael@0 556 throw new Error("Incorrect use of option |from|: " + path + " is not a descendant of " + options.from);
michael@0 557 }
michael@0 558 let innerOptions = Object.create(options, {
michael@0 559 ignoreExisting: {
michael@0 560 value: true
michael@0 561 }
michael@0 562 });
michael@0 563 // Compute the elements that appear in |path| but not in |options.from|.
michael@0 564 let items = Path.split(path).components.slice(Path.split(options.from).components.length);
michael@0 565 let current = options.from;
michael@0 566 for (let item of items) {
michael@0 567 current = Path.join(current, item);
michael@0 568 OS.File._makeDir(current, innerOptions);
michael@0 569 }
michael@0 570 };
michael@0 571
michael@0 572 if (!exports.OS.Shared) {
michael@0 573 exports.OS.Shared = {};
michael@0 574 }
michael@0 575 exports.OS.Shared.AbstractFile = AbstractFile;
michael@0 576 })(this);

mercurial