summaryrefslogtreecommitdiffstats
path: root/toolkit/components/osfile/modules
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/osfile/modules')
-rw-r--r--toolkit/components/osfile/modules/moz.build22
-rw-r--r--toolkit/components/osfile/modules/osfile_async_front.jsm1533
-rw-r--r--toolkit/components/osfile/modules/osfile_async_worker.js407
-rw-r--r--toolkit/components/osfile/modules/osfile_native.jsm70
-rw-r--r--toolkit/components/osfile/modules/osfile_shared_allthreads.jsm1315
-rw-r--r--toolkit/components/osfile/modules/osfile_shared_front.jsm567
-rw-r--r--toolkit/components/osfile/modules/osfile_unix_allthreads.jsm375
-rw-r--r--toolkit/components/osfile/modules/osfile_unix_back.jsm735
-rw-r--r--toolkit/components/osfile/modules/osfile_unix_front.jsm1193
-rw-r--r--toolkit/components/osfile/modules/osfile_win_allthreads.jsm425
-rw-r--r--toolkit/components/osfile/modules/osfile_win_back.jsm437
-rw-r--r--toolkit/components/osfile/modules/osfile_win_front.jsm1266
-rw-r--r--toolkit/components/osfile/modules/ospath.jsm45
-rw-r--r--toolkit/components/osfile/modules/ospath_unix.jsm202
-rw-r--r--toolkit/components/osfile/modules/ospath_win.jsm373
15 files changed, 8965 insertions, 0 deletions
diff --git a/toolkit/components/osfile/modules/moz.build b/toolkit/components/osfile/modules/moz.build
new file mode 100644
index 000000000..7a0580ca3
--- /dev/null
+++ b/toolkit/components/osfile/modules/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_JS_MODULES.osfile += [
+ 'osfile_async_front.jsm',
+ 'osfile_async_worker.js',
+ 'osfile_native.jsm',
+ 'osfile_shared_allthreads.jsm',
+ 'osfile_shared_front.jsm',
+ 'osfile_unix_allthreads.jsm',
+ 'osfile_unix_back.jsm',
+ 'osfile_unix_front.jsm',
+ 'osfile_win_allthreads.jsm',
+ 'osfile_win_back.jsm',
+ 'osfile_win_front.jsm',
+ 'ospath.jsm',
+ 'ospath_unix.jsm',
+ 'ospath_win.jsm',
+]
diff --git a/toolkit/components/osfile/modules/osfile_async_front.jsm b/toolkit/components/osfile/modules/osfile_async_front.jsm
new file mode 100644
index 000000000..181471cd8
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -0,0 +1,1533 @@
+/* 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/. */
+
+/**
+ * Asynchronous front-end for OS.File.
+ *
+ * This front-end is meant to be imported from the main thread. In turn,
+ * it spawns one worker (perhaps more in the future) and delegates all
+ * disk I/O to this worker.
+ *
+ * Documentation note: most of the functions and methods in this module
+ * return promises. For clarity, we denote as follows a promise that may resolve
+ * with type |A| and some value |value| or reject with type |B| and some
+ * reason |reason|
+ * @resolves {A} value
+ * @rejects {B} reason
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["OS"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+var SharedAll = {};
+Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+
+// Boilerplate, to simplify the transition to require()
+var LOG = SharedAll.LOG.bind(SharedAll, "Controller");
+var isTypedArray = SharedAll.isTypedArray;
+
+// The constructor for file errors.
+var SysAll = {};
+if (SharedAll.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+ Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+ throw new Error("I am neither under Windows nor under a Posix system");
+}
+var OSError = SysAll.Error;
+var Type = SysAll.Type;
+
+var Path = {};
+Cu.import("resource://gre/modules/osfile/ospath.jsm", Path);
+
+// The library of promises.
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+
+// The implementation of communications
+Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
+Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
+var Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {});
+
+
+// It's possible for osfile.jsm to get imported before the profile is
+// set up. In this case, some path constants aren't yet available.
+// Here, we make them lazy loaders.
+
+function lazyPathGetter(constProp, dirKey) {
+ return function () {
+ let path;
+ try {
+ path = Services.dirsvc.get(dirKey, Ci.nsIFile).path;
+ delete SharedAll.Constants.Path[constProp];
+ SharedAll.Constants.Path[constProp] = path;
+ } catch (ex) {
+ // Ignore errors if the value still isn't available. Hopefully
+ // the next access will return it.
+ }
+
+ return path;
+ };
+}
+
+for (let [constProp, dirKey] of [
+ ["localProfileDir", "ProfLD"],
+ ["profileDir", "ProfD"],
+ ["userApplicationDataDir", "UAppData"],
+ ["winAppDataDir", "AppData"],
+ ["winStartMenuProgsDir", "Progs"],
+ ]) {
+
+ if (constProp in SharedAll.Constants.Path) {
+ continue;
+ }
+
+ LOG("Installing lazy getter for OS.Constants.Path." + constProp +
+ " because it isn't defined and profile may not be loaded.");
+ Object.defineProperty(SharedAll.Constants.Path, constProp, {
+ get: lazyPathGetter(constProp, dirKey),
+ });
+}
+
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ */
+var clone = SharedAll.clone;
+
+/**
+ * Extract a shortened version of an object, fit for logging.
+ *
+ * This function returns a copy of the original object in which all
+ * long strings, Arrays, TypedArrays, ArrayBuffers are removed and
+ * replaced with placeholders. Use this function to sanitize objects
+ * if you wish to log them or to keep them in memory.
+ *
+ * @param {*} obj The obj to shorten.
+ * @return {*} array A shorter object, fit for logging.
+ */
+function summarizeObject(obj) {
+ if (!obj) {
+ return null;
+ }
+ if (typeof obj == "string") {
+ if (obj.length > 1024) {
+ return {"Long string": obj.length};
+ }
+ return obj;
+ }
+ if (typeof obj == "object") {
+ if (Array.isArray(obj)) {
+ if (obj.length > 32) {
+ return {"Long array": obj.length};
+ }
+ return obj.map(summarizeObject);
+ }
+ if ("byteLength" in obj) {
+ // Assume TypedArray or ArrayBuffer
+ return {"Binary Data": obj.byteLength};
+ }
+ let result = {};
+ for (let k of Object.keys(obj)) {
+ result[k] = summarizeObject(obj[k]);
+ }
+ return result;
+ }
+ return obj;
+}
+
+// In order to expose Scheduler to the unfiltered Cu.import return value variant
+// on B2G we need to save it to `this`. This does not make it public;
+// EXPORTED_SYMBOLS still controls that in all cases.
+var Scheduler = this.Scheduler = {
+
+ /**
+ * |true| once we have sent at least one message to the worker.
+ * This field is unaffected by resetting the worker.
+ */
+ launched: false,
+
+ /**
+ * |true| once shutdown has begun i.e. we should reject any
+ * message, including resets.
+ */
+ shutdown: false,
+
+ /**
+ * A promise resolved once all currently pending operations are complete.
+ *
+ * This promise is never rejected and the result is always undefined.
+ */
+ queue: Promise.resolve(),
+
+ /**
+ * A promise resolved once all currently pending `kill` operations
+ * are complete.
+ *
+ * This promise is never rejected and the result is always undefined.
+ */
+ _killQueue: Promise.resolve(),
+
+ /**
+ * Miscellaneous debugging information
+ */
+ Debugging: {
+ /**
+ * The latest message sent and still waiting for a reply.
+ */
+ latestSent: undefined,
+
+ /**
+ * The latest reply received, or null if we are waiting for a reply.
+ */
+ latestReceived: undefined,
+
+ /**
+ * Number of messages sent to the worker. This includes the
+ * initial SET_DEBUG, if applicable.
+ */
+ messagesSent: 0,
+
+ /**
+ * Total number of messages ever queued, including the messages
+ * sent.
+ */
+ messagesQueued: 0,
+
+ /**
+ * Number of messages received from the worker.
+ */
+ messagesReceived: 0,
+ },
+
+ /**
+ * A timer used to automatically shut down the worker after some time.
+ */
+ resetTimer: null,
+
+ /**
+ * The worker to which to send requests.
+ *
+ * If the worker has never been created or has been reset, this is a
+ * fresh worker, initialized with osfile_async_worker.js.
+ *
+ * @type {PromiseWorker}
+ */
+ get worker() {
+ if (!this._worker) {
+ // Either the worker has never been created or it has been
+ // reset. In either case, it is time to instantiate the worker.
+ this._worker = new BasePromiseWorker("resource://gre/modules/osfile/osfile_async_worker.js");
+ this._worker.log = LOG;
+ this._worker.ExceptionHandlers["OS.File.Error"] = OSError.fromMsg;
+ }
+ return this._worker;
+ },
+
+ _worker: null,
+
+ /**
+ * Prepare to kill the OS.File worker after a few seconds.
+ */
+ restartTimer: function(arg) {
+ let delay;
+ try {
+ delay = Services.prefs.getIntPref("osfile.reset_worker_delay");
+ } catch(e) {
+ // Don't auto-shutdown if we don't have a delay preference set.
+ return;
+ }
+
+ if (this.resetTimer) {
+ clearTimeout(this.resetTimer);
+ }
+ this.resetTimer = setTimeout(
+ () => Scheduler.kill({reset: true, shutdown: false}),
+ delay
+ );
+ },
+
+ /**
+ * Shutdown OS.File.
+ *
+ * @param {*} options
+ * - {boolean} shutdown If |true|, reject any further request. Otherwise,
+ * further requests will resurrect the worker.
+ * - {boolean} reset If |true|, instruct the worker to shutdown if this
+ * would not cause leaks. Otherwise, assume that the worker will be shutdown
+ * through some other mean.
+ */
+ kill: function({shutdown, reset}) {
+ // Grab the kill queue to make sure that we
+ // cannot be interrupted by another call to `kill`.
+ let killQueue = this._killQueue;
+
+ // Deactivate the queue, to ensure that no message is sent
+ // to an obsolete worker (we reactivate it in the `finally`).
+ // This needs to be done right now so that we maintain relative
+ // ordering with calls to post(), etc.
+ let deferred = Promise.defer();
+ let savedQueue = this.queue;
+ this.queue = deferred.promise;
+
+ return this._killQueue = Task.spawn(function*() {
+
+ yield killQueue;
+ // From this point, and until the end of the Task, we are the
+ // only call to `kill`, regardless of any `yield`.
+
+ yield savedQueue;
+
+ try {
+ // Enter critical section: no yield in this block
+ // (we want to make sure that we remain the only
+ // request in the queue).
+
+ if (!this.launched || this.shutdown || !this._worker) {
+ // Nothing to kill
+ this.shutdown = this.shutdown || shutdown;
+ this._worker = null;
+ return null;
+ }
+
+ // Exit critical section
+
+ let message = ["Meta_shutdown", [reset]];
+
+ Scheduler.latestReceived = [];
+ Scheduler.latestSent = [Date.now(),
+ Task.Debugging.generateReadableStack(new Error().stack),
+ ...message];
+
+ // Wait for result
+ let resources;
+ try {
+ resources = yield this._worker.post(...message);
+
+ Scheduler.latestReceived = [Date.now(), message];
+ } catch (ex) {
+ LOG("Could not dispatch Meta_reset", ex);
+ // It's most likely a programmer error, but we'll assume that
+ // the worker has been shutdown, as it's less risky than the
+ // opposite stance.
+ resources = {openedFiles: [], openedDirectoryIterators: [], killed: true};
+
+ Scheduler.latestReceived = [Date.now(), message, ex];
+ }
+
+ let {openedFiles, openedDirectoryIterators, killed} = resources;
+ if (!reset
+ && (openedFiles && openedFiles.length
+ || ( openedDirectoryIterators && openedDirectoryIterators.length))) {
+ // The worker still holds resources. Report them.
+
+ let msg = "";
+ if (openedFiles.length > 0) {
+ msg += "The following files are still open:\n" +
+ openedFiles.join("\n");
+ }
+ if (openedDirectoryIterators.length > 0) {
+ msg += "The following directory iterators are still open:\n" +
+ openedDirectoryIterators.join("\n");
+ }
+
+ LOG("WARNING: File descriptors leaks detected.\n" + msg);
+ }
+
+ // Make sure that we do not leave an invalid |worker| around.
+ if (killed || shutdown) {
+ this._worker = null;
+ }
+
+ this.shutdown = shutdown;
+
+ return resources;
+
+ } finally {
+ // Resume accepting messages. If we have set |shutdown| to |true|,
+ // any pending/future request will be rejected. Otherwise, any
+ // pending/future request will spawn a new worker if necessary.
+ deferred.resolve();
+ }
+
+ }.bind(this));
+ },
+
+ /**
+ * Push a task at the end of the queue.
+ *
+ * @param {function} code A function returning a Promise.
+ * This function will be executed once all the previously
+ * pushed tasks have completed.
+ * @return {Promise} A promise with the same behavior as
+ * the promise returned by |code|.
+ */
+ push: function(code) {
+ let promise = this.queue.then(code);
+ // By definition, |this.queue| can never reject.
+ this.queue = promise.then(null, () => undefined);
+ // Fork |promise| to ensure that uncaught errors are reported
+ return promise.then(null, null);
+ },
+
+ /**
+ * Post a message to the worker thread.
+ *
+ * @param {string} method The name of the method to call.
+ * @param {...} args The arguments to pass to the method. These arguments
+ * must be clonable.
+ * @return {Promise} A promise conveying the result/error caused by
+ * calling |method| with arguments |args|.
+ */
+ post: function post(method, args = undefined, closure = undefined) {
+ if (this.shutdown) {
+ LOG("OS.File is not available anymore. The following request has been rejected.",
+ method, args);
+ return Promise.reject(new Error("OS.File has been shut down. Rejecting post to " + method));
+ }
+ let firstLaunch = !this.launched;
+ this.launched = true;
+
+ if (firstLaunch && SharedAll.Config.DEBUG) {
+ // If we have delayed sending SET_DEBUG, do it now.
+ this.worker.post("SET_DEBUG", [true]);
+ Scheduler.Debugging.messagesSent++;
+ }
+
+ Scheduler.Debugging.messagesQueued++;
+ return this.push(Task.async(function*() {
+ if (this.shutdown) {
+ LOG("OS.File is not available anymore. The following request has been rejected.",
+ method, args);
+ throw new Error("OS.File has been shut down. Rejecting request to " + method);
+ }
+
+ // Update debugging information. As |args| may be quite
+ // expensive, we only keep a shortened version of it.
+ Scheduler.Debugging.latestReceived = null;
+ Scheduler.Debugging.latestSent = [Date.now(), method, summarizeObject(args)];
+
+ // Don't kill the worker just yet
+ Scheduler.restartTimer();
+
+
+ let reply;
+ try {
+ try {
+ Scheduler.Debugging.messagesSent++;
+ Scheduler.Debugging.latestSent = Scheduler.Debugging.latestSent.slice(0, 2);
+ reply = yield this.worker.post(method, args, closure);
+ Scheduler.Debugging.latestReceived = [Date.now(), summarizeObject(reply)];
+ return reply;
+ } finally {
+ Scheduler.Debugging.messagesReceived++;
+ }
+ } catch (error) {
+ Scheduler.Debugging.latestReceived = [Date.now(), error.message, error.fileName, error.lineNumber];
+ throw error;
+ } finally {
+ if (firstLaunch) {
+ Scheduler._updateTelemetry();
+ }
+ Scheduler.restartTimer();
+ }
+ }.bind(this)));
+ },
+
+ /**
+ * Post Telemetry statistics.
+ *
+ * This is only useful on first launch.
+ */
+ _updateTelemetry: function() {
+ let worker = this.worker;
+ let workerTimeStamps = worker.workerTimeStamps;
+ if (!workerTimeStamps) {
+ // If the first call to OS.File results in an uncaught errors,
+ // the timestamps are absent. As this case is a developer error,
+ // let's not waste time attempting to extract telemetry from it.
+ return;
+ }
+ let HISTOGRAM_LAUNCH = Services.telemetry.getHistogramById("OSFILE_WORKER_LAUNCH_MS");
+ HISTOGRAM_LAUNCH.add(worker.workerTimeStamps.entered - worker.launchTimeStamp);
+
+ let HISTOGRAM_READY = Services.telemetry.getHistogramById("OSFILE_WORKER_READY_MS");
+ HISTOGRAM_READY.add(worker.workerTimeStamps.loaded - worker.launchTimeStamp);
+ }
+};
+
+const PREF_OSFILE_LOG = "toolkit.osfile.log";
+const PREF_OSFILE_LOG_REDIRECT = "toolkit.osfile.log.redirect";
+
+/**
+ * Safely read a PREF_OSFILE_LOG preference.
+ * Returns a value read or, in case of an error, oldPref or false.
+ *
+ * @param bool oldPref
+ * An optional value that the DEBUG flag was set to previously.
+ */
+function readDebugPref(prefName, oldPref = false) {
+ let pref = oldPref;
+ try {
+ pref = Services.prefs.getBoolPref(prefName);
+ } catch (x) {
+ // In case of an error when reading a pref keep it as is.
+ }
+ // If neither pref nor oldPref were set, default it to false.
+ return pref;
+};
+
+/**
+ * Listen to PREF_OSFILE_LOG changes and update gShouldLog flag
+ * appropriately.
+ */
+Services.prefs.addObserver(PREF_OSFILE_LOG,
+ function prefObserver(aSubject, aTopic, aData) {
+ SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, SharedAll.Config.DEBUG);
+ if (Scheduler.launched) {
+ // Don't start the worker just to set this preference.
+ Scheduler.post("SET_DEBUG", [SharedAll.Config.DEBUG]);
+ }
+ }, false);
+SharedAll.Config.DEBUG = readDebugPref(PREF_OSFILE_LOG, false);
+
+Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT,
+ function prefObserver(aSubject, aTopic, aData) {
+ SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, OS.Shared.TEST);
+ }, false);
+SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false);
+
+
+/**
+ * If |true|, use the native implementaiton of OS.File methods
+ * whenever possible. Otherwise, force the use of the JS version.
+ */
+var nativeWheneverAvailable = true;
+const PREF_OSFILE_NATIVE = "toolkit.osfile.native";
+Services.prefs.addObserver(PREF_OSFILE_NATIVE,
+ function prefObserver(aSubject, aTopic, aData) {
+ nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable);
+ }, false);
+
+
+// Update worker's DEBUG flag if it's true.
+// Don't start the worker just for this, though.
+if (SharedAll.Config.DEBUG && Scheduler.launched) {
+ Scheduler.post("SET_DEBUG", [true]);
+}
+
+// Observer topics used for monitoring shutdown
+const WEB_WORKERS_SHUTDOWN_TOPIC = "web-workers-shutdown";
+
+// Preference used to configure test shutdown observer.
+const PREF_OSFILE_TEST_SHUTDOWN_OBSERVER =
+ "toolkit.osfile.test.shutdown.observer";
+
+AsyncShutdown.webWorkersShutdown.addBlocker(
+ "OS.File: flush pending requests, warn about unclosed files, shut down service.",
+ Task.async(function*() {
+ // Give clients a last chance to enqueue requests.
+ yield Barriers.shutdown.wait({crashAfterMS: null});
+
+ // Wait until all requests are complete and kill the worker.
+ yield Scheduler.kill({reset: false, shutdown: true});
+ }),
+ () => {
+ let details = Barriers.getDetails();
+ details.clients = Barriers.shutdown.state;
+ return details;
+ }
+);
+
+
+// Attaching an observer for PREF_OSFILE_TEST_SHUTDOWN_OBSERVER to enable or
+// disable the test shutdown event observer.
+// Note: By default the PREF_OSFILE_TEST_SHUTDOWN_OBSERVER is unset.
+// Note: This is meant to be used for testing purposes only.
+Services.prefs.addObserver(PREF_OSFILE_TEST_SHUTDOWN_OBSERVER,
+ function prefObserver() {
+ // The temporary phase topic used to trigger the unclosed
+ // phase warning.
+ let TOPIC = null;
+ try {
+ TOPIC = Services.prefs.getCharPref(
+ PREF_OSFILE_TEST_SHUTDOWN_OBSERVER);
+ } catch (x) {
+ }
+ if (TOPIC) {
+ // Generate a phase, add a blocker.
+ // Note that this can work only if AsyncShutdown itself has been
+ // configured for testing by the testsuite.
+ let phase = AsyncShutdown._getPhase(TOPIC);
+ phase.addBlocker(
+ "(for testing purposes) OS.File: warn about unclosed files",
+ () => Scheduler.kill({shutdown: false, reset: false})
+ );
+ }
+ }, false);
+
+/**
+ * Representation of a file, with asynchronous methods.
+ *
+ * @param {*} fdmsg The _message_ representing the platform-specific file
+ * handle.
+ *
+ * @constructor
+ */
+var File = function File(fdmsg) {
+ // FIXME: At the moment, |File| does not close on finalize
+ // (see bug 777715)
+ this._fdmsg = fdmsg;
+ this._closeResult = null;
+ this._closed = null;
+};
+
+
+File.prototype = {
+ /**
+ * Close a file asynchronously.
+ *
+ * This method is idempotent.
+ *
+ * @return {promise}
+ * @resolves {null}
+ * @rejects {OS.File.Error}
+ */
+ close: function close() {
+ if (this._fdmsg != null) {
+ let msg = this._fdmsg;
+ this._fdmsg = null;
+ return this._closeResult =
+ Scheduler.post("File_prototype_close", [msg], this);
+ }
+ return this._closeResult;
+ },
+
+ /**
+ * Fetch information about the file.
+ *
+ * @return {promise}
+ * @resolves {OS.File.Info} The latest information about the file.
+ * @rejects {OS.File.Error}
+ */
+ stat: function stat() {
+ return Scheduler.post("File_prototype_stat", [this._fdmsg], this).then(
+ File.Info.fromMsg
+ );
+ },
+
+ /**
+ * 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 | C pointer} buffer The buffer in which the
+ * the bytes are stored. The buffer must be large enough to
+ * accomodate |bytes| bytes. Using the buffer before the operation
+ * is complete is a BAD IDEA.
+ * @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|. Note that |bytes| is required
+ * if |buffer| is a C pointer.
+ *
+ * @return {number} The number of bytes actually written.
+ */
+ write: function write(buffer, options = {}) {
+ // If |buffer| is a typed array and there is no |bytes| options,
+ // we need to extract the |byteLength| now, as it will be lost
+ // by communication.
+ // Options might be a nullish value, so better check for that before using
+ // the |in| operator.
+ if (isTypedArray(buffer) && !(options && "bytes" in options)) {
+ // Preserve reference to option |outExecutionDuration|, if it is passed.
+ options = clone(options, ["outExecutionDuration"]);
+ options.bytes = buffer.byteLength;
+ }
+ return Scheduler.post("File_prototype_write",
+ [this._fdmsg,
+ Type.void_t.in_ptr.toMsg(buffer),
+ options],
+ buffer/*Ensure that |buffer| is not gc-ed*/);
+ },
+
+ /**
+ * Read bytes from this file to a new buffer.
+ *
+ * @param {number=} bytes If unspecified, read all the remaining bytes from
+ * this file. If specified, read |bytes| bytes, or less if the file does not
+ * contain that many bytes.
+ * @param {JSON} options
+ * @return {promise}
+ * @resolves {Uint8Array} An array containing the bytes read.
+ */
+ read: function read(nbytes, options = {}) {
+ let promise = Scheduler.post("File_prototype_read",
+ [this._fdmsg,
+ nbytes, options]);
+ return promise.then(
+ function onSuccess(data) {
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ });
+ },
+
+ /**
+ * Return the current position in the file, as bytes.
+ *
+ * @return {promise}
+ * @resolves {number} The current position in the file,
+ * as a number of bytes since the start of the file.
+ */
+ getPosition: function getPosition() {
+ return Scheduler.post("File_prototype_getPosition",
+ [this._fdmsg]);
+ },
+
+ /**
+ * Set the current position in the file, as bytes.
+ *
+ * @param {number} pos A number of bytes.
+ * @param {number} whence The reference position in the file,
+ * which may be either POS_START (from the start of the file),
+ * POS_END (from the end of the file) or POS_CUR (from the
+ * current position in the file).
+ *
+ * @return {promise}
+ */
+ setPosition: function setPosition(pos, whence) {
+ return Scheduler.post("File_prototype_setPosition",
+ [this._fdmsg, pos, whence]);
+ },
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any running
+ * application.
+ *
+ * @return {promise}
+ */
+ flush: function flush() {
+ return Scheduler.post("File_prototype_flush",
+ [this._fdmsg]);
+ },
+
+ /**
+ * Set the file's access permissions. This does nothing on Windows.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ setPermissions: function setPermissions(options = {}) {
+ return Scheduler.post("File_prototype_setPermissions",
+ [this._fdmsg, options]);
+ }
+};
+
+
+if (SharedAll.Constants.Sys.Name != "Android" && SharedAll.Constants.Sys.Name != "Gonk") {
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * WARNING: This method is not implemented on Android/B2G. On Android/B2G,
+ * you should use File.setDates instead.
+ *
+ * @return {promise}
+ * @rejects {TypeError}
+ * @rejects {OS.File.Error}
+ */
+ File.prototype.setDates = function(accessDate, modificationDate) {
+ return Scheduler.post("File_prototype_setDates",
+ [this._fdmsg, accessDate, modificationDate], this);
+ };
+}
+
+
+/**
+ * Open a file asynchronously.
+ *
+ * @return {promise}
+ * @resolves {OS.File}
+ * @rejects {OS.Error}
+ */
+File.open = function open(path, mode, options) {
+ return Scheduler.post(
+ "open", [Type.path.toMsg(path), mode, options],
+ path
+ ).then(
+ function onSuccess(msg) {
+ return new File(msg);
+ }
+ );
+};
+
+/**
+ * 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.
+ */
+File.openUnique = function openUnique(path, options) {
+ return Scheduler.post(
+ "openUnique", [Type.path.toMsg(path), options],
+ path
+ ).then(
+ function onSuccess(msg) {
+ return {
+ path: msg.path,
+ file: new File(msg.file)
+ };
+ }
+ );
+};
+
+/**
+ * Get the information on the file.
+ *
+ * @return {promise}
+ * @resolves {OS.File.Info}
+ * @rejects {OS.Error}
+ */
+File.stat = function stat(path, options) {
+ return Scheduler.post(
+ "stat", [Type.path.toMsg(path), options],
+ path).then(File.Info.fromMsg);
+};
+
+
+/**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @return {promise}
+ * @rejects {TypeError}
+ * @rejects {OS.File.Error}
+ */
+File.setDates = function setDates(path, accessDate, modificationDate) {
+ return Scheduler.post("setDates",
+ [Type.path.toMsg(path), accessDate, modificationDate],
+ this);
+};
+
+/**
+ * Set the file's access permissions. This does nothing on Windows.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+File.setPermissions = function setPermissions(path, options = {}) {
+ return Scheduler.post("setPermissions",
+ [Type.path.toMsg(path), options]);
+};
+
+/**
+ * Fetch the current directory
+ *
+ * @return {promise}
+ * @resolves {string} The current directory, as a path usable with OS.Path
+ * @rejects {OS.Error}
+ */
+File.getCurrentDirectory = function getCurrentDirectory() {
+ return Scheduler.post(
+ "getCurrentDirectory"
+ ).then(Type.path.fromMsg);
+};
+
+/**
+ * Change the current directory
+ *
+ * @param {string} path The OS-specific path to the current directory.
+ * You should use the methods of OS.Path and the constants of OS.Constants.Path
+ * to build OS-specific paths in a portable manner.
+ *
+ * @return {promise}
+ * @resolves {null}
+ * @rejects {OS.Error}
+ */
+File.setCurrentDirectory = function setCurrentDirectory(path) {
+ return Scheduler.post(
+ "setCurrentDirectory", [Type.path.toMsg(path)], path
+ );
+};
+
+/**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If true, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @rejects {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+*/
+File.copy = function copy(sourcePath, destPath, options) {
+ return Scheduler.post("copy", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath), options], [sourcePath, destPath]);
+};
+
+/**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @returns {Promise}
+ * @rejects {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+File.move = function move(sourcePath, destPath, options) {
+ return Scheduler.post("move", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath), options], [sourcePath, destPath]);
+};
+
+/**
+ * Create a symbolic link to a source.
+ *
+ * @param {string} sourcePath The platform-specific path to which
+ * the symbolic link should point.
+ * @param {string} destPath The platform-specific path at which the
+ * symbolic link should be created.
+ *
+ * @returns {Promise}
+ * @rejects {OS.File.Error} In case of any error.
+ */
+if (!SharedAll.Constants.Win) {
+ File.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ return Scheduler.post("unixSymLink", [Type.path.toMsg(sourcePath),
+ Type.path.toMsg(destPath)], [sourcePath, destPath]);
+ };
+}
+
+/**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} Platform-specific path to a directory on the disk to
+ * query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+File.getAvailableFreeSpace = function getAvailableFreeSpace(sourcePath) {
+ return Scheduler.post("getAvailableFreeSpace",
+ [Type.path.toMsg(sourcePath)], sourcePath
+ ).then(Type.uint64_t.fromMsg);
+};
+
+/**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |true|, do not fail if the
+ * directory does not exist yet.
+ */
+File.removeEmptyDir = function removeEmptyDir(path, options) {
+ return Scheduler.post("removeEmptyDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+File.remove = function remove(path, options) {
+ return Scheduler.post("remove",
+ [Type.path.toMsg(path), options], 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.
+ */
+File.makeDir = function makeDir(path, options) {
+ return Scheduler.post("makeDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * 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 {JSON} options Additional options.
+ * - {boolean} sequential A flag that triggers a population of the page cache
+ * with data from a file so that subsequent reads from that file would not
+ * block on disk I/O. If |true| or unspecified, inform the system that the
+ * contents of the file will be read in order. Otherwise, make no such
+ * assumption. |true| by default.
+ * - {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.
+ *
+ * @resolves {Uint8Array} A buffer holding the bytes
+ * read from the file.
+ */
+File.read = function read(path, bytes, options = {}) {
+ if (typeof bytes == "object") {
+ // Passing |bytes| as an argument is deprecated.
+ // We should now be passing it as a field of |options|.
+ options = bytes || {};
+ } else {
+ options = clone(options, ["outExecutionDuration"]);
+ if (typeof bytes != "undefined") {
+ options.bytes = bytes;
+ }
+ }
+
+ if (options.compression || !nativeWheneverAvailable) {
+ // We need to use the JS implementation.
+ let promise = Scheduler.post("read",
+ [Type.path.toMsg(path), bytes, options], path);
+ return promise.then(
+ function onSuccess(data) {
+ if (typeof data == "string") {
+ return data;
+ }
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ });
+ }
+
+ // Otherwise, use the native implementation.
+ return Scheduler.push(() => Native.read(path, options));
+};
+
+/**
+ * Find outs if a file exists.
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+File.exists = function exists(path) {
+ return Scheduler.post("exists",
+ [Type.path.toMsg(path)], path);
+};
+
+/**
+ * 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} 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 {promise}
+ * @resolves {number} The number of bytes actually written.
+ */
+File.writeAtomic = function writeAtomic(path, buffer, options = {}) {
+ // Copy |options| to avoid modifying the original object but preserve the
+ // reference to |outExecutionDuration| option if it is passed.
+ options = clone(options, ["outExecutionDuration"]);
+ // As options.tmpPath is a path, we need to encode it as |Type.path| message
+ if ("tmpPath" in options) {
+ options.tmpPath = Type.path.toMsg(options.tmpPath);
+ };
+ if (isTypedArray(buffer) && (!("bytes" in options))) {
+ options.bytes = buffer.byteLength;
+ };
+ let refObj = {};
+ TelemetryStopwatch.start("OSFILE_WRITEATOMIC_JANK_MS", refObj);
+ let promise = Scheduler.post("writeAtomic",
+ [Type.path.toMsg(path),
+ Type.void_t.in_ptr.toMsg(buffer),
+ options], [options, buffer, path]);
+ TelemetryStopwatch.finish("OSFILE_WRITEATOMIC_JANK_MS", refObj);
+ return promise;
+};
+
+File.removeDir = function(path, options = {}) {
+ return Scheduler.post("removeDir",
+ [Type.path.toMsg(path), options], path);
+};
+
+/**
+ * Information on a file, as returned by OS.File.stat or
+ * OS.File.prototype.stat
+ *
+ * @constructor
+ */
+File.Info = function Info(value) {
+ // Note that we can't just do this[k] = value[k] because our
+ // prototype defines getters for all of these fields.
+ for (let k in value) {
+ if (k != "creationDate") {
+ Object.defineProperty(this, k, {value: value[k]});
+ }
+ }
+ Object.defineProperty(this, "_deprecatedCreationDate", {value: value["creationDate"]});
+};
+File.Info.prototype = SysAll.AbstractInfo.prototype;
+
+// Deprecated
+Object.defineProperty(File.Info.prototype, "creationDate", {
+ get: function creationDate() {
+ let {Deprecated} = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("Field 'creationDate' is deprecated.", "https://developer.mozilla.org/en-US/docs/JavaScript_OS.File/OS.File.Info#Cross-platform_Attributes");
+ return this._deprecatedCreationDate;
+ }
+});
+
+File.Info.fromMsg = function fromMsg(value) {
+ return new File.Info(value);
+};
+
+/**
+ * Get worker's current DEBUG flag.
+ * Note: This is used for testing purposes.
+ */
+File.GET_DEBUG = function GET_DEBUG() {
+ return Scheduler.post("GET_DEBUG");
+};
+
+/**
+ * Iterate asynchronously through a directory
+ *
+ * @constructor
+ */
+var DirectoryIterator = function DirectoryIterator(path, options) {
+ /**
+ * Open the iterator on the worker thread
+ *
+ * @type {Promise}
+ * @resolves {*} A message accepted by the methods of DirectoryIterator
+ * in the worker thread
+ * @rejects {StopIteration} If all entries have already been visited
+ * or the iterator has been closed.
+ */
+ this.__itmsg = Scheduler.post(
+ "new_DirectoryIterator", [Type.path.toMsg(path), options],
+ path
+ );
+ this._isClosed = false;
+};
+DirectoryIterator.prototype = {
+ iterator: function () {
+ return this;
+ },
+ __iterator__: function () {
+ return this;
+ },
+
+ // Once close() is called, _itmsg should reject with a
+ // StopIteration. However, we don't want to create the promise until
+ // it's needed because it might never be used. In that case, we
+ // would get a warning on the console.
+ get _itmsg() {
+ if (!this.__itmsg) {
+ this.__itmsg = Promise.reject(StopIteration);
+ }
+ return this.__itmsg;
+ },
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @resolves {boolean}
+ */
+ exists: function exists() {
+ return this._itmsg.then(
+ function onSuccess(iterator) {
+ return Scheduler.post("DirectoryIterator_prototype_exists", [iterator]);
+ }
+ );
+ },
+ /**
+ * Get the next entry in the directory.
+ *
+ * @return {Promise}
+ * @resolves {OS.File.Entry}
+ * @rejects {StopIteration} If all entries have already been visited.
+ */
+ next: function next() {
+ let self = this;
+ let promise = this._itmsg;
+
+ // Get the iterator, call _next
+ promise = promise.then(
+ function withIterator(iterator) {
+ return self._next(iterator);
+ });
+
+ return promise;
+ },
+ /**
+ * Get several entries at once.
+ *
+ * @param {number=} length If specified, the number of entries
+ * to return. If unspecified, return all remaining entries.
+ * @return {Promise}
+ * @resolves {Array} An array containing the |length| next entries.
+ */
+ nextBatch: function nextBatch(size) {
+ if (this._isClosed) {
+ return Promise.resolve([]);
+ }
+ let promise = this._itmsg;
+ promise = promise.then(
+ function withIterator(iterator) {
+ return Scheduler.post("DirectoryIterator_prototype_nextBatch", [iterator, size]);
+ });
+ promise = promise.then(
+ function withEntries(array) {
+ return array.map(DirectoryIterator.Entry.fromMsg);
+ });
+ return promise;
+ },
+ /**
+ * 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 - return |iterator.close()| to stop the loop.
+ *
+ * If the callback returns a promise, iteration waits until the
+ * promise is resolved before proceeding.
+ *
+ * @return {Promise} A promise resolved once the loop has reached
+ * its end.
+ */
+ forEach: function forEach(cb, options) {
+ if (this._isClosed) {
+ return Promise.resolve();
+ }
+
+ let self = this;
+ let position = 0;
+ let iterator;
+
+ // Grab iterator
+ let promise = this._itmsg.then(
+ function(aIterator) {
+ iterator = aIterator;
+ }
+ );
+
+ // Then iterate
+ let loop = function loop() {
+ if (self._isClosed) {
+ return Promise.resolve();
+ }
+ return self._next(iterator).then(
+ function onSuccess(value) {
+ return Promise.resolve(cb(value, position++, self)).then(loop);
+ },
+ function onFailure(reason) {
+ if (reason == StopIteration) {
+ return;
+ }
+ throw reason;
+ }
+ );
+ };
+
+ return promise.then(loop);
+ },
+ /**
+ * Auxiliary method: fetch the next item
+ *
+ * @rejects {StopIteration} If all entries have already been visited
+ * or the iterator has been closed.
+ */
+ _next: function _next(iterator) {
+ if (this._isClosed) {
+ return this._itmsg;
+ }
+ let self = this;
+ let promise = Scheduler.post("DirectoryIterator_prototype_next", [iterator]);
+ promise = promise.then(
+ DirectoryIterator.Entry.fromMsg,
+ function onReject(reason) {
+ if (reason == StopIteration) {
+ self.close();
+ throw StopIteration;
+ }
+ throw reason;
+ });
+ return promise;
+ },
+ /**
+ * Close the iterator
+ */
+ close: function close() {
+ if (this._isClosed) {
+ return Promise.resolve();
+ }
+ this._isClosed = true;
+ let self = this;
+ return this._itmsg.then(
+ function withIterator(iterator) {
+ // Set __itmsg to null so that the _itmsg getter returns a
+ // rejected StopIteration promise if it's ever used.
+ self.__itmsg = null;
+ return Scheduler.post("DirectoryIterator_prototype_close", [iterator]);
+ }
+ );
+ }
+};
+
+DirectoryIterator.Entry = function Entry(value) {
+ return value;
+};
+DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+DirectoryIterator.Entry.fromMsg = function fromMsg(value) {
+ return new DirectoryIterator.Entry(value);
+};
+
+File.resetWorker = function() {
+ return Task.spawn(function*() {
+ let resources = yield Scheduler.kill({shutdown: false, reset: true});
+ if (resources && !resources.killed) {
+ throw new Error("Could not reset worker, this would leak file descriptors: " + JSON.stringify(resources));
+ }
+ });
+};
+
+// Constants
+File.POS_START = SysAll.POS_START;
+File.POS_CURRENT = SysAll.POS_CURRENT;
+File.POS_END = SysAll.POS_END;
+
+// Exports
+File.Error = OSError;
+File.DirectoryIterator = DirectoryIterator;
+
+this.OS = {};
+this.OS.File = File;
+this.OS.Constants = SharedAll.Constants;
+this.OS.Shared = {
+ LOG: SharedAll.LOG,
+ Type: SysAll.Type,
+ get DEBUG() {
+ return SharedAll.Config.DEBUG;
+ },
+ set DEBUG(x) {
+ return SharedAll.Config.DEBUG = x;
+ }
+};
+Object.freeze(this.OS.Shared);
+this.OS.Path = Path;
+
+// Returns a resolved promise when all the queued operation have been completed.
+Object.defineProperty(OS.File, "queue", {
+ get: function() {
+ return Scheduler.queue;
+ }
+});
+
+// `true` if this is a content process, `false` otherwise.
+// It would be nicer to go through `Services.appInfo`, but some tests need to be
+// able to replace that field with a custom implementation before it is first
+// called.
+const isContent = Components.classes["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
+/**
+ * Shutdown barriers, to let clients register to be informed during shutdown.
+ */
+var Barriers = {
+ shutdown: new AsyncShutdown.Barrier("OS.File: Waiting for clients before full shutdown"),
+ /**
+ * Return the shutdown state of OS.File
+ */
+ getDetails: function() {
+ let result = {
+ launched: Scheduler.launched,
+ shutdown: Scheduler.shutdown,
+ worker: !!Scheduler._worker,
+ pendingReset: !!Scheduler.resetTimer,
+ latestSent: Scheduler.Debugging.latestSent,
+ latestReceived: Scheduler.Debugging.latestReceived,
+ messagesSent: Scheduler.Debugging.messagesSent,
+ messagesReceived: Scheduler.Debugging.messagesReceived,
+ messagesQueued: Scheduler.Debugging.messagesQueued,
+ DEBUG: SharedAll.Config.DEBUG,
+ };
+ // Convert dates to strings for better readability
+ for (let key of ["latestSent", "latestReceived"]) {
+ if (result[key] && typeof result[key][0] == "number") {
+ result[key][0] = Date(result[key][0]);
+ }
+ }
+ return result;
+ }
+};
+
+function setupShutdown(phaseName) {
+ Barriers[phaseName] = new AsyncShutdown.Barrier(`OS.File: Waiting for clients before ${phaseName}`),
+ File[phaseName] = Barriers[phaseName].client;
+
+ // Auto-flush OS.File during `phaseName`. This ensures that any I/O
+ // that has been queued *before* `phaseName` is properly completed.
+ // To ensure that I/O queued *during* `phaseName` change is completed,
+ // clients should register using AsyncShutdown.addBlocker.
+ AsyncShutdown[phaseName].addBlocker(
+ `OS.File: flush I/O queued before ${phaseName}`,
+ Task.async(function*() {
+ // Give clients a last chance to enqueue requests.
+ yield Barriers[phaseName].wait({crashAfterMS: null});
+
+ // Wait until all currently enqueued requests are completed.
+ yield Scheduler.queue;
+ }),
+ () => {
+ let details = Barriers.getDetails();
+ details.clients = Barriers[phaseName].state;
+ return details;
+ }
+ );
+}
+
+// profile-before-change only exists in the parent, and OS.File should
+// not be used in the child process anyways.
+if (!isContent) {
+ setupShutdown("profileBeforeChange")
+}
+File.shutdown = Barriers.shutdown.client;
diff --git a/toolkit/components/osfile/modules/osfile_async_worker.js b/toolkit/components/osfile/modules/osfile_async_worker.js
new file mode 100644
index 000000000..84287c75e
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -0,0 +1,407 @@
+/* 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/. */
+
+
+if (this.Components) {
+ throw new Error("This worker can only be loaded from a worker thread");
+}
+
+// Worker thread for osfile asynchronous front-end
+
+(function(exports) {
+ "use strict";
+
+ // Timestamps, for use in Telemetry.
+ // The object is set to |null| once it has been sent
+ // to the main thread.
+ let timeStamps = {
+ entered: Date.now(),
+ loaded: null
+ };
+
+ importScripts("resource://gre/modules/osfile.jsm");
+
+ let PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Agent");
+
+ let worker = new PromiseWorker.AbstractWorker();
+ worker.dispatch = function(method, args = []) {
+ return Agent[method](...args);
+ },
+ worker.log = LOG;
+ worker.postMessage = function(message, ...transfers) {
+ if (timeStamps) {
+ message.timeStamps = timeStamps;
+ timeStamps = null;
+ }
+ self.postMessage(message, ...transfers);
+ };
+ worker.close = function() {
+ self.close();
+ };
+ let Meta = PromiseWorker.Meta;
+
+ self.addEventListener("message", msg => worker.handleMessage(msg));
+
+ /**
+ * A data structure used to track opened resources
+ */
+ let ResourceTracker = function ResourceTracker() {
+ // A number used to generate ids
+ this._idgen = 0;
+ // A map from id to resource
+ this._map = new Map();
+ };
+ ResourceTracker.prototype = {
+ /**
+ * Get a resource from its unique identifier.
+ */
+ get: function(id) {
+ let result = this._map.get(id);
+ if (result == null) {
+ return result;
+ }
+ return result.resource;
+ },
+ /**
+ * Remove a resource from its unique identifier.
+ */
+ remove: function(id) {
+ if (!this._map.has(id)) {
+ throw new Error("Cannot find resource id " + id);
+ }
+ this._map.delete(id);
+ },
+ /**
+ * Add a resource, return a new unique identifier
+ *
+ * @param {*} resource A resource.
+ * @param {*=} info Optional information. For debugging purposes.
+ *
+ * @return {*} A unique identifier. For the moment, this is a number,
+ * but this might not remain the case forever.
+ */
+ add: function(resource, info) {
+ let id = this._idgen++;
+ this._map.set(id, {resource:resource, info:info});
+ return id;
+ },
+ /**
+ * Return a list of all open resources i.e. the ones still present in
+ * ResourceTracker's _map.
+ */
+ listOpenedResources: function listOpenedResources() {
+ return Array.from(this._map, ([id, resource]) => resource.info.path);
+ }
+ };
+
+ /**
+ * A map of unique identifiers to opened files.
+ */
+ let OpenedFiles = new ResourceTracker();
+
+ /**
+ * Execute a function in the context of a given file.
+ *
+ * @param {*} id A unique identifier, as used by |OpenFiles|.
+ * @param {Function} f A function to call.
+ * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception.
+ * @return The return value of |f()|
+ *
+ * This function attempts to get the file matching |id|. If
+ * the file exists, it executes |f| within the |this| set
+ * to the corresponding file. Otherwise, it throws an error.
+ */
+ let withFile = function withFile(id, f, ignoreAbsent) {
+ let file = OpenedFiles.get(id);
+ if (file == null) {
+ if (!ignoreAbsent) {
+ throw OS.File.Error.closed("accessing file");
+ }
+ return undefined;
+ }
+ return f.call(file);
+ };
+
+ let OpenedDirectoryIterators = new ResourceTracker();
+ let withDir = function withDir(fd, f, ignoreAbsent) {
+ let file = OpenedDirectoryIterators.get(fd);
+ if (file == null) {
+ if (!ignoreAbsent) {
+ throw OS.File.Error.closed("accessing directory");
+ }
+ return undefined;
+ }
+ if (!(file instanceof File.DirectoryIterator)) {
+ throw new Error("file is not a directory iterator " + file.__proto__.toSource());
+ }
+ return f.call(file);
+ };
+
+ let Type = exports.OS.Shared.Type;
+
+ let File = exports.OS.File;
+
+ /**
+ * The agent.
+ *
+ * It is in charge of performing method-specific deserialization
+ * of messages, calling the function/method of OS.File and serializing
+ * back the results.
+ */
+ let Agent = {
+ // Update worker's OS.Shared.DEBUG flag message from controller.
+ SET_DEBUG: function(aDEBUG) {
+ SharedAll.Config.DEBUG = aDEBUG;
+ },
+ // Return worker's current OS.Shared.DEBUG value to controller.
+ // Note: This is used for testing purposes.
+ GET_DEBUG: function() {
+ return SharedAll.Config.DEBUG;
+ },
+ /**
+ * Execute shutdown sequence, returning data on leaked file descriptors.
+ *
+ * @param {bool} If |true|, kill the worker if this would not cause
+ * leaks.
+ */
+ Meta_shutdown: function(kill) {
+ let result = {
+ openedFiles: OpenedFiles.listOpenedResources(),
+ openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
+ killed: false // Placeholder
+ };
+
+ // Is it safe to kill the worker?
+ let safe = result.openedFiles.length == 0
+ && result.openedDirectoryIterators.length == 0;
+ result.killed = safe && kill;
+
+ return new Meta(result, {shutdown: result.killed});
+ },
+ // Functions of OS.File
+ stat: function stat(path, options) {
+ return exports.OS.File.Info.toMsg(
+ exports.OS.File.stat(Type.path.fromMsg(path), options));
+ },
+ setPermissions: function setPermissions(path, options = {}) {
+ return exports.OS.File.setPermissions(Type.path.fromMsg(path), options);
+ },
+ setDates: function setDates(path, accessDate, modificationDate) {
+ return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate,
+ modificationDate);
+ },
+ getCurrentDirectory: function getCurrentDirectory() {
+ return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
+ },
+ setCurrentDirectory: function setCurrentDirectory(path) {
+ File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path));
+ },
+ copy: function copy(sourcePath, destPath, options) {
+ return File.copy(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath), options);
+ },
+ move: function move(sourcePath, destPath, options) {
+ return File.move(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath), options);
+ },
+ getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
+ return Type.uint64_t.toMsg(
+ File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
+ },
+ makeDir: function makeDir(path, options) {
+ return File.makeDir(Type.path.fromMsg(path), options);
+ },
+ removeEmptyDir: function removeEmptyDir(path, options) {
+ return File.removeEmptyDir(Type.path.fromMsg(path), options);
+ },
+ remove: function remove(path, options) {
+ return File.remove(Type.path.fromMsg(path), options);
+ },
+ open: function open(path, mode, options) {
+ let filePath = Type.path.fromMsg(path);
+ let file = File.open(filePath, mode, options);
+ return OpenedFiles.add(file, {
+ // Adding path information to keep track of opened files
+ // to report leaks when debugging.
+ path: filePath
+ });
+ },
+ openUnique: function openUnique(path, options) {
+ let filePath = Type.path.fromMsg(path);
+ let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options);
+ let resourceId = OpenedFiles.add(openedFile.file, {
+ // Adding path information to keep track of opened files
+ // to report leaks when debugging.
+ path: openedFile.path
+ });
+
+ return {
+ path: openedFile.path,
+ file: resourceId
+ };
+ },
+ read: function read(path, bytes, options) {
+ let data = File.read(Type.path.fromMsg(path), bytes, options);
+ if (typeof data == "string") {
+ return data;
+ }
+ return new Meta({
+ buffer: data.buffer,
+ byteOffset: data.byteOffset,
+ byteLength: data.byteLength
+ }, {
+ transfers: [data.buffer]
+ });
+ },
+ exists: function exists(path) {
+ return File.exists(Type.path.fromMsg(path));
+ },
+ writeAtomic: function writeAtomic(path, buffer, options) {
+ if (options.tmpPath) {
+ options.tmpPath = Type.path.fromMsg(options.tmpPath);
+ }
+ return File.writeAtomic(Type.path.fromMsg(path),
+ Type.voidptr_t.fromMsg(buffer),
+ options
+ );
+ },
+ removeDir: function(path, options) {
+ return File.removeDir(Type.path.fromMsg(path), options);
+ },
+ new_DirectoryIterator: function new_DirectoryIterator(path, options) {
+ let directoryPath = Type.path.fromMsg(path);
+ let iterator = new File.DirectoryIterator(directoryPath, options);
+ return OpenedDirectoryIterators.add(iterator, {
+ // Adding path information to keep track of opened directory
+ // iterators to report leaks when debugging.
+ path: directoryPath
+ });
+ },
+ // Methods of OS.File
+ File_prototype_close: function close(fd) {
+ return withFile(fd,
+ function do_close() {
+ try {
+ return this.close();
+ } finally {
+ OpenedFiles.remove(fd);
+ }
+ });
+ },
+ File_prototype_stat: function stat(fd) {
+ return withFile(fd,
+ function do_stat() {
+ return exports.OS.File.Info.toMsg(this.stat());
+ });
+ },
+ File_prototype_setPermissions: function setPermissions(fd, options = {}) {
+ return withFile(fd,
+ function do_setPermissions() {
+ return this.setPermissions(options);
+ });
+ },
+ File_prototype_setDates: function setDates(fd, accessTime, modificationTime) {
+ return withFile(fd,
+ function do_setDates() {
+ return this.setDates(accessTime, modificationTime);
+ });
+ },
+ File_prototype_read: function read(fd, nbytes, options) {
+ return withFile(fd,
+ function do_read() {
+ let data = this.read(nbytes, options);
+ return new Meta({
+ buffer: data.buffer,
+ byteOffset: data.byteOffset,
+ byteLength: data.byteLength
+ }, {
+ transfers: [data.buffer]
+ });
+ }
+ );
+ },
+ File_prototype_readTo: function readTo(fd, buffer, options) {
+ return withFile(fd,
+ function do_readTo() {
+ return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
+ options);
+ });
+ },
+ File_prototype_write: function write(fd, buffer, options) {
+ return withFile(fd,
+ function do_write() {
+ return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
+ options);
+ });
+ },
+ File_prototype_setPosition: function setPosition(fd, pos, whence) {
+ return withFile(fd,
+ function do_setPosition() {
+ return this.setPosition(pos, whence);
+ });
+ },
+ File_prototype_getPosition: function getPosition(fd) {
+ return withFile(fd,
+ function do_getPosition() {
+ return this.getPosition();
+ });
+ },
+ File_prototype_flush: function flush(fd) {
+ return withFile(fd,
+ function do_flush() {
+ return this.flush();
+ });
+ },
+ // Methods of OS.File.DirectoryIterator
+ DirectoryIterator_prototype_next: function next(dir) {
+ return withDir(dir,
+ function do_next() {
+ try {
+ return File.DirectoryIterator.Entry.toMsg(this.next());
+ } catch (x) {
+ if (x == StopIteration) {
+ OpenedDirectoryIterators.remove(dir);
+ }
+ throw x;
+ }
+ }, false);
+ },
+ DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) {
+ return withDir(dir,
+ function do_nextBatch() {
+ let result;
+ try {
+ result = this.nextBatch(size);
+ } catch (x) {
+ OpenedDirectoryIterators.remove(dir);
+ throw x;
+ }
+ return result.map(File.DirectoryIterator.Entry.toMsg);
+ }, false);
+ },
+ DirectoryIterator_prototype_close: function close(dir) {
+ return withDir(dir,
+ function do_close() {
+ this.close();
+ OpenedDirectoryIterators.remove(dir);
+ }, true);// ignore error to support double-closing |DirectoryIterator|
+ },
+ DirectoryIterator_prototype_exists: function exists(dir) {
+ return withDir(dir,
+ function do_exists() {
+ return this.exists();
+ });
+ }
+ };
+ if (!SharedAll.Constants.Win) {
+ Agent.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ return File.unixSymLink(Type.path.fromMsg(sourcePath),
+ Type.path.fromMsg(destPath));
+ };
+ }
+
+ timeStamps.loaded = Date.now();
+})(this);
diff --git a/toolkit/components/osfile/modules/osfile_native.jsm b/toolkit/components/osfile/modules/osfile_native.jsm
new file mode 100644
index 000000000..16cd3c92a
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_native.jsm
@@ -0,0 +1,70 @@
+/* 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/. */
+
+/**
+ * Native (xpcom) implementation of key OS.File functions
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["read"];
+
+var {results: Cr, utils: Cu, interfaces: Ci} = Components;
+
+var SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {});
+
+var SysAll = {};
+if (SharedAll.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll);
+} else if (SharedAll.Constants.libc) {
+ Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll);
+} else {
+ throw new Error("I am neither under Windows nor under a Posix system");
+}
+var {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+/**
+ * The native service holding the implementation of the functions.
+ */
+XPCOMUtils.defineLazyServiceGetter(this,
+ "Internals",
+ "@mozilla.org/toolkit/osfile/native-internals;1",
+ "nsINativeOSFileInternalsService");
+
+/**
+ * Native implementation of OS.File.read
+ *
+ * This implementation does not handle option |compression|.
+ */
+this.read = function(path, options = {}) {
+ // Sanity check on types of options
+ if ("encoding" in options && typeof options.encoding != "string") {
+ return Promise.reject(new TypeError("Invalid type for option encoding"));
+ }
+ if ("compression" in options && typeof options.compression != "string") {
+ return Promise.reject(new TypeError("Invalid type for option compression"));
+ }
+ if ("bytes" in options && typeof options.bytes != "number") {
+ return Promise.reject(new TypeError("Invalid type for option bytes"));
+ }
+
+ let deferred = Promise.defer();
+ Internals.read(path,
+ options,
+ function onSuccess(success) {
+ success.QueryInterface(Ci.nsINativeOSFileResult);
+ if ("outExecutionDuration" in options) {
+ options.outExecutionDuration =
+ success.executionDurationMS +
+ (options.outExecutionDuration || 0);
+ }
+ deferred.resolve(success.result);
+ },
+ function onError(operation, oserror) {
+ deferred.reject(new SysAll.Error(operation, oserror, path));
+ }
+ );
+ return deferred.promise;
+};
diff --git a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
new file mode 100644
index 000000000..c5c505102
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm
@@ -0,0 +1,1315 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * OS.File utilities used by all threads.
+ *
+ * This module defines:
+ * - logging;
+ * - the base constants;
+ * - base types and primitives for declaring new types;
+ * - primitives for importing C functions;
+ * - primitives for dealing with integers, pointers, typed arrays;
+ * - the base class OSError;
+ * - a few additional utilities.
+ */
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+
+// Since const is lexically scoped, hoist the
+// conditionally-useful definition ourselves.
+const Cu = typeof Components != "undefined" ? Components.utils : undefined;
+const Ci = typeof Components != "undefined" ? Components.interfaces : undefined;
+const Cc = typeof Components != "undefined" ? Components.classes : undefined;
+
+/**
+ * A constructor for messages that require transfers instead of copies.
+ *
+ * See BasePromiseWorker.Meta.
+ *
+ * @constructor
+ */
+var Meta;
+if (typeof Components != "undefined") {
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+
+ Cu.import("resource://gre/modules/Services.jsm", this);
+ Meta = Cu.import("resource://gre/modules/PromiseWorker.jsm", {}).BasePromiseWorker.Meta;
+} else {
+ importScripts("resource://gre/modules/workers/require.js");
+ Meta = require("resource://gre/modules/workers/PromiseWorker.js").Meta;
+}
+
+var EXPORTED_SYMBOLS = [
+ "LOG",
+ "clone",
+ "Config",
+ "Constants",
+ "Type",
+ "HollowStructure",
+ "OSError",
+ "Library",
+ "declareFFI",
+ "declareLazy",
+ "declareLazyFFI",
+ "normalizeBufferArgs",
+ "projectValue",
+ "isArrayBuffer",
+ "isTypedArray",
+ "defineLazyGetter",
+ "OS" // Warning: this exported symbol will disappear
+];
+
+////////////////////// Configuration of OS.File
+
+var Config = {
+ /**
+ * If |true|, calls to |LOG| are shown. Otherwise, they are hidden.
+ *
+ * This configuration option is controlled by preference "toolkit.osfile.log".
+ */
+ DEBUG: false,
+
+ /**
+ * TEST
+ */
+ TEST: false
+};
+exports.Config = Config;
+
+////////////////////// OS Constants
+
+if (typeof Components != "undefined") {
+ // On the main thread, OS.Constants is defined by a xpcom
+ // component. On other threads, it is available automatically
+ Cu.import("resource://gre/modules/ctypes.jsm");
+ Cc["@mozilla.org/net/osfileconstantsservice;1"].
+ getService(Ci.nsIOSFileConstantsService).init();
+}
+
+exports.Constants = OS.Constants;
+
+///////////////////// Utilities
+
+// Define a lazy getter for a property
+var defineLazyGetter = function defineLazyGetter(object, name, getter) {
+ Object.defineProperty(object, name, {
+ configurable: true,
+ get: function lazy() {
+ delete this[name];
+ let value = getter.call(this);
+ Object.defineProperty(object, name, {
+ value: value
+ });
+ return value;
+ }
+ });
+};
+exports.defineLazyGetter = defineLazyGetter;
+
+
+///////////////////// Logging
+
+/**
+ * The default implementation of the logger.
+ *
+ * The choice of logger can be overridden with Config.TEST.
+ */
+var gLogger;
+if (typeof window != "undefined" && window.console && console.log) {
+ gLogger = console.log.bind(console, "OS");
+} else {
+ gLogger = function(...args) {
+ dump("OS " + args.join(" ") + "\n");
+ };
+}
+
+/**
+ * Attempt to stringify an argument into something useful for
+ * debugging purposes, by using |.toString()| or |JSON.stringify|
+ * if available.
+ *
+ * @param {*} arg An argument to be stringified if possible.
+ * @return {string} A stringified version of |arg|.
+ */
+var stringifyArg = function stringifyArg(arg) {
+ if (typeof arg === "string") {
+ return arg;
+ }
+ if (arg && typeof arg === "object") {
+ let argToString = "" + arg;
+
+ /**
+ * The only way to detect whether this object has a non-default
+ * implementation of |toString| is to check whether it returns
+ * '[object Object]'. Unfortunately, we cannot simply compare |arg.toString|
+ * and |Object.prototype.toString| as |arg| typically comes from another
+ * compartment.
+ */
+ if (argToString === "[object Object]") {
+ return JSON.stringify(arg, function(key, value) {
+ if (isTypedArray(value)) {
+ return "["+ value.constructor.name + " " + value.byteOffset + " " + value.byteLength + "]";
+ }
+ if (isArrayBuffer(arg)) {
+ return "[" + value.constructor.name + " " + value.byteLength + "]";
+ }
+ return value;
+ });
+ } else {
+ return argToString;
+ }
+ }
+ return arg;
+};
+
+var LOG = function (...args) {
+ if (!Config.DEBUG) {
+ // If logging is deactivated, don't log
+ return;
+ }
+
+ let logFunc = gLogger;
+ if (Config.TEST && typeof Components != "undefined") {
+ // If _TESTING_LOGGING is set, and if we are on the main thread,
+ // redirect logs to Services.console, for testing purposes
+ logFunc = function logFunc(...args) {
+ let message = ["TEST", "OS"].concat(args).join(" ");
+ Services.console.logStringMessage(message + "\n");
+ };
+ }
+ logFunc.apply(null, args.map(stringifyArg));
+};
+
+exports.LOG = LOG;
+
+/**
+ * Return a shallow clone of the enumerable properties of an object.
+ *
+ * Utility used whenever normalizing options requires making (shallow)
+ * changes to an option object. The copy ensures that we do not modify
+ * a client-provided object by accident.
+ *
+ * Note: to reference and not copy specific fields, provide an optional
+ * |refs| argument containing their names.
+ *
+ * @param {JSON} object Options to be cloned.
+ * @param {Array} refs An optional array of field names to be passed by
+ * reference instead of copying.
+ */
+var clone = function (object, refs = []) {
+ let result = {};
+ // Make a reference between result[key] and object[key].
+ let refer = function refer(result, key, object) {
+ Object.defineProperty(result, key, {
+ enumerable: true,
+ get: function() {
+ return object[key];
+ },
+ set: function(value) {
+ object[key] = value;
+ }
+ });
+ };
+ for (let k in object) {
+ if (refs.indexOf(k) < 0) {
+ result[k] = object[k];
+ } else {
+ refer(result, k, object);
+ }
+ }
+ return result;
+};
+
+exports.clone = clone;
+
+///////////////////// Abstractions above js-ctypes
+
+/**
+ * Abstraction above js-ctypes types.
+ *
+ * Use values of this type to register FFI functions. In addition to the
+ * usual features of js-ctypes, values of this type perform the necessary
+ * transformations to ensure that C errors are handled nicely, to connect
+ * resources with their finalizer, etc.
+ *
+ * @param {string} name The name of the type. Must be unique.
+ * @param {CType} implementation The js-ctypes implementation of the type.
+ *
+ * @constructor
+ */
+function Type(name, implementation) {
+ if (!(typeof name == "string")) {
+ throw new TypeError("Type expects as first argument a name, got: "
+ + name);
+ }
+ if (!(implementation instanceof ctypes.CType)) {
+ throw new TypeError("Type expects as second argument a ctypes.CType"+
+ ", got: " + implementation);
+ }
+ Object.defineProperty(this, "name", { value: name });
+ Object.defineProperty(this, "implementation", { value: implementation });
+}
+Type.prototype = {
+ /**
+ * Serialize a value of |this| |Type| into a format that can
+ * be transmitted as a message (not necessarily a string).
+ *
+ * In the default implementation, the method returns the
+ * value unchanged.
+ */
+ toMsg: function default_toMsg(value) {
+ return value;
+ },
+ /**
+ * Deserialize a message to a value of |this| |Type|.
+ *
+ * In the default implementation, the method returns the
+ * message unchanged.
+ */
+ fromMsg: function default_fromMsg(msg) {
+ return msg;
+ },
+ /**
+ * Import a value from C.
+ *
+ * In this default implementation, return the value
+ * unchanged.
+ */
+ importFromC: function default_importFromC(value) {
+ return value;
+ },
+
+ /**
+ * A pointer/array used to pass data to the foreign function.
+ */
+ get in_ptr() {
+ delete this.in_ptr;
+ let ptr_t = new PtrType(
+ "[in] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "in_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to receive data from the foreign function.
+ */
+ get out_ptr() {
+ delete this.out_ptr;
+ let ptr_t = new PtrType(
+ "[out] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "out_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * A pointer/array used to both pass data to the foreign function
+ * and receive data from the foreign function.
+ *
+ * Whenever possible, prefer using |in_ptr| or |out_ptr|, which
+ * are generally faster.
+ */
+ get inout_ptr() {
+ delete this.inout_ptr;
+ let ptr_t = new PtrType(
+ "[inout] " + this.name + "*",
+ this.implementation.ptr,
+ this);
+ Object.defineProperty(this, "inout_ptr",
+ {
+ get: function() {
+ return ptr_t;
+ }
+ });
+ return ptr_t;
+ },
+
+ /**
+ * Attach a finalizer to a type.
+ */
+ releaseWith: function releaseWith(finalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", " + finalizer + "] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ finalizer);
+ };
+ return type;
+ },
+
+ /**
+ * Lazy variant of releaseWith.
+ * Attach a finalizer lazily to a type.
+ *
+ * @param {function} getFinalizer The function that
+ * returns finalizer lazily.
+ */
+ releaseWithLazy: function releaseWithLazy(getFinalizer) {
+ let parent = this;
+ let type = this.withName("[auto " + this.name + ", (lazy)] ");
+ type.importFromC = function importFromC(value, operation) {
+ return ctypes.CDataFinalizer(
+ parent.importFromC(value, operation),
+ getFinalizer());
+ };
+ return type;
+ },
+
+ /**
+ * Return an alias to a type with a different name.
+ */
+ withName: function withName(name) {
+ return Object.create(this, {name: {value: name}});
+ },
+
+ /**
+ * Cast a C value to |this| type.
+ *
+ * Throw an error if the value cannot be casted.
+ */
+ cast: function cast(value) {
+ return ctypes.cast(value, this.implementation);
+ },
+
+ /**
+ * Return the number of bytes in a value of |this| type.
+ *
+ * This may not be defined, e.g. for |void_t|, array types
+ * without length, etc.
+ */
+ get size() {
+ return this.implementation.size;
+ }
+};
+
+/**
+ * Utility function used to determine whether an object is a typed array
+ */
+var isTypedArray = function isTypedArray(obj) {
+ return obj != null && typeof obj == "object"
+ && "byteOffset" in obj;
+};
+exports.isTypedArray = isTypedArray;
+
+/**
+ * Utility function used to determine whether an object is an ArrayBuffer.
+ */
+var isArrayBuffer = function(obj) {
+ return obj != null && typeof obj == "object" &&
+ obj.constructor.name == "ArrayBuffer";
+};
+exports.isArrayBuffer = isArrayBuffer;
+
+/**
+ * A |Type| of pointers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The type of this pointer.
+ * @param {Type} targetType The target type.
+ */
+function PtrType(name, implementation, targetType) {
+ Type.call(this, name, implementation);
+ if (targetType == null || !targetType instanceof Type) {
+ throw new TypeError("targetType must be an instance of Type");
+ }
+ /**
+ * The type of values targeted by this pointer type.
+ */
+ Object.defineProperty(this, "targetType", {
+ value: targetType
+ });
+}
+PtrType.prototype = Object.create(Type.prototype);
+
+/**
+ * Convert a value to a pointer.
+ *
+ * Protocol:
+ * - |null| returns |null|
+ * - a string returns |{string: value}|
+ * - a typed array returns |{ptr: address_of_buffer}|
+ * - a C array returns |{ptr: address_of_buffer}|
+ * everything else raises an error
+ */
+PtrType.prototype.toMsg = function ptr_toMsg(value) {
+ if (value == null) {
+ return null;
+ }
+ if (typeof value == "string") {
+ return { string: value };
+ }
+ if (isTypedArray(value)) {
+ // Automatically transfer typed arrays
+ return new Meta({data: value}, {transfers: [value.buffer]});
+ }
+ if (isArrayBuffer(value)) {
+ // Automatically transfer array buffers
+ return new Meta({data: value}, {transfers: [value]});
+ }
+ let normalized;
+ if ("addressOfElement" in value) { // C array
+ normalized = value.addressOfElement(0);
+ } else if ("isNull" in value) { // C pointer
+ normalized = value;
+ } else {
+ throw new TypeError("Value " + value +
+ " cannot be converted to a pointer");
+ }
+ let cast = Type.uintptr_t.cast(normalized);
+ return {ptr: cast.value.toString()};
+};
+
+/**
+ * Convert a message back to a pointer.
+ */
+PtrType.prototype.fromMsg = function ptr_fromMsg(msg) {
+ if (msg == null) {
+ return null;
+ }
+ if ("string" in msg) {
+ return msg.string;
+ }
+ if ("data" in msg) {
+ return msg.data;
+ }
+ if ("ptr" in msg) {
+ let address = ctypes.uintptr_t(msg.ptr);
+ return this.cast(address);
+ }
+ throw new TypeError("Message " + msg.toSource() +
+ " does not represent a pointer");
+};
+
+exports.Type = Type;
+
+
+/*
+ * Some values are large integers on 64 bit platforms. Unfortunately,
+ * in practice, 64 bit integers cannot be manipulated in JS. We
+ * therefore project them to regular numbers whenever possible.
+ */
+
+var projectLargeInt = function projectLargeInt(x) {
+ let str = x.toString();
+ let rv = parseInt(str, 10);
+ if (rv.toString() !== str) {
+ throw new TypeError("Number " + str + " cannot be projected to a double");
+ }
+ return rv;
+};
+var projectLargeUInt = function projectLargeUInt(x) {
+ return projectLargeInt(x);
+};
+var projectValue = function projectValue(x) {
+ if (!(x instanceof ctypes.CData)) {
+ return x;
+ }
+ if (!("value" in x)) { // Sanity check
+ throw new TypeError("Number " + x.toSource() + " has no field |value|");
+ }
+ return x.value;
+};
+
+function projector(type, signed) {
+ LOG("Determining best projection for", type,
+ "(size: ", type.size, ")", signed?"signed":"unsigned");
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (!type.size) {
+ throw new TypeError("Argument is not a proper C type");
+ }
+ // Determine if type is projected to Int64/Uint64
+ if (type.size == 8 // Usual case
+ // The following cases have special treatment in js-ctypes
+ // Regardless of their size, the value getter returns
+ // a Int64/Uint64
+ || type == ctypes.size_t // Special cases
+ || type == ctypes.ssize_t
+ || type == ctypes.intptr_t
+ || type == ctypes.uintptr_t
+ || type == ctypes.off_t) {
+ if (signed) {
+ LOG("Projected as a large signed integer");
+ return projectLargeInt;
+ } else {
+ LOG("Projected as a large unsigned integer");
+ return projectLargeUInt;
+ }
+ }
+ LOG("Projected as a regular number");
+ return projectValue;
+};
+exports.projectValue = projectValue;
+
+/**
+ * Get the appropriate type for an unsigned int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.uintn_t = function uintn_t(size) {
+ switch (size) {
+ case 1: return Type.uint8_t;
+ case 2: return Type.uint16_t;
+ case 4: return Type.uint32_t;
+ case 8: return Type.uint64_t;
+ default:
+ throw new Error("Cannot represent unsigned integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Get the appropriate type for an signed int of the given size.
+ *
+ * This function is useful to define types such as |mode_t| whose
+ * actual width depends on the OS/platform.
+ *
+ * @param {number} size The number of bytes requested.
+ */
+Type.intn_t = function intn_t(size) {
+ switch (size) {
+ case 1: return Type.int8_t;
+ case 2: return Type.int16_t;
+ case 4: return Type.int32_t;
+ case 8: return Type.int64_t;
+ default:
+ throw new Error("Cannot represent integers of " + size + " bytes");
+ }
+};
+
+/**
+ * Actual implementation of common C types.
+ */
+
+/**
+ * The void value.
+ */
+Type.void_t =
+ new Type("void",
+ ctypes.void_t);
+
+/**
+ * Shortcut for |void*|.
+ */
+Type.voidptr_t =
+ new PtrType("void*",
+ ctypes.voidptr_t,
+ Type.void_t);
+
+// void* is a special case as we can cast any pointer to/from it
+// so we have to shortcut |in_ptr|/|out_ptr|/|inout_ptr| and
+// ensure that js-ctypes' casting mechanism is invoked directly
+["in_ptr", "out_ptr", "inout_ptr"].forEach(function(key) {
+ Object.defineProperty(Type.void_t, key,
+ {
+ value: Type.voidptr_t
+ });
+});
+
+/**
+ * A Type of integers.
+ *
+ * @param {string} name The name of this type.
+ * @param {CType} implementation The underlying js-ctypes implementation.
+ * @param {bool} signed |true| if this is a type of signed integers,
+ * |false| otherwise.
+ *
+ * @constructor
+ */
+function IntType(name, implementation, signed) {
+ Type.call(this, name, implementation);
+ this.importFromC = projector(implementation, signed);
+ this.project = this.importFromC;
+};
+IntType.prototype = Object.create(Type.prototype);
+IntType.prototype.toMsg = function toMsg(value) {
+ if (typeof value == "number") {
+ return value;
+ }
+ return this.project(value);
+};
+
+/**
+ * A C char (one byte)
+ */
+Type.char =
+ new Type("char",
+ ctypes.char);
+
+/**
+ * A C wide char (two bytes)
+ */
+Type.char16_t =
+ new Type("char16_t",
+ ctypes.char16_t);
+
+ /**
+ * Base string types.
+ */
+Type.cstring = Type.char.in_ptr.withName("[in] C string");
+Type.wstring = Type.char16_t.in_ptr.withName("[in] wide string");
+Type.out_cstring = Type.char.out_ptr.withName("[out] C string");
+Type.out_wstring = Type.char16_t.out_ptr.withName("[out] wide string");
+
+/**
+ * A C integer (8-bits).
+ */
+Type.int8_t =
+ new IntType("int8_t", ctypes.int8_t, true);
+
+Type.uint8_t =
+ new IntType("uint8_t", ctypes.uint8_t, false);
+
+/**
+ * A C integer (16-bits).
+ *
+ * Also known as WORD under Windows.
+ */
+Type.int16_t =
+ new IntType("int16_t", ctypes.int16_t, true);
+
+Type.uint16_t =
+ new IntType("uint16_t", ctypes.uint16_t, false);
+
+/**
+ * A C integer (32-bits).
+ *
+ * Also known as DWORD under Windows.
+ */
+Type.int32_t =
+ new IntType("int32_t", ctypes.int32_t, true);
+
+Type.uint32_t =
+ new IntType("uint32_t", ctypes.uint32_t, false);
+
+/**
+ * A C integer (64-bits).
+ */
+Type.int64_t =
+ new IntType("int64_t", ctypes.int64_t, true);
+
+Type.uint64_t =
+ new IntType("uint64_t", ctypes.uint64_t, false);
+
+ /**
+ * A C integer
+ *
+ * Size depends on the platform.
+ */
+Type.int = Type.intn_t(ctypes.int.size).
+ withName("int");
+
+Type.unsigned_int = Type.intn_t(ctypes.unsigned_int.size).
+ withName("unsigned int");
+
+/**
+ * A C long integer.
+ *
+ * Size depends on the platform.
+ */
+Type.long =
+ Type.intn_t(ctypes.long.size).withName("long");
+
+Type.unsigned_long =
+ Type.intn_t(ctypes.unsigned_long.size).withName("unsigned long");
+
+/**
+ * An unsigned integer with the same size as a pointer.
+ *
+ * Used to cast a pointer to an integer, whenever necessary.
+ */
+Type.uintptr_t =
+ Type.uintn_t(ctypes.uintptr_t.size).withName("uintptr_t");
+
+/**
+ * A boolean.
+ * Implemented as a C integer.
+ */
+Type.bool = Type.int.withName("bool");
+Type.bool.importFromC = function projectBool(x) {
+ return !!(x.value);
+};
+
+/**
+ * A user identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.uid_t =
+ Type.int.withName("uid_t");
+
+/**
+ * A group identifier.
+ *
+ * Implemented as a C integer.
+ */
+Type.gid_t =
+ Type.int.withName("gid_t");
+
+/**
+ * An offset (positive or negative).
+ *
+ * Implemented as a C integer.
+ */
+Type.off_t =
+ new IntType("off_t", ctypes.off_t, true);
+
+/**
+ * A size (positive).
+ *
+ * Implemented as a C size_t.
+ */
+Type.size_t =
+ new IntType("size_t", ctypes.size_t, false);
+
+/**
+ * An offset (positive or negative).
+ * Implemented as a C integer.
+ */
+Type.ssize_t =
+ new IntType("ssize_t", ctypes.ssize_t, true);
+
+/**
+ * Encoding/decoding strings
+ */
+Type.uencoder =
+ new Type("uencoder", ctypes.StructType("uencoder"));
+Type.udecoder =
+ new Type("udecoder", ctypes.StructType("udecoder"));
+
+/**
+ * Utility class, used to build a |struct| type
+ * from a set of field names, types and offsets.
+ *
+ * @param {string} name The name of the |struct| type.
+ * @param {number} size The total size of the |struct| type in bytes.
+ */
+function HollowStructure(name, size) {
+ if (!name) {
+ throw new TypeError("HollowStructure expects a name");
+ }
+ if (!size || size < 0) {
+ throw new TypeError("HollowStructure expects a (positive) size");
+ }
+
+ // A mapping from offsets in the struct to name/type pairs
+ // (or nothing if no field starts at that offset).
+ this.offset_to_field_info = [];
+
+ // The name of the struct
+ this.name = name;
+
+ // The size of the struct, in bytes
+ this.size = size;
+
+ // The number of paddings inserted so far.
+ // Used to give distinct names to padding fields.
+ this._paddings = 0;
+}
+HollowStructure.prototype = {
+ /**
+ * Add a field at a given offset.
+ *
+ * @param {number} offset The offset at which to insert the field.
+ * @param {string} name The name of the field.
+ * @param {CType|Type} type The type of the field.
+ */
+ add_field_at: function add_field_at(offset, name, type) {
+ if (offset == null) {
+ throw new TypeError("add_field_at requires a non-null offset");
+ }
+ if (!name) {
+ throw new TypeError("add_field_at requires a non-null name");
+ }
+ if (!type) {
+ throw new TypeError("add_field_at requires a non-null type");
+ }
+ if (type instanceof Type) {
+ type = type.implementation;
+ }
+ if (this.offset_to_field_info[offset]) {
+ throw new Error("HollowStructure " + this.name +
+ " already has a field at offset " + offset);
+ }
+ if (offset + type.size > this.size) {
+ throw new Error("HollowStructure " + this.name +
+ " cannot place a value of type " + type +
+ " at offset " + offset +
+ " without exceeding its size of " + this.size);
+ }
+ let field = {name: name, type:type};
+ this.offset_to_field_info[offset] = field;
+ },
+
+ /**
+ * Create a pseudo-field that will only serve as padding.
+ *
+ * @param {number} size The number of bytes in the field.
+ * @return {Object} An association field-name => field-type,
+ * as expected by |ctypes.StructType|.
+ */
+ _makePaddingField: function makePaddingField(size) {
+ let field = ({});
+ field["padding_" + this._paddings] =
+ ctypes.ArrayType(ctypes.uint8_t, size);
+ this._paddings++;
+ return field;
+ },
+
+ /**
+ * Convert this |HollowStructure| into a |Type|.
+ */
+ getType: function getType() {
+ // Contents of the structure, in the format expected
+ // by ctypes.StructType.
+ let struct = [];
+
+ let i = 0;
+ while (i < this.size) {
+ let currentField = this.offset_to_field_info[i];
+ if (!currentField) {
+ // No field was specified at this offset, we need to
+ // introduce some padding.
+
+ // Firstly, determine how many bytes of padding
+ let padding_length = 1;
+ while (i + padding_length < this.size
+ && !this.offset_to_field_info[i + padding_length]) {
+ ++padding_length;
+ }
+
+ // Then add the padding
+ struct.push(this._makePaddingField(padding_length));
+
+ // And proceed
+ i += padding_length;
+ } else {
+ // We have a field at this offset.
+
+ // Firstly, ensure that we do not have two overlapping fields
+ for (let j = 1; j < currentField.type.size; ++j) {
+ let candidateField = this.offset_to_field_info[i + j];
+ if (candidateField) {
+ throw new Error("Fields " + currentField.name +
+ " and " + candidateField.name +
+ " overlap at position " + (i + j));
+ }
+ }
+
+ // Then add the field
+ let field = ({});
+ field[currentField.name] = currentField.type;
+ struct.push(field);
+
+ // And proceed
+ i += currentField.type.size;
+ }
+ }
+ let result = new Type(this.name, ctypes.StructType(this.name, struct));
+ if (result.implementation.size != this.size) {
+ throw new Error("Wrong size for type " + this.name +
+ ": expected " + this.size +
+ ", found " + result.implementation.size +
+ " (" + result.implementation.toSource() + ")");
+ }
+ return result;
+ }
+};
+exports.HollowStructure = HollowStructure;
+
+/**
+ * Representation of a native library.
+ *
+ * The native library is opened lazily, during the first call to its
+ * field |library| or whenever accessing one of the methods imported
+ * with declareLazyFFI.
+ *
+ * @param {string} name A human-readable name for the library. Used
+ * for debugging and error reporting.
+ * @param {string...} candidates A list of system libraries that may
+ * represent this library. Used e.g. to try different library names
+ * on distinct operating systems ("libxul", "XUL", etc.).
+ *
+ * @constructor
+ */
+function Library(name, ...candidates) {
+ this.name = name;
+ this._candidates = candidates;
+};
+Library.prototype = Object.freeze({
+ /**
+ * The native library as a js-ctypes object.
+ *
+ * @throws {Error} If none of the candidate libraries could be opened.
+ */
+ get library() {
+ let library;
+ delete this.library;
+ for (let candidate of this._candidates) {
+ try {
+ library = ctypes.open(candidate);
+ break;
+ } catch (ex) {
+ LOG("Could not open library", candidate, ex);
+ }
+ }
+ this._candidates = null;
+ if (library) {
+ Object.defineProperty(this, "library", {
+ value: library
+ });
+ Object.freeze(this);
+ return library;
+ }
+ let error = new Error("Could not open library " + this.name);
+ Object.defineProperty(this, "library", {
+ get: function() {
+ throw error;
+ }
+ });
+ Object.freeze(this);
+ throw error;
+ },
+
+ /**
+ * Declare a function, lazily.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+ declareLazyFFI: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(lib.library, ...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazy: function(object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ /**
+ * Define a js-ctypes function lazily using ctypes method declare,
+ * with a fallback library to use if this library can't be opened
+ * or the function cannot be declared.
+ *
+ * @param {fallbacklibrary} The fallback Library object.
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+ declareLazyWithFallback: function(fallbacklibrary, object, field, ...args) {
+ let lib = this;
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.library.declare(...args);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ } catch (ex) {
+ // Use the fallback library and get the symbol from there.
+ fallbacklibrary.declareLazy(object, field, ...args);
+ return object[field];
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+
+ toString: function() {
+ return "[Library " + this.name + "]";
+ }
+});
+exports.Library = Library;
+
+/**
+ * Declare a function through js-ctypes
+ *
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ *
+ * @return null if the function could not be defined (generally because
+ * it does not exist), or a JavaScript wrapper performing the call to C
+ * and any type conversion required.
+ */
+var declareFFI = function declareFFI(lib, symbol, abi,
+ returnType /*, argTypes ...*/) {
+ LOG("Attempting to declare FFI ", symbol);
+ // We guard agressively, to avoid any late surprise
+ if (typeof symbol != "string") {
+ throw new TypeError("declareFFI expects as first argument a string");
+ }
+ abi = abi || ctypes.default_abi;
+ if (Object.prototype.toString.call(abi) != "[object CABI]") {
+ // Note: This is the only known manner of checking whether an object
+ // is an abi.
+ throw new TypeError("declareFFI expects as second argument an abi or null");
+ }
+ if (!returnType.importFromC) {
+ throw new TypeError("declareFFI expects as third argument an instance of Type");
+ }
+ let signature = [symbol, abi];
+ let argtypes = [];
+ for (let i = 3; i < arguments.length; ++i) {
+ let current = arguments[i];
+ if (!current) {
+ throw new TypeError("Missing type for argument " + ( i - 3 ) +
+ " of symbol " + symbol);
+ }
+ if (!current.implementation) {
+ throw new TypeError("Missing implementation for argument " + (i - 3)
+ + " of symbol " + symbol
+ + " ( " + current.name + " )" );
+ }
+ signature.push(current.implementation);
+ }
+ try {
+ let fun = lib.declare.apply(lib, signature);
+ let result = function ffi(...args) {
+ for (let i = 0; i < args.length; i++) {
+ if (typeof args[i] == "undefined") {
+ throw new TypeError("Argument " + i + " of " + symbol + " is undefined");
+ }
+ }
+ let result = fun.apply(fun, args);
+ return returnType.importFromC(result, symbol);
+ };
+ LOG("Function", symbol, "declared");
+ return result;
+ } catch (x) {
+ // Note: Not being able to declare a function is normal.
+ // Some functions are OS (or OS version)-specific.
+ LOG("Could not declare function ", symbol, x);
+ return null;
+ }
+};
+exports.declareFFI = declareFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using declareFFI.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {Type} returnType The type of values returned by the function.
+ * @param {...Type} argTypes The type of arguments to the function.
+ */
+function declareLazyFFI(object, field, ...declareFFIArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ let ffi = declareFFI(...declareFFIArgs);
+ if (ffi) {
+ return this[field] = ffi;
+ }
+ return undefined;
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+exports.declareLazyFFI = declareLazyFFI;
+
+/**
+ * Define a lazy getter to a js-ctypes function using ctypes method declare.
+ *
+ * @param {object} The object containing the function as a field.
+ * @param {string} The name of the field containing the function.
+ * @param {ctypes.library} lib The ctypes library holding the function.
+ * @param {string} symbol The name of the function, as defined in the
+ * library.
+ * @param {ctypes.abi} abi The abi to use, or |null| for default.
+ * @param {ctypes.CType} returnType The type of values returned by the function.
+ * @param {...ctypes.CType} argTypes The type of arguments to the function.
+ */
+function declareLazy(object, field, lib, ...declareArgs) {
+ Object.defineProperty(object, field, {
+ get: function() {
+ delete this[field];
+ try {
+ let ffi = lib.declare(...declareArgs);
+ return this[field] = ffi;
+ } catch (ex) {
+ // The symbol doesn't exist
+ return undefined;
+ }
+ },
+ configurable: true
+ });
+}
+exports.declareLazy = declareLazy;
+
+/**
+ * Utility function used to sanity check buffer and length arguments. The
+ * buffer must be a Typed Array.
+ *
+ * @param {Typed array} candidate The buffer.
+ * @param {number} bytes The number of bytes that |candidate| should contain.
+ *
+ * @return number The bytes argument clamped to the length of the buffer.
+ */
+function normalizeBufferArgs(candidate, bytes) {
+ if (!candidate) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (!isTypedArray(candidate)) {
+ throw new TypeError("Expecting a Typed Array");
+ }
+ if (bytes == null) {
+ bytes = candidate.byteLength;
+ } else if (candidate.byteLength < bytes) {
+ throw new TypeError("Buffer is too short. I need at least " +
+ bytes +
+ " bytes but I have only " +
+ candidate.byteLength +
+ "bytes");
+ }
+ return bytes;
+};
+exports.normalizeBufferArgs = normalizeBufferArgs;
+
+///////////////////// OS interactions
+
+/**
+ * An OS error.
+ *
+ * This class is provided mostly for type-matching. If you need more
+ * details about an error, you should use the platform-specific error
+ * codes provided by subclasses of |OS.Shared.Error|.
+ *
+ * @param {string} operation The operation that failed.
+ * @param {string=} path The path of the file on which the operation failed,
+ * or nothing if there was no file involved in the failure.
+ *
+ * @constructor
+ */
+function OSError(operation, path = "") {
+ Error.call(this);
+ this.operation = operation;
+ this.path = path;
+}
+OSError.prototype = Object.create(Error.prototype);
+exports.OSError = OSError;
+
+
+///////////////////// Temporary boilerplate
+// Boilerplate, to simplify the transition to require()
+// Do not rely upon this symbol, it will disappear with
+// bug 883050.
+exports.OS = {
+ Constants: exports.Constants,
+ Shared: {
+ LOG: LOG,
+ clone: clone,
+ Type: Type,
+ HollowStructure: HollowStructure,
+ Error: OSError,
+ declareFFI: declareFFI,
+ projectValue: projectValue,
+ isTypedArray: isTypedArray,
+ defineLazyGetter: defineLazyGetter
+ }
+};
+
+Object.defineProperty(exports.OS.Shared, "DEBUG", {
+ get: function() {
+ return Config.DEBUG;
+ },
+ set: function(x) {
+ return Config.DEBUG = x;
+ }
+});
+Object.defineProperty(exports.OS.Shared, "TEST", {
+ get: function() {
+ return Config.TEST;
+ },
+ set: function(x) {
+ return Config.TEST = x;
+ }
+});
+
+
+///////////////////// Permanent boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/toolkit/components/osfile/modules/osfile_shared_front.jsm b/toolkit/components/osfile/modules/osfile_shared_front.jsm
new file mode 100644
index 000000000..a2971991d
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -0,0 +1,567 @@
+/* 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.
+ */
+
+if (typeof Components != "undefined") {
+ throw new Error("osfile_shared_front.jsm 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");
+var LOG = 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;
+ } else {
+ 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: 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 ...
+ }
+ }
+ throw OS.File.Error.exists("could not find an unused file name.", path);
+ }
+};
+
+/**
+ * Code shared by iterators.
+ */
+AbstractFile.AbstractIterator = function AbstractIterator() {
+};
+AbstractFile.AbstractIterator.prototype = {
+ /**
+ * Allow iterating with |for|
+ */
+ __iterator__: function __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 in 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 in 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");
+ }
+ 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
+ let encoding = options.encoding || "utf-8";
+ buffer = new TextEncoder(encoding).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.
+ }
+ }
+ // 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.
+ }
+ }
+
+ 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 in 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);
diff --git a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
new file mode 100644
index 000000000..632f9c40b
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm
@@ -0,0 +1,375 @@
+/* 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/. */
+
+/**
+ * This module defines the thread-agnostic components of the Unix version
+ * of OS.File. It depends on the thread-agnostic cross-platform components
+ * of OS.File.
+ *
+ * It serves the following purposes:
+ * - open libc;
+ * - define OS.Unix.Error;
+ * - define a few constants and types that need to be defined on all platforms.
+ *
+ * This module can be:
+ * - opened from the main thread as a jsm module;
+ * - opened from a chrome worker through require().
+ */
+
+"use strict";
+
+var SharedAll;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ // Module is opened as a jsm module
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ // Module is loaded with require()
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+} else {
+ throw new Error("Please open this module with Component.utils.import or with require()");
+}
+
+var LOG = SharedAll.LOG.bind(SharedAll, "Unix", "allthreads");
+var Const = SharedAll.Constants.libc;
+
+// Open libc
+var libc = new SharedAll.Library("libc",
+ "libc.so", "libSystem.B.dylib", "a.out");
+exports.libc = libc;
+
+// Define declareFFI
+var declareFFI = SharedAll.declareFFI.bind(null, libc);
+exports.declareFFI = declareFFI;
+
+// Define lazy binding
+var LazyBindings = {};
+libc.declareLazy(LazyBindings, "strerror",
+ "strerror", ctypes.default_abi,
+ /*return*/ ctypes.char.ptr,
+ /*errnum*/ ctypes.int);
+
+/**
+ * A File-related error.
+ *
+ * To obtain a human-readable error message, use method |toString|.
+ * To determine the cause of the error, use the various |becauseX|
+ * getters. To determine the operation that failed, use field
+ * |operation|.
+ *
+ * Additionally, this implementation offers a field
+ * |unixErrno|, which holds the OS-specific error
+ * constant. If you need this level of detail, you may match the value
+ * of this field against the error constants of |OS.Constants.libc|.
+ *
+ * @param {string=} operation The operation that failed. If unspecified,
+ * the name of the calling function is taken to be the operation that
+ * failed.
+ * @param {number=} lastError The OS-specific constant detailing the
+ * reason of the error. If unspecified, this is fetched from the system
+ * status.
+ * @param {string=} path The file path that manipulated. If unspecified,
+ * assign the empty string.
+ *
+ * @constructor
+ * @extends {OS.Shared.Error}
+ */
+var OSError = function OSError(operation = "unknown operation",
+ errno = ctypes.errno, path = "") {
+ SharedAll.OSError.call(this, operation, path);
+ this.unixErrno = errno;
+};
+OSError.prototype = Object.create(SharedAll.OSError.prototype);
+OSError.prototype.toString = function toString() {
+ return "Unix error " + this.unixErrno +
+ " during operation " + this.operation +
+ (this.path? " on file " + this.path : "") +
+ " (" + LazyBindings.strerror(this.unixErrno).readString() + ")";
+};
+OSError.prototype.toMsg = function toMsg() {
+ return OSError.toMsg(this);
+};
+
+/**
+ * |true| if the error was raised because a file or directory
+ * already exists, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseExists", {
+ get: function becauseExists() {
+ return this.unixErrno == Const.EEXIST;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNoSuchFile", {
+ get: function becauseNoSuchFile() {
+ return this.unixErrno == Const.ENOENT;
+ }
+});
+
+/**
+ * |true| if the error was raised because a directory is not empty
+ * does not exist, |false| otherwise.
+ */
+ Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
+ get: function becauseNotEmpty() {
+ return this.unixErrno == Const.ENOTEMPTY;
+ }
+ });
+/**
+ * |true| if the error was raised because a file or directory
+ * is closed, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseClosed", {
+ get: function becauseClosed() {
+ return this.unixErrno == Const.EBADF;
+ }
+});
+/**
+ * |true| if the error was raised because permission is denied to
+ * access a file or directory, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
+ get: function becauseAccessDenied() {
+ return this.unixErrno == Const.EACCES;
+ }
+});
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+ get: function becauseInvalidArgument() {
+ return this.unixErrno == Const.EINVAL;
+ }
+});
+
+/**
+ * Serialize an instance of OSError to something that can be
+ * transmitted across threads (not necessarily a string).
+ */
+OSError.toMsg = function toMsg(error) {
+ return {
+ exn: "OS.File.Error",
+ fileName: error.moduleName,
+ lineNumber: error.lineNumber,
+ stack: error.moduleStack,
+ operation: error.operation,
+ unixErrno: error.unixErrno,
+ path: error.path
+ };
+};
+
+/**
+ * Deserialize a message back to an instance of OSError
+ */
+OSError.fromMsg = function fromMsg(msg) {
+ let error = new OSError(msg.operation, msg.unixErrno, msg.path);
+ error.stack = msg.stack;
+ error.fileName = msg.fileName;
+ error.lineNumber = msg.lineNumber;
+ return error;
+};
+exports.Error = OSError;
+
+/**
+ * Code shared by implementations of File.Info on Unix
+ *
+ * @constructor
+*/
+var AbstractInfo = function AbstractInfo(path, isDir, isSymLink, size, lastAccessDate,
+ lastModificationDate, unixLastStatusChangeDate,
+ unixOwner, unixGroup, unixMode) {
+ this._path = path;
+ this._isDir = isDir;
+ this._isSymlLink = isSymLink;
+ this._size = size;
+ this._lastAccessDate = lastAccessDate;
+ this._lastModificationDate = lastModificationDate;
+ this._unixLastStatusChangeDate = unixLastStatusChangeDate;
+ this._unixOwner = unixOwner;
+ this._unixGroup = unixGroup;
+ this._unixMode = unixMode;
+};
+
+AbstractInfo.prototype = {
+ /**
+ * The path of the file, used for error-reporting.
+ *
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ },
+ /**
+ * |true| if this file is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if this file is a symbolink link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymlLink;
+ },
+ /**
+ * The size of the file, in bytes.
+ *
+ * Note that the result may be |NaN| if the size of the file cannot be
+ * represented in JavaScript.
+ *
+ * @type {number}
+ */
+ get size() {
+ return this._size;
+ },
+ /**
+ * The date of last access to this file.
+ *
+ * Note that the definition of last access may depend on the
+ * underlying operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastAccessDate() {
+ return this._lastAccessDate;
+ },
+ /**
+ * Return the date of last modification of this file.
+ */
+ get lastModificationDate() {
+ return this._lastModificationDate;
+ },
+ /**
+ * Return the date at which the status of this file was last modified
+ * (this is the date of the latest write/renaming/mode change/...
+ * of the file)
+ */
+ get unixLastStatusChangeDate() {
+ return this._unixLastStatusChangeDate;
+ },
+ /*
+ * Return the Unix owner of this file
+ */
+ get unixOwner() {
+ return this._unixOwner;
+ },
+ /*
+ * Return the Unix group of this file
+ */
+ get unixGroup() {
+ return this._unixGroup;
+ },
+ /*
+ * Return the Unix group of this file
+ */
+ get unixMode() {
+ return this._unixMode;
+ }
+};
+exports.AbstractInfo = AbstractInfo;
+
+/**
+ * Code shared by implementations of File.DirectoryIterator.Entry on Unix
+ *
+ * @constructor
+*/
+var AbstractEntry = function AbstractEntry(isDir, isSymLink, name, path) {
+ this._isDir = isDir;
+ this._isSymlLink = isSymLink;
+ this._name = name;
+ this._path = path;
+};
+
+AbstractEntry.prototype = {
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymlLink;
+ },
+ /**
+ * The name of the entry
+ * @type {string}
+ */
+ get name() {
+ return this._name;
+ },
+ /**
+ * The full path to the entry
+ */
+ get path() {
+ return this._path;
+ }
+};
+exports.AbstractEntry = AbstractEntry;
+
+// Special constants that need to be defined on all platforms
+
+exports.POS_START = Const.SEEK_SET;
+exports.POS_CURRENT = Const.SEEK_CUR;
+exports.POS_END = Const.SEEK_END;
+
+// Special types that need to be defined for communication
+// between threads
+var Type = Object.create(SharedAll.Type);
+exports.Type = Type;
+
+/**
+ * Native paths
+ *
+ * Under Unix, expressed as C strings
+ */
+Type.path = Type.cstring.withName("[in] path");
+Type.out_path = Type.out_cstring.withName("[out] path");
+
+// Special constructors that need to be defined on all threads
+OSError.closed = function closed(operation, path) {
+ return new OSError(operation, Const.EBADF, path);
+};
+
+OSError.exists = function exists(operation, path) {
+ return new OSError(operation, Const.EEXIST, path);
+};
+
+OSError.noSuchFile = function noSuchFile(operation, path) {
+ return new OSError(operation, Const.ENOENT, path);
+};
+
+OSError.invalidArgument = function invalidArgument(operation) {
+ return new OSError(operation, Const.EINVAL);
+};
+
+var EXPORTED_SYMBOLS = [
+ "declareFFI",
+ "libc",
+ "Error",
+ "AbstractInfo",
+ "AbstractEntry",
+ "Type",
+ "POS_START",
+ "POS_CURRENT",
+ "POS_END"
+];
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/toolkit/components/osfile/modules/osfile_unix_back.jsm b/toolkit/components/osfile/modules/osfile_unix_back.jsm
new file mode 100644
index 000000000..a028dda7d
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_unix_back.jsm
@@ -0,0 +1,735 @@
+/* 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/. */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_unix_back.jsm to be used directly as a main thread
+ // module yet. When time comes, it will be loaded by a combination of
+ // a main thread front-end/worker thread implementation that makes sure
+ // that we are not executing synchronous IO code in the main thread.
+
+ throw new Error("osfile_unix_back.jsm cannot be used from the main thread yet");
+ }
+ (function(exports) {
+ "use strict";
+ if (exports.OS && exports.OS.Unix && exports.OS.Unix.File) {
+ return; // Avoid double initialization
+ }
+
+ let SharedAll =
+ require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let SysAll =
+ require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
+ let libc = SysAll.libc;
+ let Const = SharedAll.Constants.libc;
+
+ /**
+ * Initialize the Unix module.
+ *
+ * @param {function=} declareFFI
+ */
+ // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
+ let init = function init(aDeclareFFI) {
+ let declareFFI;
+ if (aDeclareFFI) {
+ declareFFI = aDeclareFFI.bind(null, libc);
+ } else {
+ declareFFI = SysAll.declareFFI;
+ }
+ let declareLazyFFI = SharedAll.declareLazyFFI;
+
+ // Initialize types that require additional OS-specific
+ // support - either finalization or matching against
+ // OS-specific constants.
+ let Type = Object.create(SysAll.Type);
+ let SysFile = exports.OS.Unix.File = { Type: Type };
+
+ /**
+ * A file descriptor.
+ */
+ Type.fd = Type.int.withName("fd");
+ Type.fd.importFromC = function importFromC(fd_int) {
+ return ctypes.CDataFinalizer(fd_int, SysFile._close);
+ };
+
+
+ /**
+ * A C integer holding -1 in case of error or a file descriptor
+ * in case of success.
+ */
+ Type.negativeone_or_fd = Type.fd.withName("negativeone_or_fd");
+ Type.negativeone_or_fd.importFromC =
+ function importFromC(fd_int) {
+ if (fd_int == -1) {
+ return -1;
+ }
+ return ctypes.CDataFinalizer(fd_int, SysFile._close);
+ };
+
+ /**
+ * A C integer holding -1 in case of error or a meaningless value
+ * in case of success.
+ */
+ Type.negativeone_or_nothing =
+ Type.int.withName("negativeone_or_nothing");
+
+ /**
+ * A C integer holding -1 in case of error or a positive integer
+ * in case of success.
+ */
+ Type.negativeone_or_ssize_t =
+ Type.ssize_t.withName("negativeone_or_ssize_t");
+
+ /**
+ * Various libc integer types
+ */
+ Type.mode_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_MODE_T).withName("mode_t");
+ Type.uid_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_UID_T).withName("uid_t");
+ Type.gid_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_GID_T).withName("gid_t");
+
+ /**
+ * Type |time_t|
+ */
+ Type.time_t =
+ Type.intn_t(Const.OSFILE_SIZEOF_TIME_T).withName("time_t");
+
+ // Structure |dirent|
+ // Building this type is rather complicated, as its layout varies between
+ // variants of Unix. For this reason, we rely on a number of constants
+ // (computed in C from the C data structures) that give us the layout.
+ // The structure we compute looks like
+ // { int8_t[...] before_d_type; // ignored content
+ // int8_t d_type ;
+ // int8_t[...] before_d_name; // ignored content
+ // char[...] d_name;
+ // };
+ {
+ let d_name_extra_size = 0;
+ if (Const.OSFILE_SIZEOF_DIRENT_D_NAME < 8) {
+ // d_name is defined like "char d_name[1];" on some platforms
+ // (e.g. Solaris), we need to give it more size for our structure.
+ d_name_extra_size = 256;
+ }
+
+ let dirent = new SharedAll.HollowStructure("dirent",
+ Const.OSFILE_SIZEOF_DIRENT + d_name_extra_size);
+ if (Const.OSFILE_OFFSETOF_DIRENT_D_TYPE != undefined) {
+ // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
+ dirent.add_field_at(Const.OSFILE_OFFSETOF_DIRENT_D_TYPE,
+ "d_type", ctypes.uint8_t);
+ }
+ dirent.add_field_at(Const.OSFILE_OFFSETOF_DIRENT_D_NAME,
+ "d_name", ctypes.ArrayType(ctypes.char,
+ Const.OSFILE_SIZEOF_DIRENT_D_NAME + d_name_extra_size));
+
+ // We now have built |dirent|.
+ Type.dirent = dirent.getType();
+ }
+ Type.null_or_dirent_ptr =
+ new SharedAll.Type("null_of_dirent",
+ Type.dirent.out_ptr.implementation);
+
+ // Structure |stat|
+ // Same technique
+ {
+ let stat = new SharedAll.HollowStructure("stat",
+ Const.OSFILE_SIZEOF_STAT);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_MODE,
+ "st_mode", Type.mode_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_UID,
+ "st_uid", Type.uid_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_GID,
+ "st_gid", Type.gid_t.implementation);
+
+ // Here, things get complicated with different data structures.
+ // Some platforms have |time_t st_atime| and some platforms have
+ // |timespec st_atimespec|. However, since |timespec| starts with
+ // a |time_t|, followed by nanoseconds, we just cheat and pretend
+ // that everybody has |time_t st_atime|, possibly followed by padding
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_ATIME,
+ "st_atime", Type.time_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_MTIME,
+ "st_mtime", Type.time_t.implementation);
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_CTIME,
+ "st_ctime", Type.time_t.implementation);
+
+ // To complicate further, MacOS and some BSDs have a field |birthtime|
+ if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_BIRTHTIME,
+ "st_birthtime", Type.time_t.implementation);
+ }
+
+ stat.add_field_at(Const.OSFILE_OFFSETOF_STAT_ST_SIZE,
+ "st_size", Type.off_t.implementation);
+ Type.stat = stat.getType();
+ }
+
+ // Structure |DIR|
+ if ("OSFILE_SIZEOF_DIR" in Const) {
+ // On platforms for which we need to access the fields of DIR
+ // directly (e.g. because certain functions are implemented
+ // as macros), we need to define DIR as a hollow structure.
+ let DIR = new SharedAll.HollowStructure(
+ "DIR",
+ Const.OSFILE_SIZEOF_DIR);
+
+ DIR.add_field_at(
+ Const.OSFILE_OFFSETOF_DIR_DD_FD,
+ "dd_fd",
+ Type.fd.implementation);
+
+ Type.DIR = DIR.getType();
+ } else {
+ // On other platforms, we keep DIR as a blackbox
+ Type.DIR =
+ new SharedAll.Type("DIR",
+ ctypes.StructType("DIR"));
+ }
+
+ Type.null_or_DIR_ptr =
+ Type.DIR.out_ptr.withName("null_or_DIR*");
+ Type.null_or_DIR_ptr.importFromC = function importFromC(dir) {
+ if (dir == null || dir.isNull()) {
+ return null;
+ }
+ return ctypes.CDataFinalizer(dir, SysFile._close_dir);
+ };
+
+ // Structure |timeval|
+ {
+ let timeval = new SharedAll.HollowStructure(
+ "timeval",
+ Const.OSFILE_SIZEOF_TIMEVAL);
+ timeval.add_field_at(
+ Const.OSFILE_OFFSETOF_TIMEVAL_TV_SEC,
+ "tv_sec",
+ Type.long.implementation);
+ timeval.add_field_at(
+ Const.OSFILE_OFFSETOF_TIMEVAL_TV_USEC,
+ "tv_usec",
+ Type.long.implementation);
+ Type.timeval = timeval.getType();
+ Type.timevals = new SharedAll.Type("two timevals",
+ ctypes.ArrayType(Type.timeval.implementation, 2));
+ }
+
+ // Types fsblkcnt_t and fsfilcnt_t, used by structure |statvfs|
+ Type.fsblkcnt_t =
+ Type.uintn_t(Const.OSFILE_SIZEOF_FSBLKCNT_T).withName("fsblkcnt_t");
+
+ // Structure |statvfs|
+ // Use an hollow structure
+ {
+ let statvfs = new SharedAll.HollowStructure("statvfs",
+ Const.OSFILE_SIZEOF_STATVFS);
+
+ statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BSIZE,
+ "f_bsize", Type.unsigned_long.implementation);
+ statvfs.add_field_at(Const.OSFILE_OFFSETOF_STATVFS_F_BAVAIL,
+ "f_bavail", Type.fsblkcnt_t.implementation);
+
+ Type.statvfs = statvfs.getType();
+ }
+
+ // Declare libc functions as functions of |OS.Unix.File|
+
+ // Finalizer-related functions
+ libc.declareLazy(SysFile, "_close",
+ "close", ctypes.default_abi,
+ /*return */ctypes.int,
+ /*fd*/ ctypes.int);
+
+ SysFile.close = function close(fd) {
+ // Detach the finalizer and call |_close|.
+ return fd.dispose();
+ };
+
+ libc.declareLazy(SysFile, "_close_dir",
+ "closedir", ctypes.default_abi,
+ /*return */ctypes.int,
+ /*dirp*/ Type.DIR.in_ptr.implementation);
+
+ SysFile.closedir = function closedir(fd) {
+ // Detach the finalizer and call |_close_dir|.
+ return fd.dispose();
+ };
+
+ {
+ // Symbol free() is special.
+ // We override the definition of free() on several platforms.
+ let default_lib = new SharedAll.Library("default_lib",
+ "a.out");
+
+ // On platforms for which we override free(), nspr defines
+ // a special library name "a.out" that will resolve to the
+ // correct implementation free().
+ // If it turns out we don't have an a.out library or a.out
+ // doesn't contain free, use the ordinary libc free.
+
+ default_lib.declareLazyWithFallback(libc, SysFile, "free",
+ "free", ctypes.default_abi,
+ /*return*/ ctypes.void_t,
+ /*ptr*/ ctypes.voidptr_t);
+ }
+
+
+ // Other functions
+ libc.declareLazyFFI(SysFile, "access",
+ "access", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*mode*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "chdir",
+ "chdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "chmod",
+ "chmod", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*mode*/ Type.mode_t);
+
+ libc.declareLazyFFI(SysFile, "chown",
+ "chown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*uid*/ Type.uid_t,
+ /*gid*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "copyfile",
+ "copyfile", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path,
+ /*state*/ Type.void_t.in_ptr, // Ignored atm
+ /*flags*/ Type.uint32_t);
+
+ libc.declareLazyFFI(SysFile, "dup",
+ "dup", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*fd*/ Type.fd);
+
+ if ("OSFILE_SIZEOF_DIR" in Const) {
+ // On platforms for which |dirfd| is a macro
+ SysFile.dirfd =
+ function dirfd(DIRp) {
+ return Type.DIR.in_ptr.implementation(DIRp).contents.dd_fd;
+ };
+ } else {
+ // On platforms for which |dirfd| is a function
+ libc.declareLazyFFI(SysFile, "dirfd",
+ "dirfd", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*dir*/ Type.DIR.in_ptr);
+ }
+
+ libc.declareLazyFFI(SysFile, "chdir",
+ "chdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "fchdir",
+ "fchdir", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd);
+
+ libc.declareLazyFFI(SysFile, "fchmod",
+ "fchmod", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*mode*/ Type.mode_t);
+
+ libc.declareLazyFFI(SysFile, "fchown",
+ "fchown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*uid_t*/ Type.uid_t,
+ /*gid_t*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "fsync",
+ "fsync", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd);
+
+ libc.declareLazyFFI(SysFile, "getcwd",
+ "getcwd", ctypes.default_abi,
+ /*return*/ Type.out_path,
+ /*buf*/ Type.out_path,
+ /*size*/ Type.size_t);
+
+ libc.declareLazyFFI(SysFile, "getwd",
+ "getwd", ctypes.default_abi,
+ /*return*/ Type.out_path,
+ /*buf*/ Type.out_path);
+
+ // Two variants of |getwd| which allocate the memory
+ // dynamically.
+
+ // Linux/Android version
+ libc.declareLazyFFI(SysFile, "get_current_dir_name",
+ "get_current_dir_name", ctypes.default_abi,
+ /*return*/ Type.out_path.releaseWithLazy(() =>
+ SysFile.free
+ ));
+
+ // MacOS/BSD version (will return NULL on Linux/Android)
+ libc.declareLazyFFI(SysFile, "getwd_auto",
+ "getwd", ctypes.default_abi,
+ /*return*/ Type.out_path.releaseWithLazy(() =>
+ SysFile.free
+ ),
+ /*buf*/ Type.void_t.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "fdatasync",
+ "fdatasync", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd); // Note: MacOS/BSD-specific
+
+ libc.declareLazyFFI(SysFile, "ftruncate",
+ "ftruncate", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*length*/ Type.off_t);
+
+
+ libc.declareLazyFFI(SysFile, "lchown",
+ "lchown", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*uid_t*/ Type.uid_t,
+ /*gid_t*/ Type.gid_t);
+
+ libc.declareLazyFFI(SysFile, "link",
+ "link", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "lseek",
+ "lseek", ctypes.default_abi,
+ /*return*/ Type.off_t,
+ /*fd*/ Type.fd,
+ /*offset*/ Type.off_t,
+ /*whence*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "mkdir",
+ "mkdir", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*path*/ Type.path,
+ /*mode*/ Type.int);
+
+ libc.declareLazyFFI(SysFile, "mkstemp",
+ "mkstemp", ctypes.default_abi,
+ /*return*/ Type.fd,
+ /*template*/ Type.out_path);
+
+ libc.declareLazyFFI(SysFile, "open",
+ "open", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_fd,
+ /*path*/ Type.path,
+ /*oflags*/ Type.int,
+ /*mode*/ Type.int);
+
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "opendir",
+ "__opendir30", ctypes.default_abi,
+ /*return*/ Type.null_or_DIR_ptr,
+ /*path*/ Type.path);
+ } else {
+ libc.declareLazyFFI(SysFile, "opendir",
+ "opendir", ctypes.default_abi,
+ /*return*/ Type.null_or_DIR_ptr,
+ /*path*/ Type.path);
+ }
+
+ libc.declareLazyFFI(SysFile, "pread",
+ "pread", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.out_ptr,
+ /*nbytes*/ Type.size_t,
+ /*offset*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "pwrite",
+ "pwrite", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.in_ptr,
+ /*nbytes*/ Type.size_t,
+ /*offset*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "read",
+ "read", ctypes.default_abi,
+ /*return*/Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.out_ptr,
+ /*nbytes*/Type.size_t);
+
+ libc.declareLazyFFI(SysFile, "posix_fadvise",
+ "posix_fadvise", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*fd*/ Type.fd,
+ /*offset*/ Type.off_t,
+ /*len*/ Type.off_t,
+ /*advise*/ Type.int);
+
+ if (Const._DARWIN_FEATURE_64_BIT_INODE) {
+ // Special case for MacOS X 10.5+
+ // Symbol name "readdir" still exists but is used for a
+ // deprecated function that does not match the
+ // constants of |Const|.
+ libc.declareLazyFFI(SysFile, "readdir",
+ "readdir$INODE64", ctypes.default_abi,
+ /*return*/ Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // For MacOS X
+ } else if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "readdir",
+ "__readdir30", ctypes.default_abi,
+ /*return*/Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // Other Unices
+ } else {
+ libc.declareLazyFFI(SysFile, "readdir",
+ "readdir", ctypes.default_abi,
+ /*return*/Type.null_or_dirent_ptr,
+ /*dir*/ Type.DIR.in_ptr); // Other Unices
+ }
+
+ libc.declareLazyFFI(SysFile, "rename",
+ "rename", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*old*/ Type.path,
+ /*new*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "rmdir",
+ "rmdir", ctypes.default_abi,
+ /*return*/ Type.int,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "splice",
+ "splice", ctypes.default_abi,
+ /*return*/ Type.long,
+ /*fd_in*/ Type.fd,
+ /*off_in*/ Type.off_t.in_ptr,
+ /*fd_out*/ Type.fd,
+ /*off_out*/Type.off_t.in_ptr,
+ /*len*/ Type.size_t,
+ /*flags*/ Type.unsigned_int); // Linux/Android-specific
+
+ libc.declareLazyFFI(SysFile, "statfs",
+ "statfs", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.statvfs.out_ptr); // Android,B2G
+
+ libc.declareLazyFFI(SysFile, "statvfs",
+ "statvfs", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.statvfs.out_ptr); // Other platforms
+
+ libc.declareLazyFFI(SysFile, "symlink",
+ "symlink", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*source*/ Type.path,
+ /*dest*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "truncate",
+ "truncate", ctypes.default_abi,
+ /*return*/Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*length*/ Type.off_t);
+
+ libc.declareLazyFFI(SysFile, "unlink",
+ "unlink", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "write",
+ "write", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_ssize_t,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.void_t.in_ptr,
+ /*nbytes*/ Type.size_t);
+
+ // Weird cases that require special treatment
+
+ // OSes use a variety of hacks to differentiate between
+ // 32-bits and 64-bits versions of |stat|, |lstat|, |fstat|.
+ if (Const._DARWIN_FEATURE_64_BIT_INODE) {
+ // MacOS X 64-bits
+ libc.declareLazyFFI(SysFile, "stat",
+ "stat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "lstat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "fstat$INODE64", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ } else if (Const._STAT_VER != undefined) {
+ const ver = Const._STAT_VER;
+ let xstat_name, lxstat_name, fxstat_name;
+ if (OS.Constants.Sys.Name == "SunOS") {
+ // Solaris
+ xstat_name = "_xstat";
+ lxstat_name = "_lxstat";
+ fxstat_name = "_fxstat";
+ } else {
+ // Linux, all widths
+ xstat_name = "__xstat";
+ lxstat_name = "__lxstat";
+ fxstat_name = "__fxstat";
+ }
+
+ let Stat = {};
+ libc.declareLazyFFI(Stat, "xstat",
+ xstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr);
+ libc.declareLazyFFI(Stat, "lxstat",
+ lxstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr);
+ libc.declareLazyFFI(Stat, "fxstat",
+ fxstat_name, ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*_stat_ver*/ Type.int,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr);
+
+
+ SysFile.stat = function stat(path, buf) {
+ return Stat.xstat(ver, path, buf);
+ };
+
+ SysFile.lstat = function lstat(path, buf) {
+ return Stat.lxstat(ver, path, buf);
+ };
+
+ SysFile.fstat = function fstat(fd, buf) {
+ return Stat.fxstat(ver, fd, buf);
+ };
+ } else if (OS.Constants.Sys.Name == "NetBSD") {
+ // NetBSD 5.0 and newer
+ libc.declareLazyFFI(SysFile, "stat",
+ "__stat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "__lstat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "__fstat50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ } else {
+ // Mac OS X 32-bits, other Unix
+ libc.declareLazyFFI(SysFile, "stat",
+ "stat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "lstat",
+ "lstat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*buf*/ Type.stat.out_ptr
+ );
+ libc.declareLazyFFI(SysFile, "fstat",
+ "fstat", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*buf*/ Type.stat.out_ptr
+ );
+ }
+
+ // We cannot make a C array of CDataFinalizer, so
+ // pipe cannot be directly defined as a C function.
+
+ let Pipe = {};
+ libc.declareLazyFFI(Pipe, "_pipe",
+ "pipe", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fds*/ new SharedAll.Type("two file descriptors",
+ ctypes.ArrayType(ctypes.int, 2)));
+
+ // A shared per-thread buffer used to communicate with |pipe|
+ let _pipebuf = new (ctypes.ArrayType(ctypes.int, 2))();
+
+ SysFile.pipe = function pipe(array) {
+ let result = Pipe._pipe(_pipebuf);
+ if (result == -1) {
+ return result;
+ }
+ array[0] = ctypes.CDataFinalizer(_pipebuf[0], SysFile._close);
+ array[1] = ctypes.CDataFinalizer(_pipebuf[1], SysFile._close);
+ return result;
+ };
+
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "utimes",
+ "__utimes50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ } else {
+ libc.declareLazyFFI(SysFile, "utimes",
+ "utimes", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*path*/ Type.path,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ }
+ if (OS.Constants.Sys.Name == "NetBSD") {
+ libc.declareLazyFFI(SysFile, "futimes",
+ "__futimes50", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ } else {
+ libc.declareLazyFFI(SysFile, "futimes",
+ "futimes", ctypes.default_abi,
+ /*return*/ Type.negativeone_or_nothing,
+ /*fd*/ Type.fd,
+ /*timeval[2]*/ Type.timevals.out_ptr
+ );
+ }
+ };
+
+ exports.OS.Unix = {
+ File: {
+ _init: init
+ }
+ };
+ })(this);
+}
diff --git a/toolkit/components/osfile/modules/osfile_unix_front.jsm b/toolkit/components/osfile/modules/osfile_unix_front.jsm
new file mode 100644
index 000000000..19a27ae1a
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -0,0 +1,1193 @@
+/* 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/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Unix implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_unix_front.jsm to be used directly as a main thread
+ // module yet.
+
+ throw new Error("osfile_unix_front.jsm cannot be used from the main thread yet");
+ }
+ (function(exports) {
+ "use strict";
+
+ // exports.OS.Unix is created by osfile_unix_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_unix_allthreads.jsm");
+ exports.OS.Unix.File._init();
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix front-end");
+ let Const = SharedAll.Constants.libc;
+ let UnixFile = exports.OS.Unix.File;
+ let Type = UnixFile.Type;
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = UnixFile._close(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.errno, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options = {}) {
+ // Populate the page cache with data from a file so the subsequent reads
+ // from that file will not block on disk I/O.
+ if (typeof(UnixFile.posix_fadvise) === 'function' &&
+ (options.sequential || !("sequential" in options))) {
+ UnixFile.posix_fadvise(this.fd, 0, nbytes,
+ OS.Constants.libc.POSIX_FADV_SEQUENTIAL);
+ }
+ return throw_on_negative("read",
+ UnixFile.read(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options = {}) {
+ return throw_on_negative("write",
+ UnixFile.write(this.fd, buffer, nbytes),
+ this._path
+ );
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.SEEK_SET;
+ }
+ return throw_on_negative("setPosition",
+ UnixFile.lseek(this.fd, pos, whence),
+ this._path
+ );
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_negative("stat", UnixFile.fstat(this.fd, gStatDataPtr),
+ this._path);
+ return new File.Info(gStatData, this._path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.fchmod(this.fd, unixMode(options)),
+ this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * WARNING: This method is not implemented on Android/B2G. On Android/B2G,
+ * you should use File.setDates instead.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ if (SharedAll.Constants.Sys.Name != "Android") {
+ File.prototype.setDates = function(accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.futimes(this.fd, ptr),
+ this._path);
+ };
+ }
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_negative("flush", UnixFile.fsync(this.fd), this._path);
+ };
+
+ // The default unix mode for opening (0600)
+ const DEFAULT_UNIX_MODE = 384;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixFlags If specified, file opening flags, as
+ * per libc function |open|. Replaces |mode|.
+ * - {number} unixMode If specified, a file creation mode,
+ * as per libc function |open|. If unspecified, files are
+ * created with a default mode of 0600 (file is private to the
+ * user, the user can read and write).
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Unix_open(path, mode, options = {}) {
+ // We don't need to filter for the umask because "open" does this for us.
+ let omode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let flags;
+ if (options.unixFlags !== undefined) {
+ flags = options.unixFlags;
+ } else {
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+ // Handle read/write
+ if (!mode.write) {
+ flags = Const.O_RDONLY;
+ } else if (mode.read) {
+ flags = Const.O_RDWR;
+ } else {
+ flags = Const.O_WRONLY;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ flags |= Const.O_TRUNC;
+ } else {
+ flags |= Const.O_CREAT | Const.O_TRUNC;
+ }
+ } else if (mode.create) {
+ flags |= Const.O_CREAT | Const.O_EXCL;
+ } else if (mode.read && !mode.write) {
+ // flags are sufficient
+ } else if (!mode.existing) {
+ flags |= Const.O_CREAT;
+ }
+ if (mode.append) {
+ flags |= Const.O_APPEND;
+ }
+ }
+ return error_or_file(UnixFile.open(path, flags, omode), path);
+ };
+
+ /**
+ * Checks if a file exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Unix_exists(path) {
+ if (UnixFile.access(path, Const.F_OK) == -1) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ let result = UnixFile.unlink(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("remove", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = UnixFile.rmdir(path);
+ if (result == -1) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Unix_getAvailableFreeSpace(sourcePath) {
+ let fileSystemInfo = new Type.statvfs.implementation();
+ let fileSystemInfoPtr = fileSystemInfo.address();
+
+ throw_on_negative("statvfs", (UnixFile.statvfs || UnixFile.statfs)(sourcePath, fileSystemInfoPtr));
+
+ let bytes = new Type.uint64_t.implementation(
+ fileSystemInfo.f_bsize * fileSystemInfo.f_bavail);
+
+ return bytes.value;
+ };
+
+ /**
+ * Default mode for opening directories.
+ */
+ const DEFAULT_UNIX_MODE_DIR = Const.S_IRWXU;
+
+ /**
+ * Create a directory.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {number} unixMode 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).
+ * - {bool} ignoreExisting If |false|, throw error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let omode = options.unixMode !== undefined ? options.unixMode : DEFAULT_UNIX_MODE_DIR;
+ let result = UnixFile.mkdir(path, omode);
+ if (result == -1) {
+ if ((!("ignoreExisting" in options) || options.ignoreExisting) &&
+ (ctypes.errno == Const.EEXIST || ctypes.errno == Const.EISDIR)) {
+ return;
+ }
+ throw new File.Error("makeDir", ctypes.errno, path);
+ }
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = null;
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same device.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = null;
+
+ if (UnixFile.copyfile) {
+ // This implementation uses |copyfile(3)|, from the BSD library.
+ // Adding copying of hierarchies and/or attributes is just a flag
+ // away.
+ File.copy = function copyfile(sourcePath, destPath, options = {}) {
+ let flags = Const.COPYFILE_DATA;
+ if (options.noOverwrite) {
+ flags |= Const.COPYFILE_EXCL;
+ }
+ throw_on_negative("copy",
+ UnixFile.copyfile(sourcePath, destPath, null, flags),
+ sourcePath
+ );
+ };
+ } else {
+ // If the OS does not implement file copying for us, we need to
+ // implement it ourselves. For this purpose, we need to define
+ // a pumping function.
+
+ /**
+ * Copy bytes from one file to another one.
+ *
+ * @param {File} source The file containing the data to be copied. It
+ * should be opened for reading.
+ * @param {File} dest The file to which the data should be written. It
+ * should be opened for writing.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {number} nbytes The maximal number of bytes to
+ * copy. If unspecified, copy everything from the current
+ * position.
+ * @option {number} bufSize A hint regarding the size of the
+ * buffer to use for copying. The implementation may decide to
+ * ignore this hint.
+ * @option {bool} unixUserland Will force the copy operation to be
+ * caried out in user land, instead of using optimized syscalls such
+ * as splice(2).
+ *
+ * @throws {OS.File.Error} In case of error.
+ */
+ let pump;
+
+ // A buffer used by |pump_userland|
+ let pump_buffer = null;
+
+ // An implementation of |pump| using |read|/|write|
+ let pump_userland = function pump_userland(source, dest, options = {}) {
+ let bufSize = options.bufSize > 0 ? options.bufSize : 4096;
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ if (!pump_buffer || pump_buffer.length < bufSize) {
+ pump_buffer = new (ctypes.ArrayType(ctypes.char))(bufSize);
+ }
+ let read = source._read.bind(source);
+ let write = dest._write.bind(dest);
+ // Perform actual copy
+ let total_read = 0;
+ while (true) {
+ let chunk_size = Math.min(nbytes, bufSize);
+ let bytes_just_read = read(pump_buffer, bufSize);
+ if (bytes_just_read == 0) {
+ return total_read;
+ }
+ total_read += bytes_just_read;
+ let bytes_written = 0;
+ do {
+ bytes_written += write(
+ pump_buffer.addressOfElement(bytes_written),
+ bytes_just_read - bytes_written
+ );
+ } while (bytes_written < bytes_just_read);
+ nbytes -= bytes_written;
+ if (nbytes <= 0) {
+ return total_read;
+ }
+ }
+ };
+
+ // Fortunately, under Linux, that pumping function can be optimized.
+ if (UnixFile.splice) {
+ const BUFSIZE = 1 << 17;
+
+ // An implementation of |pump| using |splice| (for Linux/Android)
+ pump = function pump_splice(source, dest, options = {}) {
+ let nbytes = options.nbytes > 0 ? options.nbytes : Infinity;
+ let pipe = [];
+ throw_on_negative("pump", UnixFile.pipe(pipe));
+ let pipe_read = pipe[0];
+ let pipe_write = pipe[1];
+ let source_fd = source.fd;
+ let dest_fd = dest.fd;
+ let total_read = 0;
+ let total_written = 0;
+ try {
+ while (true) {
+ let chunk_size = Math.min(nbytes, BUFSIZE);
+ let bytes_read = throw_on_negative("pump",
+ UnixFile.splice(source_fd, null,
+ pipe_write, null, chunk_size, 0)
+ );
+ if (!bytes_read) {
+ break;
+ }
+ total_read += bytes_read;
+ let bytes_written = throw_on_negative(
+ "pump",
+ UnixFile.splice(pipe_read, null,
+ dest_fd, null, bytes_read,
+ (bytes_read == chunk_size)?Const.SPLICE_F_MORE:0
+ ));
+ if (!bytes_written) {
+ // This should never happen
+ throw new Error("Internal error: pipe disconnected");
+ }
+ total_written += bytes_written;
+ nbytes -= bytes_read;
+ if (!nbytes) {
+ break;
+ }
+ }
+ return total_written;
+ } catch (x) {
+ if (x.unixErrno == Const.EINVAL) {
+ // We *might* be on a file system that does not support splice.
+ // Try again with a fallback pump.
+ if (total_read) {
+ source.setPosition(-total_read, File.POS_CURRENT);
+ }
+ if (total_written) {
+ dest.setPosition(-total_written, File.POS_CURRENT);
+ }
+ return pump_userland(source, dest, options);
+ }
+ throw x;
+ } finally {
+ pipe_read.dispose();
+ pipe_write.dispose();
+ }
+ };
+ } else {
+ // Fallback implementation of pump for other Unix platforms.
+ pump = pump_userland;
+ }
+
+ // Implement |copy| using |pump|.
+ // This implementation would require some work before being able to
+ // copy directories
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ let source, dest;
+ let result;
+ try {
+ source = File.open(sourcePath);
+ // Need to open the output file with |append:false|, or else |splice|
+ // won't work.
+ if (options.noOverwrite) {
+ dest = File.open(destPath, {create:true, append:false});
+ } else {
+ dest = File.open(destPath, {trunc:true, append:false});
+ }
+ if (options.unixUserland) {
+ result = pump_userland(source, dest, options);
+ } else {
+ result = pump(source, dest, options);
+ }
+ } catch (x) {
+ if (dest) {
+ dest.close();
+ }
+ if (source) {
+ source.close();
+ }
+ throw x;
+ }
+ };
+ } // End of definition of copy
+
+ // Implement |move| using |rename| (wherever possible) or |copy|
+ // (if files are on distinct devices).
+ File.move = function move(sourcePath, destPath, options = {}) {
+ // An implementation using |rename| whenever possible or
+ // |File.pump| when required, for other Unices.
+ // It can move directories on one file system, not
+ // across file systems
+
+ // If necessary, fail if the destination file exists
+ if (options.noOverwrite) {
+ let fd = UnixFile.open(destPath, Const.O_RDONLY, 0);
+ if (fd != -1) {
+ fd.dispose();
+ // The file exists and we have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ } else if (ctypes.errno == Const.EACCESS) {
+ // The file exists and we don't have access
+ throw new File.Error("move", Const.EEXIST, sourcePath);
+ }
+ }
+
+ // If we can, rename the file
+ let result = UnixFile.rename(sourcePath, destPath);
+ if (result != -1)
+ return;
+
+ // If the error is not EXDEV ("not on the same device"),
+ // or if the error is EXDEV and we have passed an option
+ // that prevents us from crossing devices, throw the
+ // error.
+ if (ctypes.errno != Const.EXDEV || options.noCopy) {
+ throw new File.Error("move", ctypes.errno, sourcePath);
+ }
+
+ // Otherwise, copy and remove.
+ File.copy(sourcePath, destPath, options);
+ // FIXME: Clean-up in case of copy error?
+ File.remove(sourcePath);
+ };
+
+ File.unixSymLink = function unixSymLink(sourcePath, destPath) {
+ throw_on_negative("symlink", UnixFile.symlink(sourcePath, destPath),
+ sourcePath);
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options Ignored in this implementation.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ this._path = path;
+ this._dir = UnixFile.opendir(this._path);
+ if (this._dir == null) {
+ let error = ctypes.errno;
+ if (error != Const.ENOENT) {
+ throw new File.Error("DirectoryIterator", error, path);
+ }
+ this._exists = false;
+ this._closed = true;
+ } else {
+ this._exists = true;
+ this._closed = false;
+ }
+ };
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ if (this._closed) {
+ throw StopIteration;
+ }
+ for (let entry = UnixFile.readdir(this._dir);
+ entry != null && !entry.isNull();
+ entry = UnixFile.readdir(this._dir)) {
+ let contents = entry.contents;
+ let name = contents.d_name.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+
+ let isDir, isSymLink;
+ if (!("d_type" in contents)) {
+ // |dirent| doesn't have d_type on some platforms (e.g. Solaris).
+ let path = Path.join(this._path, name);
+ throw_on_negative("lstat", UnixFile.lstat(path, gStatDataPtr), this._path);
+ isDir = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ isSymLink = (gStatData.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ } else {
+ isDir = contents.d_type == Const.DT_DIR;
+ isSymLink = contents.d_type == Const.DT_LNK;
+ }
+
+ return new File.DirectoryIterator.Entry(isDir, isSymLink, name, this._path);
+ }
+ this.close();
+ throw StopIteration;
+ };
+
+ /**
+ * Close the iterator and recover all resources.
+ * You should call this once you have finished iterating on a directory.
+ */
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) return;
+ this._closed = true;
+ UnixFile.closedir(this._dir);
+ this._dir = null;
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ /**
+ * Return directory as |File|
+ */
+ File.DirectoryIterator.prototype.unixAsFile = function unixAsFile() {
+ if (!this._dir) throw File.Error.closed("unixAsFile", this._path);
+ return error_or_file(UnixFile.dirfd(this._dir), this._path);
+ };
+
+ /**
+ * An entry in a directory.
+ */
+ File.DirectoryIterator.Entry = function Entry(isDir, isSymLink, name, parent) {
+ // Copy the relevant part of |unix_entry| to ensure that
+ // our data is not overwritten prematurely.
+ this._parent = parent;
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!value instanceof File.DirectoryIterator.Entry) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+ let gStatData = new Type.stat.implementation();
+ let gStatDataPtr = gStatData.address();
+
+ let MODE_MASK = 4095 /*= 07777*/;
+ File.Info = function Info(stat, path) {
+ let isDir = (stat.st_mode & Const.S_IFMT) == Const.S_IFDIR;
+ let isSymLink = (stat.st_mode & Const.S_IFMT) == Const.S_IFLNK;
+ let size = Type.off_t.importFromC(stat.st_size);
+
+ let lastAccessDate = new Date(stat.st_atime * 1000);
+ let lastModificationDate = new Date(stat.st_mtime * 1000);
+ let unixLastStatusChangeDate = new Date(stat.st_ctime * 1000);
+
+ let unixOwner = Type.uid_t.importFromC(stat.st_uid);
+ let unixGroup = Type.gid_t.importFromC(stat.st_gid);
+ let unixMode = Type.mode_t.importFromC(stat.st_mode & MODE_MASK);
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ lastAccessDate, lastModificationDate, unixLastStatusChangeDate,
+ unixOwner, unixGroup, unixMode);
+
+ // Some platforms (e.g. MacOS X, some BSDs) store a file creation date
+ if ("OSFILE_OFFSETOF_STAT_ST_BIRTHTIME" in Const) {
+ let date = new Date(stat.st_birthtime * 1000);
+
+ /**
+ * The date of creation of this file.
+ *
+ * Note that the date returned by this method is not always
+ * reliable. Not all file systems are able to provide this
+ * information.
+ *
+ * @type {Date}
+ */
+ this.macBirthDate = date;
+ }
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ // Deprecated, use macBirthDate/winBirthDate instead
+ Object.defineProperty(File.Info.prototype, "creationDate", {
+ get: function creationDate() {
+ // On the Macintosh, returns the birth date if available.
+ // On other Unix, as the birth date is not available,
+ // returns the epoch.
+ return this.macBirthDate || new Date(0);
+ }
+ });
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!stat instanceof File.Info) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+ /**
+ * Fetch the information on a file.
+ *
+ * @param {string} path The full name of the file to open.
+ * @param {*=} options Additional options. In this implementation:
+ *
+ * - {bool} unixNoFollowingLinks If set and |true|, if |path|
+ * represents a symbolic link, the call will return the information
+ * of the link itself, rather than that of the target file.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path, options = {}) {
+ if (options.unixNoFollowingLinks) {
+ throw_on_negative("stat", UnixFile.lstat(path, gStatDataPtr), path);
+ } else {
+ throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr), path);
+ }
+ return new File.Info(gStatData, path);
+ };
+
+ /**
+ * Set the file's access permissions.
+ *
+ * This operation is likely to fail if applied to a file that was
+ * not created by the currently running program (more precisely,
+ * if it was created by a program running under a different OS-level
+ * user account). It may also fail, or silently do nothing, if the
+ * filesystem containing the file does not support access permissions.
+ *
+ * @param {string} path The name of the file to reset the permissions of.
+ * @param {*=} options Object specifying the requested permissions:
+ *
+ * - {number} unixMode The POSIX file mode to set on the file. If omitted,
+ * the POSIX file mode is reset to the default used by |OS.file.open|. If
+ * specified, the permissions will respect the process umask as if they
+ * had been specified as arguments of |OS.File.open|, unless the
+ * |unixHonorUmask| parameter tells otherwise.
+ * - {bool} unixHonorUmask If omitted or true, any |unixMode| value is
+ * modified by the process umask, as |OS.File.open| would have done. If
+ * false, the exact value of |unixMode| will be applied.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ throw_on_negative("setPermissions",
+ UnixFile.chmod(path, unixMode(options)),
+ path);
+ };
+
+ /**
+ * Convert an access date and a modification date to an array
+ * of two |timeval|.
+ */
+ function datesToTimevals(accessDate, modificationDate) {
+ accessDate = normalizeDate("File.setDates", accessDate);
+ modificationDate = normalizeDate("File.setDates", modificationDate);
+
+ let timevals = new Type.timevals.implementation();
+ let timevalsPtr = timevals.address();
+
+ timevals[0].tv_sec = (accessDate / 1000) | 0;
+ timevals[0].tv_usec = 0;
+ timevals[1].tv_sec = (modificationDate / 1000) | 0;
+ timevals[1].tv_usec = 0;
+
+ return { value: timevals, ptr: timevalsPtr };
+ }
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let {value, ptr} = datesToTimevals(accessDate, modificationDate);
+ throw_on_negative("setDates",
+ UnixFile.utimes(path, ptr),
+ path);
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ *
+ * Note: This function will remove a symlink even if it points a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ let isSymLink;
+ try {
+ let info = File.stat(path, {unixNoFollowingLinks: true});
+ isSymLink = info.isSymLink;
+ } catch (e) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.errno == Const.ENOENT) {
+ return;
+ }
+ throw e;
+ }
+ if (isSymLink) {
+ // A Unix symlink itself is not a directory even if it points
+ // a directory.
+ File.remove(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ let path, buf;
+ if (UnixFile.get_current_dir_name) {
+ path = UnixFile.get_current_dir_name();
+ } else if (UnixFile.getwd_auto) {
+ path = UnixFile.getwd_auto(null);
+ } else {
+ for (let length = Const.PATH_MAX; !path; length *= 2) {
+ buf = new (ctypes.char.array(length));
+ path = UnixFile.getcwd(buf, length);
+ };
+ }
+ throw_on_null("getCurrentDirectory", path);
+ return path.readString();
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_negative("setCurrentDirectory",
+ UnixFile.chdir(path),
+ path
+ );
+ };
+
+ /**
+ * Get/set the current directory.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == -1) {
+ throw new File.Error("open", ctypes.errno, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.errno, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.errno, path);
+ }
+ return result;
+ }
+
+ /**
+ * Normalize and verify a Date or numeric date value.
+ *
+ * @param {string} fn Function name of the calling function.
+ * @param {Date,number} date The date to normalize. If omitted or null,
+ * then the current date will be used.
+ *
+ * @throws {TypeError} Invalid date provided.
+ *
+ * @return {number} Sanitized, numeric date in milliseconds since epoch.
+ */
+ function normalizeDate(fn, date) {
+ if (typeof date !== "number" && !date) {
+ // |date| was Omitted or null.
+ date = Date.now();
+ } else if (typeof date.getTime === "function") {
+ // Input might be a date or date-like object.
+ date = date.getTime();
+ }
+
+ if (typeof date !== "number" || Number.isNaN(date)) {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ return date;
+ };
+
+ /**
+ * Helper used by both versions of setPermissions.
+ */
+ function unixMode(options) {
+ let mode = options.unixMode !== undefined ?
+ options.unixMode : DEFAULT_UNIX_MODE;
+ let unixHonorUmask = true;
+ if ("unixHonorUmask" in options) {
+ unixHonorUmask = options.unixHonorUmask;
+ }
+ if (unixHonorUmask) {
+ mode &= ~SharedAll.Constants.Sys.umask;
+ }
+ return mode;
+ }
+
+ File.Unix = exports.OS.Unix.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}
diff --git a/toolkit/components/osfile/modules/osfile_win_allthreads.jsm b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
new file mode 100644
index 000000000..b059d4e12
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm
@@ -0,0 +1,425 @@
+/* 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/. */
+
+/**
+ * This module defines the thread-agnostic components of the Win version
+ * of OS.File. It depends on the thread-agnostic cross-platform components
+ * of OS.File.
+ *
+ * It serves the following purposes:
+ * - open kernel32;
+ * - define OS.Shared.Win.Error;
+ * - define a few constants and types that need to be defined on all platforms.
+ *
+ * This module can be:
+ * - opened from the main thread as a jsm module;
+ * - opened from a chrome worker through require().
+ */
+
+"use strict";
+
+var SharedAll;
+if (typeof Components != "undefined") {
+ let Cu = Components.utils;
+ // Module is opened as a jsm module
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+ SharedAll = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", SharedAll);
+ this.exports = {};
+} else if (typeof module != "undefined" && typeof require != "undefined") {
+ // Module is loaded with require()
+ SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+} else {
+ throw new Error("Please open this module with Component.utils.import or with require()");
+}
+
+var LOG = SharedAll.LOG.bind(SharedAll, "Win", "allthreads");
+var Const = SharedAll.Constants.Win;
+
+// Open libc
+var libc = new SharedAll.Library("libc", "kernel32.dll");
+exports.libc = libc;
+
+// Define declareFFI
+var declareFFI = SharedAll.declareFFI.bind(null, libc);
+exports.declareFFI = declareFFI;
+
+var Scope = {};
+
+// Define Error
+libc.declareLazy(Scope, "FormatMessage",
+ "FormatMessageW", ctypes.winapi_abi,
+ /*return*/ ctypes.uint32_t,
+ /*flags*/ ctypes.uint32_t,
+ /*source*/ ctypes.voidptr_t,
+ /*msgid*/ ctypes.uint32_t,
+ /*langid*/ ctypes.uint32_t,
+ /*buf*/ ctypes.char16_t.ptr,
+ /*size*/ ctypes.uint32_t,
+ /*Arguments*/ctypes.voidptr_t);
+
+/**
+ * A File-related error.
+ *
+ * To obtain a human-readable error message, use method |toString|.
+ * To determine the cause of the error, use the various |becauseX|
+ * getters. To determine the operation that failed, use field
+ * |operation|.
+ *
+ * Additionally, this implementation offers a field
+ * |winLastError|, which holds the OS-specific error
+ * constant. If you need this level of detail, you may match the value
+ * of this field against the error constants of |OS.Constants.Win|.
+ *
+ * @param {string=} operation The operation that failed. If unspecified,
+ * the name of the calling function is taken to be the operation that
+ * failed.
+ * @param {number=} lastError The OS-specific constant detailing the
+ * reason of the error. If unspecified, this is fetched from the system
+ * status.
+ * @param {string=} path The file path that manipulated. If unspecified,
+ * assign the empty string.
+ *
+ * @constructor
+ * @extends {OS.Shared.Error}
+ */
+var OSError = function OSError(operation = "unknown operation",
+ lastError = ctypes.winLastError, path = "") {
+ operation = operation;
+ SharedAll.OSError.call(this, operation, path);
+ this.winLastError = lastError;
+};
+OSError.prototype = Object.create(SharedAll.OSError.prototype);
+OSError.prototype.toString = function toString() {
+ let buf = new (ctypes.ArrayType(ctypes.char16_t, 1024))();
+ let result = Scope.FormatMessage(
+ Const.FORMAT_MESSAGE_FROM_SYSTEM |
+ Const.FORMAT_MESSAGE_IGNORE_INSERTS,
+ null,
+ /* The error number */ this.winLastError,
+ /* Default language */ 0,
+ /* Output buffer*/ buf,
+ /* Minimum size of buffer */ 1024,
+ /* Format args*/ null
+ );
+ if (!result) {
+ buf = "additional error " +
+ ctypes.winLastError +
+ " while fetching system error message";
+ }
+ return "Win error " + this.winLastError + " during operation "
+ + this.operation + (this.path? " on file " + this.path : "") +
+ " (" + buf.readString() + ")";
+};
+OSError.prototype.toMsg = function toMsg() {
+ return OSError.toMsg(this);
+};
+
+/**
+ * |true| if the error was raised because a file or directory
+ * already exists, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseExists", {
+ get: function becauseExists() {
+ return this.winLastError == Const.ERROR_FILE_EXISTS ||
+ this.winLastError == Const.ERROR_ALREADY_EXISTS;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNoSuchFile", {
+ get: function becauseNoSuchFile() {
+ return this.winLastError == Const.ERROR_FILE_NOT_FOUND ||
+ this.winLastError == Const.ERROR_PATH_NOT_FOUND;
+ }
+});
+/**
+ * |true| if the error was raised because a directory is not empty
+ * does not exist, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseNotEmpty", {
+ get: function becauseNotEmpty() {
+ return this.winLastError == Const.ERROR_DIR_NOT_EMPTY;
+ }
+});
+/**
+ * |true| if the error was raised because a file or directory
+ * is closed, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseClosed", {
+ get: function becauseClosed() {
+ return this.winLastError == Const.ERROR_INVALID_HANDLE;
+ }
+});
+/**
+ * |true| if the error was raised because permission is denied to
+ * access a file or directory, |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseAccessDenied", {
+ get: function becauseAccessDenied() {
+ return this.winLastError == Const.ERROR_ACCESS_DENIED;
+ }
+});
+/**
+ * |true| if the error was raised because some invalid argument was passed,
+ * |false| otherwise.
+ */
+Object.defineProperty(OSError.prototype, "becauseInvalidArgument", {
+ get: function becauseInvalidArgument() {
+ return this.winLastError == Const.ERROR_NOT_SUPPORTED ||
+ this.winLastError == Const.ERROR_BAD_ARGUMENTS;
+ }
+});
+
+/**
+ * Serialize an instance of OSError to something that can be
+ * transmitted across threads (not necessarily a string).
+ */
+OSError.toMsg = function toMsg(error) {
+ return {
+ exn: "OS.File.Error",
+ fileName: error.moduleName,
+ lineNumber: error.lineNumber,
+ stack: error.moduleStack,
+ operation: error.operation,
+ winLastError: error.winLastError,
+ path: error.path
+ };
+};
+
+/**
+ * Deserialize a message back to an instance of OSError
+ */
+OSError.fromMsg = function fromMsg(msg) {
+ let error = new OSError(msg.operation, msg.winLastError, msg.path);
+ error.stack = msg.stack;
+ error.fileName = msg.fileName;
+ error.lineNumber = msg.lineNumber;
+ return error;
+};
+exports.Error = OSError;
+
+/**
+ * Code shared by implementation of File.Info on Windows
+ *
+ * @constructor
+ */
+var AbstractInfo = function AbstractInfo(path, isDir, isSymLink, size,
+ winBirthDate,
+ lastAccessDate, lastWriteDate,
+ winAttributes) {
+ this._path = path;
+ this._isDir = isDir;
+ this._isSymLink = isSymLink;
+ this._size = size;
+ this._winBirthDate = winBirthDate;
+ this._lastAccessDate = lastAccessDate;
+ this._lastModificationDate = lastWriteDate;
+ this._winAttributes = winAttributes;
+};
+
+AbstractInfo.prototype = {
+ /**
+ * The path of the file, used for error-reporting.
+ *
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ },
+ /**
+ * |true| if this file is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if this file is a symbolic link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymLink;
+ },
+ /**
+ * The size of the file, in bytes.
+ *
+ * Note that the result may be |NaN| if the size of the file cannot be
+ * represented in JavaScript.
+ *
+ * @type {number}
+ */
+ get size() {
+ return this._size;
+ },
+ // Deprecated
+ get creationDate() {
+ return this._winBirthDate;
+ },
+ /**
+ * The date of creation of this file.
+ *
+ * @type {Date}
+ */
+ get winBirthDate() {
+ return this._winBirthDate;
+ },
+ /**
+ * The date of last access to this file.
+ *
+ * Note that the definition of last access may depend on the underlying
+ * operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastAccessDate() {
+ return this._lastAccessDate;
+ },
+ /**
+ * The date of last modification of this file.
+ *
+ * Note that the definition of last access may depend on the underlying
+ * operating system and file system.
+ *
+ * @type {Date}
+ */
+ get lastModificationDate() {
+ return this._lastModificationDate;
+ },
+ /**
+ * The Object with following boolean properties of this file.
+ * {readOnly, system, hidden}
+ *
+ * @type {object}
+ */
+ get winAttributes() {
+ return this._winAttributes;
+ }
+};
+exports.AbstractInfo = AbstractInfo;
+
+/**
+ * Code shared by implementation of File.DirectoryIterator.Entry on Windows
+ *
+ * @constructor
+ */
+var AbstractEntry = function AbstractEntry(isDir, isSymLink, name,
+ winCreationDate, winLastWriteDate,
+ winLastAccessDate, path) {
+ this._isDir = isDir;
+ this._isSymLink = isSymLink;
+ this._name = name;
+ this._winCreationDate = winCreationDate;
+ this._winLastWriteDate = winLastWriteDate;
+ this._winLastAccessDate = winLastAccessDate;
+ this._path = path;
+};
+
+AbstractEntry.prototype = {
+ /**
+ * |true| if the entry is a directory, |false| otherwise
+ */
+ get isDir() {
+ return this._isDir;
+ },
+ /**
+ * |true| if the entry is a symbolic link, |false| otherwise
+ */
+ get isSymLink() {
+ return this._isSymLink;
+ },
+ /**
+ * The name of the entry.
+ * @type {string}
+ */
+ get name() {
+ return this._name;
+ },
+ /**
+ * The creation time of this file.
+ * @type {Date}
+ */
+ get winCreationDate() {
+ return this._winCreationDate;
+ },
+ /**
+ * The last modification time of this file.
+ * @type {Date}
+ */
+ get winLastWriteDate() {
+ return this._winLastWriteDate;
+ },
+ /**
+ * The last access time of this file.
+ * @type {Date}
+ */
+ get winLastAccessDate() {
+ return this._winLastAccessDate;
+ },
+ /**
+ * The full path of the entry
+ * @type {string}
+ */
+ get path() {
+ return this._path;
+ }
+};
+exports.AbstractEntry = AbstractEntry;
+
+// Special constants that need to be defined on all platforms
+
+exports.POS_START = Const.FILE_BEGIN;
+exports.POS_CURRENT = Const.FILE_CURRENT;
+exports.POS_END = Const.FILE_END;
+
+// Special types that need to be defined for communication
+// between threads
+var Type = Object.create(SharedAll.Type);
+exports.Type = Type;
+
+/**
+ * Native paths
+ *
+ * Under Windows, expressed as wide strings
+ */
+Type.path = Type.wstring.withName("[in] path");
+Type.out_path = Type.out_wstring.withName("[out] path");
+
+// Special constructors that need to be defined on all threads
+OSError.closed = function closed(operation, path) {
+ return new OSError(operation, Const.ERROR_INVALID_HANDLE, path);
+};
+
+OSError.exists = function exists(operation, path) {
+ return new OSError(operation, Const.ERROR_FILE_EXISTS, path);
+};
+
+OSError.noSuchFile = function noSuchFile(operation, path) {
+ return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path);
+};
+
+OSError.invalidArgument = function invalidArgument(operation) {
+ return new OSError(operation, Const.ERROR_NOT_SUPPORTED);
+};
+
+var EXPORTED_SYMBOLS = [
+ "declareFFI",
+ "libc",
+ "Error",
+ "AbstractInfo",
+ "AbstractEntry",
+ "Type",
+ "POS_START",
+ "POS_CURRENT",
+ "POS_END"
+];
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/toolkit/components/osfile/modules/osfile_win_back.jsm b/toolkit/components/osfile/modules/osfile_win_back.jsm
new file mode 100644
index 000000000..21172b6bf
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_win_back.jsm
@@ -0,0 +1,437 @@
+/* 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/. */
+
+/**
+ * This file can be used in the following contexts:
+ *
+ * 1. included from a non-osfile worker thread using importScript
+ * (it serves to define a synchronous API for that worker thread)
+ * (bug 707681)
+ *
+ * 2. included from the main thread using Components.utils.import
+ * (it serves to define the asynchronous API, whose implementation
+ * resides in the worker thread)
+ * (bug 729057)
+ *
+ * 3. included from the osfile worker thread using importScript
+ * (it serves to define the implementation of the asynchronous API)
+ * (bug 729057)
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_win.jsm to be used directly as a main thread
+ // module yet. When time comes, it will be loaded by a combination of
+ // a main thread front-end/worker thread implementation that makes sure
+ // that we are not executing synchronous IO code in the main thread.
+
+ throw new Error("osfile_win.jsm cannot be used from the main thread yet");
+ }
+
+ (function(exports) {
+ "use strict";
+ if (exports.OS && exports.OS.Win && exports.OS.Win.File) {
+ return; // Avoid double initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
+ let LOG = SharedAll.LOG.bind(SharedAll, "Unix", "back");
+ let libc = SysAll.libc;
+ let advapi32 = new SharedAll.Library("advapi32", "advapi32.dll");
+ let Const = SharedAll.Constants.Win;
+
+ /**
+ * Initialize the Windows module.
+ *
+ * @param {function=} declareFFI
+ */
+ // FIXME: Both |init| and |aDeclareFFI| are deprecated, we should remove them
+ let init = function init(aDeclareFFI) {
+ let declareFFI;
+ if (aDeclareFFI) {
+ declareFFI = aDeclareFFI.bind(null, libc);
+ } else {
+ declareFFI = SysAll.declareFFI;
+ }
+ let declareLazyFFI = SharedAll.declareLazyFFI;
+
+ // Initialize types that require additional OS-specific
+ // support - either finalization or matching against
+ // OS-specific constants.
+ let Type = Object.create(SysAll.Type);
+ let SysFile = exports.OS.Win.File = { Type: Type };
+
+ // Initialize types
+
+ /**
+ * A C integer holding INVALID_HANDLE_VALUE in case of error or
+ * a file descriptor in case of success.
+ */
+ Type.HANDLE =
+ Type.voidptr_t.withName("HANDLE");
+ Type.HANDLE.importFromC = function importFromC(maybe) {
+ if (Type.int.cast(maybe).value == INVALID_HANDLE) {
+ // Ensure that API clients can effectively compare against
+ // Const.INVALID_HANDLE_VALUE. Without this cast,
+ // == would always return |false|.
+ return INVALID_HANDLE;
+ }
+ return ctypes.CDataFinalizer(maybe, this.finalizeHANDLE);
+ };
+ Type.HANDLE.finalizeHANDLE = function placeholder() {
+ throw new Error("finalizeHANDLE should be implemented");
+ };
+ let INVALID_HANDLE = Const.INVALID_HANDLE_VALUE;
+
+ Type.file_HANDLE = Type.HANDLE.withName("file HANDLE");
+ SharedAll.defineLazyGetter(Type.file_HANDLE,
+ "finalizeHANDLE",
+ function() {
+ return SysFile._CloseHandle;
+ });
+
+ Type.find_HANDLE = Type.HANDLE.withName("find HANDLE");
+ SharedAll.defineLazyGetter(Type.find_HANDLE,
+ "finalizeHANDLE",
+ function() {
+ return SysFile._FindClose;
+ });
+
+ Type.DWORD = Type.uint32_t.withName("DWORD");
+
+ /* A special type used to represent flags passed as DWORDs to a function.
+ * In JavaScript, bitwise manipulation of numbers, such as or-ing flags,
+ * can produce negative numbers. Since DWORD is unsigned, these negative
+ * numbers simply cannot be converted to DWORD. For this reason, whenever
+ * bit manipulation is called for, you should rather use DWORD_FLAGS,
+ * which is represented as a signed integer, hence has the correct
+ * semantics.
+ */
+ Type.DWORD_FLAGS = Type.int32_t.withName("DWORD_FLAGS");
+
+ /**
+ * A C integer holding 0 in case of error or a positive integer
+ * in case of success.
+ */
+ Type.zero_or_DWORD =
+ Type.DWORD.withName("zero_or_DWORD");
+
+ /**
+ * A C integer holding 0 in case of error, any other value in
+ * case of success.
+ */
+ Type.zero_or_nothing =
+ Type.int.withName("zero_or_nothing");
+
+ /**
+ * A C integer holding flags related to NTFS security.
+ */
+ Type.SECURITY_ATTRIBUTES =
+ Type.void_t.withName("SECURITY_ATTRIBUTES");
+
+ /**
+ * A C integer holding pointers related to NTFS security.
+ */
+ Type.PSID =
+ Type.voidptr_t.withName("PSID");
+
+ Type.PACL =
+ Type.voidptr_t.withName("PACL");
+
+ Type.PSECURITY_DESCRIPTOR =
+ Type.voidptr_t.withName("PSECURITY_DESCRIPTOR");
+
+ /**
+ * A C integer holding Win32 local memory handle.
+ */
+ Type.HLOCAL =
+ Type.voidptr_t.withName("HLOCAL");
+
+ Type.FILETIME =
+ new SharedAll.Type("FILETIME",
+ ctypes.StructType("FILETIME", [
+ { lo: Type.DWORD.implementation },
+ { hi: Type.DWORD.implementation }]));
+
+ Type.FindData =
+ new SharedAll.Type("FIND_DATA",
+ ctypes.StructType("FIND_DATA", [
+ { dwFileAttributes: ctypes.uint32_t },
+ { ftCreationTime: Type.FILETIME.implementation },
+ { ftLastAccessTime: Type.FILETIME.implementation },
+ { ftLastWriteTime: Type.FILETIME.implementation },
+ { nFileSizeHigh: Type.DWORD.implementation },
+ { nFileSizeLow: Type.DWORD.implementation },
+ { dwReserved0: Type.DWORD.implementation },
+ { dwReserved1: Type.DWORD.implementation },
+ { cFileName: ctypes.ArrayType(ctypes.char16_t, Const.MAX_PATH) },
+ { cAlternateFileName: ctypes.ArrayType(ctypes.char16_t, 14) }
+ ]));
+
+ Type.FILE_INFORMATION =
+ new SharedAll.Type("FILE_INFORMATION",
+ ctypes.StructType("FILE_INFORMATION", [
+ { dwFileAttributes: ctypes.uint32_t },
+ { ftCreationTime: Type.FILETIME.implementation },
+ { ftLastAccessTime: Type.FILETIME.implementation },
+ { ftLastWriteTime: Type.FILETIME.implementation },
+ { dwVolumeSerialNumber: ctypes.uint32_t },
+ { nFileSizeHigh: Type.DWORD.implementation },
+ { nFileSizeLow: Type.DWORD.implementation },
+ { nNumberOfLinks: ctypes.uint32_t },
+ { nFileIndex: ctypes.uint64_t }
+ ]));
+
+ Type.SystemTime =
+ new SharedAll.Type("SystemTime",
+ ctypes.StructType("SystemTime", [
+ { wYear: ctypes.int16_t },
+ { wMonth: ctypes.int16_t },
+ { wDayOfWeek: ctypes.int16_t },
+ { wDay: ctypes.int16_t },
+ { wHour: ctypes.int16_t },
+ { wMinute: ctypes.int16_t },
+ { wSecond: ctypes.int16_t },
+ { wMilliSeconds: ctypes.int16_t }
+ ]));
+
+ // Special case: these functions are used by the
+ // finalizer
+ libc.declareLazy(SysFile, "_CloseHandle",
+ "CloseHandle", ctypes.winapi_abi,
+ /*return */ctypes.bool,
+ /*handle*/ ctypes.voidptr_t);
+
+ SysFile.CloseHandle = function(fd) {
+ if (fd == INVALID_HANDLE) {
+ return true;
+ } else {
+ return fd.dispose(); // Returns the value of |CloseHandle|.
+ }
+ };
+
+ libc.declareLazy(SysFile, "_FindClose",
+ "FindClose", ctypes.winapi_abi,
+ /*return */ctypes.bool,
+ /*handle*/ ctypes.voidptr_t);
+
+ SysFile.FindClose = function(handle) {
+ if (handle == INVALID_HANDLE) {
+ return true;
+ } else {
+ return handle.dispose(); // Returns the value of |FindClose|.
+ }
+ };
+
+ // Declare libc functions as functions of |OS.Win.File|
+
+ libc.declareLazyFFI(SysFile, "CopyFile",
+ "CopyFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*sourcePath*/ Type.path,
+ /*destPath*/ Type.path,
+ /*bailIfExist*/Type.bool);
+
+ libc.declareLazyFFI(SysFile, "CreateDirectory",
+ "CreateDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*name*/ Type.char16_t.in_ptr,
+ /*security*/Type.SECURITY_ATTRIBUTES.in_ptr);
+
+ libc.declareLazyFFI(SysFile, "CreateFile",
+ "CreateFileW", ctypes.winapi_abi,
+ /*return*/ Type.file_HANDLE,
+ /*name*/ Type.path,
+ /*access*/ Type.DWORD_FLAGS,
+ /*share*/ Type.DWORD_FLAGS,
+ /*security*/Type.SECURITY_ATTRIBUTES.in_ptr,
+ /*creation*/Type.DWORD_FLAGS,
+ /*flags*/ Type.DWORD_FLAGS,
+ /*template*/Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "DeleteFile",
+ "DeleteFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "FileTimeToSystemTime",
+ "FileTimeToSystemTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*filetime*/Type.FILETIME.in_ptr,
+ /*systime*/ Type.SystemTime.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "SystemTimeToFileTime",
+ "SystemTimeToFileTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*systime*/ Type.SystemTime.in_ptr,
+ /*filetime*/ Type.FILETIME.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FindFirstFile",
+ "FindFirstFileW", ctypes.winapi_abi,
+ /*return*/ Type.find_HANDLE,
+ /*pattern*/Type.path,
+ /*data*/ Type.FindData.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FindNextFile",
+ "FindNextFileW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*prev*/ Type.find_HANDLE,
+ /*data*/ Type.FindData.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "FormatMessage",
+ "FormatMessageW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*flags*/ Type.DWORD_FLAGS,
+ /*source*/ Type.void_t.in_ptr,
+ /*msgid*/ Type.DWORD_FLAGS,
+ /*langid*/ Type.DWORD_FLAGS,
+ /*buf*/ Type.out_wstring,
+ /*size*/ Type.DWORD,
+ /*Arguments*/Type.void_t.in_ptr
+ );
+
+ libc.declareLazyFFI(SysFile, "GetCurrentDirectory",
+ "GetCurrentDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_DWORD,
+ /*length*/ Type.DWORD,
+ /*buf*/ Type.out_path
+ );
+
+ libc.declareLazyFFI(SysFile, "GetFullPathName",
+ "GetFullPathNameW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_DWORD,
+ /*fileName*/ Type.path,
+ /*length*/ Type.DWORD,
+ /*buf*/ Type.out_path,
+ /*filePart*/ Type.DWORD
+ );
+
+ libc.declareLazyFFI(SysFile, "GetDiskFreeSpaceEx",
+ "GetDiskFreeSpaceExW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*directoryName*/ Type.path,
+ /*freeBytesForUser*/ Type.uint64_t.out_ptr,
+ /*totalBytesForUser*/ Type.uint64_t.out_ptr,
+ /*freeTotalBytesOnDrive*/ Type.uint64_t.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "GetFileInformationByHandle",
+ "GetFileInformationByHandle", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*handle*/ Type.HANDLE,
+ /*info*/ Type.FILE_INFORMATION.out_ptr);
+
+ libc.declareLazyFFI(SysFile, "MoveFileEx",
+ "MoveFileExW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*sourcePath*/ Type.path,
+ /*destPath*/ Type.path,
+ /*flags*/ Type.DWORD
+ );
+
+ libc.declareLazyFFI(SysFile, "ReadFile",
+ "ReadFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*buffer*/ Type.voidptr_t,
+ /*nbytes*/ Type.DWORD,
+ /*nbytes_read*/Type.DWORD.out_ptr,
+ /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
+ );
+
+ libc.declareLazyFFI(SysFile, "RemoveDirectory",
+ "RemoveDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "SetCurrentDirectory",
+ "SetCurrentDirectoryW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*path*/ Type.path
+ );
+
+ libc.declareLazyFFI(SysFile, "SetEndOfFile",
+ "SetEndOfFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "SetFilePointer",
+ "SetFilePointer", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*file*/ Type.HANDLE,
+ /*distlow*/Type.long,
+ /*disthi*/ Type.long.in_ptr,
+ /*method*/ Type.DWORD);
+
+ libc.declareLazyFFI(SysFile, "SetFileTime",
+ "SetFileTime", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*creation*/ Type.FILETIME.in_ptr,
+ /*access*/ Type.FILETIME.in_ptr,
+ /*write*/ Type.FILETIME.in_ptr);
+
+
+ libc.declareLazyFFI(SysFile, "WriteFile",
+ "WriteFile", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE,
+ /*buffer*/ Type.voidptr_t,
+ /*nbytes*/ Type.DWORD,
+ /*nbytes_wr*/Type.DWORD.out_ptr,
+ /*overlapped*/Type.void_t.inout_ptr // FIXME: Implement?
+ );
+
+ libc.declareLazyFFI(SysFile, "FlushFileBuffers",
+ "FlushFileBuffers", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*file*/ Type.HANDLE);
+
+ libc.declareLazyFFI(SysFile, "GetFileAttributes",
+ "GetFileAttributesW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD_FLAGS,
+ /*fileName*/ Type.path);
+
+ libc.declareLazyFFI(SysFile, "SetFileAttributes",
+ "SetFileAttributesW", ctypes.winapi_abi,
+ /*return*/ Type.zero_or_nothing,
+ /*fileName*/ Type.path,
+ /*fileAttributes*/ Type.DWORD_FLAGS);
+
+ advapi32.declareLazyFFI(SysFile, "GetNamedSecurityInfo",
+ "GetNamedSecurityInfoW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*objectName*/ Type.path,
+ /*objectType*/ Type.DWORD,
+ /*securityInfo*/ Type.DWORD,
+ /*sidOwner*/ Type.PSID.out_ptr,
+ /*sidGroup*/ Type.PSID.out_ptr,
+ /*dacl*/ Type.PACL.out_ptr,
+ /*sacl*/ Type.PACL.out_ptr,
+ /*securityDesc*/ Type.PSECURITY_DESCRIPTOR.out_ptr);
+
+ advapi32.declareLazyFFI(SysFile, "SetNamedSecurityInfo",
+ "SetNamedSecurityInfoW", ctypes.winapi_abi,
+ /*return*/ Type.DWORD,
+ /*objectName*/ Type.path,
+ /*objectType*/ Type.DWORD,
+ /*securityInfo*/ Type.DWORD,
+ /*sidOwner*/ Type.PSID,
+ /*sidGroup*/ Type.PSID,
+ /*dacl*/ Type.PACL,
+ /*sacl*/ Type.PACL);
+
+ libc.declareLazyFFI(SysFile, "LocalFree",
+ "LocalFree", ctypes.winapi_abi,
+ /*return*/ Type.HLOCAL,
+ /*mem*/ Type.HLOCAL);
+ };
+
+ exports.OS.Win = {
+ File: {
+ _init: init
+ }
+ };
+ })(this);
+}
diff --git a/toolkit/components/osfile/modules/osfile_win_front.jsm b/toolkit/components/osfile/modules/osfile_win_front.jsm
new file mode 100644
index 000000000..387dd08b5
--- /dev/null
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -0,0 +1,1266 @@
+/* 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/. */
+
+/**
+ * Synchronous front-end for the JavaScript OS.File library.
+ * Windows implementation.
+ *
+ * This front-end is meant to be imported by a worker thread.
+ */
+
+{
+ if (typeof Components != "undefined") {
+ // We do not wish osfile_win_front.jsm to be used directly as a main thread
+ // module yet.
+ throw new Error("osfile_win_front.jsm cannot be used from the main thread yet");
+ }
+
+ (function(exports) {
+ "use strict";
+
+
+ // exports.OS.Win is created by osfile_win_back.jsm
+ if (exports.OS && exports.OS.File) {
+ return; // Avoid double-initialization
+ }
+
+ let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
+ let Path = require("resource://gre/modules/osfile/ospath.jsm");
+ let SysAll = require("resource://gre/modules/osfile/osfile_win_allthreads.jsm");
+ exports.OS.Win.File._init();
+ let Const = exports.OS.Constants.Win;
+ let WinFile = exports.OS.Win.File;
+ let Type = WinFile.Type;
+
+ // Mutable thread-global data
+ // In the Windows implementation, methods |read| and |write|
+ // require passing a pointer to an uint32 to determine how many
+ // bytes have been read/written. In C, this is a benigne operation,
+ // but in js-ctypes, this has a cost. Rather than re-allocating a
+ // C uint32 and a C uint32* for each |read|/|write|, we take advantage
+ // of the fact that the state is thread-private -- hence that two
+ // |read|/|write| operations cannot take place at the same time --
+ // and we use the following global mutable values:
+ let gBytesRead = new ctypes.uint32_t(0);
+ let gBytesReadPtr = gBytesRead.address();
+ let gBytesWritten = new ctypes.uint32_t(0);
+ let gBytesWrittenPtr = gBytesWritten.address();
+
+ // Same story for GetFileInformationByHandle
+ let gFileInfo = new Type.FILE_INFORMATION.implementation();
+ let gFileInfoPtr = gFileInfo.address();
+
+ /**
+ * Representation of a file.
+ *
+ * You generally do not need to call this constructor yourself. Rather,
+ * to open a file, use function |OS.File.open|.
+ *
+ * @param fd A OS-specific file descriptor.
+ * @param {string} path File path of the file handle, used for error-reporting.
+ * @constructor
+ */
+ let File = function File(fd, path) {
+ exports.OS.Shared.AbstractFile.call(this, fd, path);
+ this._closeResult = null;
+ };
+ File.prototype = Object.create(exports.OS.Shared.AbstractFile.prototype);
+
+ /**
+ * Close the file.
+ *
+ * This method has no effect if the file is already closed. However,
+ * if the first call to |close| has thrown an error, further calls
+ * will throw the same error.
+ *
+ * @throws File.Error If closing the file revealed an error that could
+ * not be reported earlier.
+ */
+ File.prototype.close = function close() {
+ if (this._fd) {
+ let fd = this._fd;
+ this._fd = null;
+ // Call |close(fd)|, detach finalizer if any
+ // (|fd| may not be a CDataFinalizer if it has been
+ // instantiated from a controller thread).
+ let result = WinFile._CloseHandle(fd);
+ if (typeof fd == "object" && "forget" in fd) {
+ fd.forget();
+ }
+ if (result == -1) {
+ this._closeResult = new File.Error("close", ctypes.winLastError, this._path);
+ }
+ }
+ if (this._closeResult) {
+ throw this._closeResult;
+ }
+ return;
+ };
+
+ /**
+ * Read some bytes from a file.
+ *
+ * @param {C pointer} buffer A buffer for holding the data
+ * once it is read.
+ * @param {number} nbytes The number of bytes to read. It must not
+ * exceed the size of |buffer| in bytes but it may exceed the number
+ * of bytes unread in the file.
+ * @param {*=} options Additional options for reading. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively read. If zero,
+ * the end of the file has been reached.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._read = function _read(buffer, nbytes, options) {
+ // |gBytesReadPtr| is a pointer to |gBytesRead|.
+ throw_on_zero("read",
+ WinFile.ReadFile(this.fd, buffer, nbytes, gBytesReadPtr, null),
+ this._path
+ );
+ return gBytesRead.value;
+ };
+
+ /**
+ * Write some bytes to a file.
+ *
+ * @param {Typed array} buffer A buffer holding the data that must be
+ * written.
+ * @param {number} nbytes The number of bytes to write. It must not
+ * exceed the size of |buffer| in bytes.
+ * @param {*=} options Additional options for writing. Ignored in
+ * this implementation.
+ *
+ * @return {number} The number of bytes effectively written.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype._write = function _write(buffer, nbytes, options) {
+ if (this._appendMode) {
+ // Need to manually seek on Windows, as O_APPEND is not supported.
+ // This is, of course, a race, but there is no real way around this.
+ this.setPosition(0, File.POS_END);
+ }
+ // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
+ throw_on_zero("write",
+ WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null),
+ this._path
+ );
+ return gBytesWritten.value;
+ };
+
+ /**
+ * Return the current position in the file.
+ */
+ File.prototype.getPosition = function getPosition(pos) {
+ return this.setPosition(0, File.POS_CURRENT);
+ };
+
+ /**
+ * Change the current position in the file.
+ *
+ * @param {number} pos The new position. Whether this position
+ * is considered from the current position, from the start of
+ * the file or from the end of the file is determined by
+ * argument |whence|. Note that |pos| may exceed the length of
+ * the file.
+ * @param {number=} whence The reference position. If omitted
+ * or |OS.File.POS_START|, |pos| is relative to the start of the
+ * file. If |OS.File.POS_CURRENT|, |pos| is relative to the
+ * current position in the file. If |OS.File.POS_END|, |pos| is
+ * relative to the end of the file.
+ *
+ * @return The new position in the file.
+ */
+ File.prototype.setPosition = function setPosition(pos, whence) {
+ if (whence === undefined) {
+ whence = Const.FILE_BEGIN;
+ }
+ let pos64 = ctypes.Int64(pos);
+ // Per MSDN, while |lDistanceToMove| (low) is declared as int32_t, when
+ // providing |lDistanceToMoveHigh| as well, it should countain the
+ // bottom 32 bits of the 64-bit integer. Hence the following |posLo|
+ // cast is OK.
+ let posLo = new ctypes.uint32_t(ctypes.Int64.lo(pos64));
+ posLo = ctypes.cast(posLo, ctypes.int32_t);
+ let posHi = new ctypes.int32_t(ctypes.Int64.hi(pos64));
+ let result = WinFile.SetFilePointer(
+ this.fd, posLo.value, posHi.address(), whence);
+ // INVALID_SET_FILE_POINTER might be still a valid result, as it
+ // represents the lower 32 bit of the int64 result. MSDN says to check
+ // both, INVALID_SET_FILE_POINTER and a non-zero winLastError.
+ if (result == Const.INVALID_SET_FILE_POINTER && ctypes.winLastError) {
+ throw new File.Error("setPosition", ctypes.winLastError, this._path);
+ }
+ pos64 = ctypes.Int64.join(posHi.value, result);
+ return Type.int64_t.project(pos64);
+ };
+
+ /**
+ * Fetch the information on the file.
+ *
+ * @return File.Info The information on |this| file.
+ */
+ File.prototype.stat = function stat() {
+ throw_on_zero("stat",
+ WinFile.GetFileInformationByHandle(this.fd, gFileInfoPtr),
+ this._path);
+ return new File.Info(gFileInfo, this._path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid parameters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.setDates = function setDates(accessDate, modificationDate) {
+ accessDate = Date_to_FILETIME("File.prototype.setDates", accessDate, this._path);
+ modificationDate = Date_to_FILETIME("File.prototype.setDates",
+ modificationDate,
+ this._path);
+ throw_on_zero("setDates",
+ WinFile.SetFileTime(this.fd, null, accessDate.address(),
+ modificationDate.address()),
+ this._path);
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.prototype.setPermissions = function setPermissions(options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(this._path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, this._path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(this._path, newAttributes),
+ this._path);
+ };
+
+ /**
+ * Flushes the file's buffers and causes all buffered data
+ * to be written.
+ * Disk flushes are very expensive and therefore should be used carefully,
+ * sparingly and only in scenarios where it is vital that data survives
+ * system crashes. Even though the function will be executed off the
+ * main-thread, it might still affect the overall performance of any
+ * running application.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.prototype.flush = function flush() {
+ throw_on_zero("flush", WinFile.FlushFileBuffers(this.fd), this._path);
+ };
+
+ // The default sharing mode for opening files: files are not
+ // locked against being reopened for reading/writing or against
+ // being deleted by the same process or another process.
+ // This is consistent with the default Unix policy.
+ const DEFAULT_SHARE = Const.FILE_SHARE_READ |
+ Const.FILE_SHARE_WRITE | Const.FILE_SHARE_DELETE;
+
+ // The default flags for opening files.
+ const DEFAULT_FLAGS = Const.FILE_ATTRIBUTE_NORMAL;
+
+ /**
+ * Open a file
+ *
+ * @param {string} path The path to the file.
+ * @param {*=} mode The opening mode for the file, as
+ * an object that may contain the following fields:
+ *
+ * - {bool} truncate If |true|, the file will be opened
+ * for writing. If the file does not exist, it will be
+ * created. If the file exists, its contents will be
+ * erased. Cannot be specified with |create|.
+ * - {bool} create If |true|, the file will be opened
+ * for writing. If the file exists, this function fails.
+ * If the file does not exist, it will be created. Cannot
+ * be specified with |truncate| or |existing|.
+ * - {bool} existing. If the file does not exist, this function
+ * fails. Cannot be specified with |create|.
+ * - {bool} read If |true|, the file will be opened for
+ * reading. The file may also be opened for writing, depending
+ * on the other fields of |mode|.
+ * - {bool} write If |true|, the file will be opened for
+ * writing. The file may also be opened for reading, depending
+ * on the other fields of |mode|.
+ * - {bool} append If |true|, the file will be opened for appending,
+ * meaning the equivalent of |.setPosition(0, POS_END)| is executed
+ * before each write. The default is |true|, i.e. opening a file for
+ * appending. Specify |append: false| to open the file in regular mode.
+ *
+ * If neither |truncate|, |create| or |write| is specified, the file
+ * is opened for reading.
+ *
+ * Note that |false|, |null| or |undefined| flags are simply ignored.
+ *
+ * @param {*=} options Additional options for file opening. This
+ * implementation interprets the following fields:
+ *
+ * - {number} winShare If specified, a share mode, as per
+ * Windows function |CreateFile|. You can build it from
+ * constants |OS.Constants.Win.FILE_SHARE_*|. If unspecified,
+ * the file uses the default sharing policy: it can be opened
+ * for reading and/or writing and it can be removed by other
+ * processes and by the same process.
+ * - {number} winSecurity If specified, Windows security
+ * attributes, as per Windows function |CreateFile|. If unspecified,
+ * no security attributes.
+ * - {number} winAccess If specified, Windows access mode, as
+ * per Windows function |CreateFile|. This also requires option
+ * |winDisposition| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ * - {number} winDisposition If specified, Windows disposition mode,
+ * as per Windows function |CreateFile|. This also requires option
+ * |winAccess| and this replaces argument |mode|. If unspecified,
+ * uses the string |mode|.
+ *
+ * @return {File} A file object.
+ * @throws {OS.File.Error} If the file could not be opened.
+ */
+ File.open = function Win_open(path, mode = {}, options = {}) {
+ let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
+ let security = options.winSecurity || null;
+ let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
+ let template = options.winTemplate ? options.winTemplate._fd : null;
+ let access;
+ let disposition;
+
+ mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+
+ // The following option isn't a generic implementation of access to paths
+ // of arbitrary lengths. It allows for the specific case of writing to an
+ // Alternate Data Stream on a file whose path length is already close to
+ // MAX_PATH. This implementation is safe with a full path as input, if
+ // the first part of the path comes from local configuration and the
+ // file without the ADS was successfully opened before, so we know the
+ // path is valid.
+ if (options.winAllowLengthBeyondMaxPathWithCaveats) {
+ // Use the \\?\ syntax to allow lengths beyond MAX_PATH. This limited
+ // implementation only supports a DOS local path or UNC path as input.
+ let isUNC = path.length >= 2 && (path[0] == "\\" || path[0] == "/") &&
+ (path[1] == "\\" || path[1] == "/");
+ let pathToUse = "\\\\?\\" + (isUNC ? "UNC\\" + path.slice(2) : path);
+ // Use GetFullPathName to normalize slashes into backslashes. This is
+ // required because CreateFile won't do this for the \\?\ syntax.
+ let buffer_size = 512;
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("open",
+ WinFile.GetFullPathName(pathToUse, buffer_size, array, 0)
+ );
+ if (expected_size > buffer_size) {
+ // We don't need to allow an arbitrary path length for now.
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ path = array.readString();
+ }
+
+ if ("winAccess" in options && "winDisposition" in options) {
+ access = options.winAccess;
+ disposition = options.winDisposition;
+ } else if (("winAccess" in options && !("winDisposition" in options))
+ ||(!("winAccess" in options) && "winDisposition" in options)) {
+ throw new TypeError("OS.File.open requires either both options " +
+ "winAccess and winDisposition or neither");
+ } else {
+ if (mode.read) {
+ access |= Const.GENERIC_READ;
+ }
+ if (mode.write) {
+ access |= Const.GENERIC_WRITE;
+ }
+ // Finally, handle create/existing/trunc
+ if (mode.trunc) {
+ if (mode.existing) {
+ // It seems that Const.TRUNCATE_EXISTING is broken
+ // in presence of links (source, anyone?). We need
+ // to open normally, then perform truncation manually.
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.CREATE_ALWAYS;
+ }
+ } else if (mode.create) {
+ disposition = Const.CREATE_NEW;
+ } else if (mode.read && !mode.write) {
+ disposition = Const.OPEN_EXISTING;
+ } else if (mode.existing) {
+ disposition = Const.OPEN_EXISTING;
+ } else {
+ disposition = Const.OPEN_ALWAYS;
+ }
+ }
+
+ let file = error_or_file(WinFile.CreateFile(path,
+ access, share, security, disposition, flags, template), path);
+
+ file._appendMode = !!mode.append;
+
+ if (!(mode.trunc && mode.existing)) {
+ return file;
+ }
+ // Now, perform manual truncation
+ file.setPosition(0, File.POS_START);
+ throw_on_zero("open",
+ WinFile.SetEndOfFile(file.fd),
+ path);
+ return file;
+ };
+
+ /**
+ * Checks if a file or directory exists
+ *
+ * @param {string} path The path to the file.
+ *
+ * @return {bool} true if the file exists, false otherwise.
+ */
+ File.exists = function Win_exists(path) {
+ try {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ file.close();
+ return true;
+ } catch (x) {
+ return false;
+ }
+ };
+
+ /**
+ * Remove an existing file.
+ *
+ * @param {string} path The name of the file.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the file does
+ * not exist. |true| by default.
+ *
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.remove = function remove(path, options = {}) {
+ if (WinFile.DeleteFile(path)) {
+ return;
+ }
+
+ if (ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND ||
+ ctypes.winLastError == Const.ERROR_PATH_NOT_FOUND) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent)) {
+ return;
+ }
+ } else if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED) {
+ // Save winLastError before another ctypes call.
+ let lastError = ctypes.winLastError;
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes != Const.INVALID_FILE_ATTRIBUTES) {
+ if (!(attributes & Const.FILE_ATTRIBUTE_READONLY)) {
+ throw new File.Error("remove", lastError, path);
+ }
+ let newAttributes = attributes & ~Const.FILE_ATTRIBUTE_READONLY;
+ if (WinFile.SetFileAttributes(path, newAttributes) &&
+ WinFile.DeleteFile(path)) {
+ return;
+ }
+ }
+ }
+
+ throw new File.Error("remove", ctypes.winLastError, path);
+ };
+
+ /**
+ * Remove an empty directory.
+ *
+ * @param {string} path The name of the directory to remove.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory
+ * does not exist. |true| by default
+ */
+ File.removeEmptyDir = function removeEmptyDir(path, options = {}) {
+ let result = WinFile.RemoveDirectory(path);
+ if (!result) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ };
+
+ /**
+ * Create a directory and, optionally, its parent directories.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options. This
+ * implementation interprets the following fields:
+ *
+ * - {C pointer} winSecurity If specified, security attributes
+ * as per winapi function |CreateDirectory|. If unspecified,
+ * use the default security descriptor, inherited from the
+ * parent directory.
+ * - {bool} ignoreExisting If |false|, throw an error if the directory
+ * already exists. |true| by default
+ * - {string} from If specified, the call to |makeDir| creates all the
+ * ancestors of |path| that are descendants of |from|. Note that |from|
+ * and its existing descendants must be user-writeable and that |path|
+ * must be a descendant of |from|.
+ * Example:
+ * makeDir(Path.join(profileDir, "foo", "bar"), { from: profileDir });
+ * creates directories profileDir/foo, profileDir/foo/bar
+ */
+ File._makeDir = function makeDir(path, options = {}) {
+ let security = options.winSecurity || null;
+ let result = WinFile.CreateDirectory(path, security);
+
+ if (result) {
+ return;
+ }
+
+ if (("ignoreExisting" in options) && !options.ignoreExisting) {
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ }
+
+ if (ctypes.winLastError == Const.ERROR_ALREADY_EXISTS) {
+ return;
+ }
+
+ // If the user has no access, but it's a root directory, no error should be thrown
+ let splitPath = OS.Path.split(path);
+ // Removing last component if it's empty
+ // An empty last component is caused by trailing slashes in path
+ // This is always the case with root directories
+ if( splitPath.components[splitPath.components.length - 1].length === 0 ) {
+ splitPath.components.pop();
+ }
+ // One component consisting of a drive letter implies a directory root.
+ if (ctypes.winLastError == Const.ERROR_ACCESS_DENIED &&
+ splitPath.winDrive &&
+ splitPath.components.length === 1 ) {
+ return;
+ }
+
+ throw new File.Error("makeDir", ctypes.winLastError, path);
+ };
+
+ /**
+ * Copy a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be copied.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If true, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be copied with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.copy = function copy(sourcePath, destPath, options = {}) {
+ throw_on_zero("copy",
+ WinFile.CopyFile(sourcePath, destPath, options.noOverwrite || false),
+ sourcePath
+ );
+ };
+
+ /**
+ * Move a file to a destination.
+ *
+ * @param {string} sourcePath The platform-specific path at which
+ * the file may currently be found.
+ * @param {string} destPath The platform-specific path at which the
+ * file should be moved.
+ * @param {*=} options An object which may contain the following fields:
+ *
+ * @option {bool} noOverwrite - If set, this function will fail if
+ * a file already exists at |destPath|. Otherwise, if this file exists,
+ * it will be erased silently.
+ * @option {bool} noCopy - If set, this function will fail if the
+ * operation is more sophisticated than a simple renaming, i.e. if
+ * |sourcePath| and |destPath| are not situated on the same drive.
+ *
+ * @throws {OS.File.Error} In case of any error.
+ *
+ * General note: The behavior of this function is defined only when
+ * it is called on a single file. If it is called on a directory, the
+ * behavior is undefined and may not be the same across all platforms.
+ *
+ * General note: The behavior of this function with respect to metadata
+ * is unspecified. Metadata may or may not be moved with the file. The
+ * behavior may not be the same across all platforms.
+ */
+ File.move = function move(sourcePath, destPath, options = {}) {
+ let flags = 0;
+ if (!options.noCopy) {
+ flags = Const.MOVEFILE_COPY_ALLOWED;
+ }
+ if (!options.noOverwrite) {
+ flags = flags | Const.MOVEFILE_REPLACE_EXISTING;
+ }
+ throw_on_zero("move",
+ WinFile.MoveFileEx(sourcePath, destPath, flags),
+ sourcePath
+ );
+
+ // Inherit NTFS permissions from the destination directory
+ // if possible.
+ if (Path.dirname(sourcePath) === Path.dirname(destPath)) {
+ // Skip if the move operation was the simple rename,
+ return;
+ }
+ // The function may fail for various reasons (e.g. not all
+ // filesystems support NTFS permissions or the user may not
+ // have the enough rights to read/write permissions).
+ // However we can safely ignore errors. The file was already
+ // moved. Setting permissions is not mandatory.
+ let dacl = new ctypes.voidptr_t();
+ let sd = new ctypes.voidptr_t();
+ WinFile.GetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl.address(), null /*sacl*/,
+ sd.address());
+ // dacl will be set only if the function succeeds.
+ if (!dacl.isNull()) {
+ WinFile.SetNamedSecurityInfo(destPath, Const.SE_FILE_OBJECT,
+ Const.DACL_SECURITY_INFORMATION |
+ Const.UNPROTECTED_DACL_SECURITY_INFORMATION,
+ null /*sidOwner*/, null /*sidGroup*/,
+ dacl, null /*sacl*/);
+ }
+ // sd will be set only if the function succeeds.
+ if (!sd.isNull()) {
+ WinFile.LocalFree(Type.HLOCAL.cast(sd));
+ }
+ };
+
+ /**
+ * Gets the number of bytes available on disk to the current user.
+ *
+ * @param {string} sourcePath Platform-specific path to a directory on
+ * the disk to query for free available bytes.
+ *
+ * @return {number} The number of bytes available for the current user.
+ * @throws {OS.File.Error} In case of any error.
+ */
+ File.getAvailableFreeSpace = function Win_getAvailableFreeSpace(sourcePath) {
+ let freeBytesAvailableToUser = new Type.uint64_t.implementation(0);
+ let freeBytesAvailableToUserPtr = freeBytesAvailableToUser.address();
+
+ throw_on_zero("getAvailableFreeSpace",
+ WinFile.GetDiskFreeSpaceEx(sourcePath, freeBytesAvailableToUserPtr, null, null)
+ );
+
+ return freeBytesAvailableToUser.value;
+ };
+
+ /**
+ * A global value used to receive data during time conversions.
+ */
+ let gSystemTime = new Type.SystemTime.implementation();
+ let gSystemTimePtr = gSystemTime.address();
+
+ /**
+ * Utility function: convert a FILETIME to a JavaScript Date.
+ */
+ let FILETIME_to_Date = function FILETIME_to_Date(fileTime, path) {
+ if (fileTime == null) {
+ throw new TypeError("Expecting a non-null filetime");
+ }
+ throw_on_zero("FILETIME_to_Date",
+ WinFile.FileTimeToSystemTime(fileTime.address(),
+ gSystemTimePtr),
+ path);
+ // Windows counts hours, minutes, seconds from UTC,
+ // JS counts from local time, so we need to go through UTC.
+ let utc = Date.UTC(gSystemTime.wYear,
+ gSystemTime.wMonth - 1
+ /*Windows counts months from 1, JS from 0*/,
+ gSystemTime.wDay, gSystemTime.wHour,
+ gSystemTime.wMinute, gSystemTime.wSecond,
+ gSystemTime.wMilliSeconds);
+ return new Date(utc);
+ };
+
+ /**
+ * Utility function: convert Javascript Date to FileTime.
+ *
+ * @param {string} fn Name of the calling function.
+ * @param {Date,number} date The date to be converted. If omitted or null,
+ * then the current date will be used. If numeric, assumed to be the date
+ * in milliseconds since epoch.
+ */
+ let Date_to_FILETIME = function Date_to_FILETIME(fn, date, path) {
+ if (typeof date === "number") {
+ date = new Date(date);
+ } else if (!date) {
+ date = new Date();
+ } else if (typeof date.getUTCFullYear !== "function") {
+ throw new TypeError("|date| parameter of " + fn + " must be a " +
+ "|Date| instance or number");
+ }
+ gSystemTime.wYear = date.getUTCFullYear();
+ // Windows counts months from 1, JS from 0.
+ gSystemTime.wMonth = date.getUTCMonth() + 1;
+ gSystemTime.wDay = date.getUTCDate();
+ gSystemTime.wHour = date.getUTCHours();
+ gSystemTime.wMinute = date.getUTCMinutes();
+ gSystemTime.wSecond = date.getUTCSeconds();
+ gSystemTime.wMilliseconds = date.getUTCMilliseconds();
+ let result = new OS.Shared.Type.FILETIME.implementation();
+ throw_on_zero("Date_to_FILETIME",
+ WinFile.SystemTimeToFileTime(gSystemTimePtr,
+ result.address()),
+ path);
+ return result;
+ };
+
+ /**
+ * Iterate on one directory.
+ *
+ * This iterator will not enter subdirectories.
+ *
+ * @param {string} path The directory upon which to iterate.
+ * @param {*=} options An object that may contain the following field:
+ * @option {string} winPattern Windows file name pattern; if set,
+ * only files matching this pattern are returned.
+ *
+ * @throws {File.Error} If |path| does not represent a directory or
+ * if the directory cannot be iterated.
+ * @constructor
+ */
+ File.DirectoryIterator = function DirectoryIterator(path, options) {
+ exports.OS.Shared.AbstractFile.AbstractIterator.call(this);
+ if (options && options.winPattern) {
+ this._pattern = path + "\\" + options.winPattern;
+ } else {
+ this._pattern = path + "\\*";
+ }
+ this._path = path;
+
+ // Pre-open the first item.
+ this._first = true;
+ this._findData = new Type.FindData.implementation();
+ this._findDataPtr = this._findData.address();
+ this._handle = WinFile.FindFirstFile(this._pattern, this._findDataPtr);
+ if (this._handle == Const.INVALID_HANDLE_VALUE) {
+ let error = ctypes.winLastError;
+ this._findData = null;
+ this._findDataPtr = null;
+ if (error == Const.ERROR_FILE_NOT_FOUND) {
+ // Directory is empty, let's behave as if it were closed
+ SharedAll.LOG("Directory is empty");
+ this._closed = true;
+ this._exists = true;
+ } else if (error == Const.ERROR_PATH_NOT_FOUND) {
+ // Directory does not exist, let's throw if we attempt to walk it
+ SharedAll.LOG("Directory does not exist");
+ this._closed = true;
+ this._exists = false;
+ } else {
+ throw new File.Error("DirectoryIterator", error, this._path);
+ }
+ } else {
+ this._closed = false;
+ this._exists = true;
+ }
+ };
+
+ File.DirectoryIterator.prototype = Object.create(exports.OS.Shared.AbstractFile.AbstractIterator.prototype);
+
+
+ /**
+ * Fetch the next entry in the directory.
+ *
+ * @return null If we have reached the end of the directory.
+ */
+ File.DirectoryIterator.prototype._next = function _next() {
+ // Bailout if the directory does not exist
+ if (!this._exists) {
+ throw File.Error.noSuchFile("DirectoryIterator.prototype.next", this._path);
+ }
+ // Bailout if the iterator is closed.
+ if (this._closed) {
+ return null;
+ }
+ // If this is the first entry, we have obtained it already
+ // during construction.
+ if (this._first) {
+ this._first = false;
+ return this._findData;
+ }
+
+ if (WinFile.FindNextFile(this._handle, this._findDataPtr)) {
+ return this._findData;
+ } else {
+ let error = ctypes.winLastError;
+ this.close();
+ if (error == Const.ERROR_NO_MORE_FILES) {
+ return null;
+ } else {
+ throw new File.Error("iter (FindNextFile)", error, this._path);
+ }
+ }
+ },
+
+ /**
+ * Return the next entry in the directory, if any such entry is
+ * available.
+ *
+ * Skip special directories "." and "..".
+ *
+ * @return {File.Entry} The next entry in the directory.
+ * @throws {StopIteration} Once all files in the directory have been
+ * encountered.
+ */
+ File.DirectoryIterator.prototype.next = function next() {
+ // FIXME: If we start supporting "\\?\"-prefixed paths, do not forget
+ // that "." and ".." are absolutely normal file names if _path starts
+ // with such prefix
+ for (let entry = this._next(); entry != null; entry = this._next()) {
+ let name = entry.cFileName.readString();
+ if (name == "." || name == "..") {
+ continue;
+ }
+ return new File.DirectoryIterator.Entry(entry, this._path);
+ }
+ throw StopIteration;
+ };
+
+ File.DirectoryIterator.prototype.close = function close() {
+ if (this._closed) {
+ return;
+ }
+ this._closed = true;
+ if (this._handle) {
+ // We might not have a handle if the iterator is closed
+ // before being used.
+ throw_on_zero("FindClose",
+ WinFile.FindClose(this._handle),
+ this._path);
+ this._handle = null;
+ }
+ };
+
+ /**
+ * Determine whether the directory exists.
+ *
+ * @return {boolean}
+ */
+ File.DirectoryIterator.prototype.exists = function exists() {
+ return this._exists;
+ };
+
+ File.DirectoryIterator.Entry = function Entry(win_entry, parent) {
+ if (!win_entry.dwFileAttributes || !win_entry.ftCreationTime ||
+ !win_entry.ftLastAccessTime || !win_entry.ftLastWriteTime)
+ throw new TypeError();
+
+ // Copy the relevant part of |win_entry| to ensure that
+ // our data is not overwritten prematurely.
+ let isDir = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(win_entry.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winCreationDate = FILETIME_to_Date(win_entry.ftCreationTime, this._path);
+ let winLastWriteDate = FILETIME_to_Date(win_entry.ftLastWriteTime, this._path);
+ let winLastAccessDate = FILETIME_to_Date(win_entry.ftLastAccessTime, this._path);
+
+ let name = win_entry.cFileName.readString();
+ if (!name) {
+ throw new TypeError("Empty name");
+ }
+
+ if (!parent) {
+ throw new TypeError("Empty parent");
+ }
+ this._parent = parent;
+
+ let path = Path.join(this._parent, name);
+
+ SysAll.AbstractEntry.call(this, isDir, isSymLink, name,
+ winCreationDate, winLastWriteDate,
+ winLastAccessDate, path);
+ };
+ File.DirectoryIterator.Entry.prototype = Object.create(SysAll.AbstractEntry.prototype);
+
+ /**
+ * Return a version of an instance of
+ * File.DirectoryIterator.Entry that can be sent from a worker
+ * thread to the main thread. Note that deserialization is
+ * asymmetric and returns an object with a different
+ * implementation.
+ */
+ File.DirectoryIterator.Entry.toMsg = function toMsg(value) {
+ if (!value instanceof File.DirectoryIterator.Entry) {
+ throw new TypeError("parameter of " +
+ "File.DirectoryIterator.Entry.toMsg must be a " +
+ "File.DirectoryIterator.Entry");
+ }
+ let serialized = {};
+ for (let key in File.DirectoryIterator.Entry.prototype) {
+ serialized[key] = value[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Information on a file.
+ *
+ * To obtain the latest information on a file, use |File.stat|
+ * (for an unopened file) or |File.prototype.stat| (for an
+ * already opened file).
+ *
+ * @constructor
+ */
+ File.Info = function Info(stat, path) {
+ let isDir = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_DIRECTORY);
+ let isSymLink = !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_REPARSE_POINT);
+
+ let winBirthDate = FILETIME_to_Date(stat.ftCreationTime, this._path);
+ let lastAccessDate = FILETIME_to_Date(stat.ftLastAccessTime, this._path);
+ let lastWriteDate = FILETIME_to_Date(stat.ftLastWriteTime, this._path);
+
+ let value = ctypes.UInt64.join(stat.nFileSizeHigh, stat.nFileSizeLow);
+ let size = Type.uint64_t.importFromC(value);
+ let winAttributes = {
+ readOnly: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_READONLY),
+ system: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_SYSTEM),
+ hidden: !!(stat.dwFileAttributes & Const.FILE_ATTRIBUTE_HIDDEN),
+ };
+
+ SysAll.AbstractInfo.call(this, path, isDir, isSymLink, size,
+ winBirthDate, lastAccessDate, lastWriteDate, winAttributes);
+ };
+ File.Info.prototype = Object.create(SysAll.AbstractInfo.prototype);
+
+ /**
+ * Return a version of an instance of File.Info that can be sent
+ * from a worker thread to the main thread. Note that deserialization
+ * is asymmetric and returns an object with a different implementation.
+ */
+ File.Info.toMsg = function toMsg(stat) {
+ if (!stat instanceof File.Info) {
+ throw new TypeError("parameter of File.Info.toMsg must be a File.Info");
+ }
+ let serialized = {};
+ for (let key in File.Info.prototype) {
+ serialized[key] = stat[key];
+ }
+ return serialized;
+ };
+
+
+ /**
+ * Fetch the information on a file.
+ *
+ * Performance note: if you have opened the file already,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the read capability,
+ * this function will fail.
+ *
+ * @return {File.Information}
+ */
+ File.stat = function stat(path) {
+ let file = File.open(path, FILE_STAT_MODE, FILE_STAT_OPTIONS);
+ try {
+ return file.stat();
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.stat
+ // also works on directories.
+ const FILE_STAT_MODE = {
+ read: true
+ };
+ const FILE_STAT_OPTIONS = {
+ // Directories can be opened neither for reading(!) nor for writing
+ winAccess: 0,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ /**
+ * Set the file's access permission bits.
+ */
+ File.setPermissions = function setPermissions(path, options = {}) {
+ if (!("winAttributes" in options)) {
+ return;
+ }
+ let oldAttributes = WinFile.GetFileAttributes(path);
+ if (oldAttributes == Const.INVALID_FILE_ATTRIBUTES) {
+ throw new File.Error("setPermissions", ctypes.winLastError, path);
+ }
+ let newAttributes = toFileAttributes(options.winAttributes, oldAttributes);
+ throw_on_zero("setPermissions",
+ WinFile.SetFileAttributes(path, newAttributes),
+ path);
+ };
+
+ /**
+ * Set the last access and modification date of the file.
+ * The time stamp resolution is 1 second at best, but might be worse
+ * depending on the platform.
+ *
+ * Performance note: if you have opened the file already in write mode,
+ * method |File.prototype.stat| is generally much faster
+ * than method |File.stat|.
+ *
+ * Platform-specific note: under Windows, if the file is
+ * already opened without sharing of the write capability,
+ * this function will fail.
+ *
+ * @param {string} path The full name of the file to set the dates for.
+ * @param {Date,number=} accessDate The last access date. If numeric,
+ * milliseconds since epoch. If omitted or null, then the current date
+ * will be used.
+ * @param {Date,number=} modificationDate The last modification date. If
+ * numeric, milliseconds since epoch. If omitted or null, then the current
+ * date will be used.
+ *
+ * @throws {TypeError} In case of invalid paramters.
+ * @throws {OS.File.Error} In case of I/O error.
+ */
+ File.setDates = function setDates(path, accessDate, modificationDate) {
+ let file = File.open(path, FILE_SETDATES_MODE, FILE_SETDATES_OPTIONS);
+ try {
+ return file.setDates(accessDate, modificationDate);
+ } finally {
+ file.close();
+ }
+ };
+ // All of the following is required to ensure that File.setDates
+ // also works on directories.
+ const FILE_SETDATES_MODE = {
+ write: true
+ };
+ const FILE_SETDATES_OPTIONS = {
+ winAccess: Const.GENERIC_WRITE,
+ // Directories can only be opened with backup semantics(!)
+ winFlags: Const.FILE_FLAG_BACKUP_SEMANTICS,
+ winDisposition: Const.OPEN_EXISTING
+ };
+
+ File.read = exports.OS.Shared.AbstractFile.read;
+ File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+ File.openUnique = exports.OS.Shared.AbstractFile.openUnique;
+ File.makeDir = exports.OS.Shared.AbstractFile.makeDir;
+
+ /**
+ * Remove an existing directory and its contents.
+ *
+ * @param {string} path The name of the directory.
+ * @param {*=} options Additional options.
+ * - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+ * exist. |true| by default.
+ * - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+ * permission.
+ *
+ * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+ * not a directory.
+ */
+ File.removeDir = function(path, options = {}) {
+ // We can't use File.stat here because it will follow the symlink.
+ let attributes = WinFile.GetFileAttributes(path);
+ if (attributes == Const.INVALID_FILE_ATTRIBUTES) {
+ if ((!("ignoreAbsent" in options) || options.ignoreAbsent) &&
+ ctypes.winLastError == Const.ERROR_FILE_NOT_FOUND) {
+ return;
+ }
+ throw new File.Error("removeEmptyDir", ctypes.winLastError, path);
+ }
+ if (attributes & Const.FILE_ATTRIBUTE_REPARSE_POINT) {
+ // Unlike Unix symlinks, NTFS junctions or NTFS symlinks to
+ // directories are directories themselves. OS.File.remove()
+ // will not work for them.
+ OS.File.removeEmptyDir(path, options);
+ return;
+ }
+ exports.OS.Shared.AbstractFile.removeRecursive(path, options);
+ };
+
+ /**
+ * Get the current directory by getCurrentDirectory.
+ */
+ File.getCurrentDirectory = function getCurrentDirectory() {
+ // This function is more complicated than one could hope.
+ //
+ // This is due to two facts:
+ // - the maximal length of a path under Windows is not completely
+ // specified (there is a constant MAX_PATH, but it is quite possible
+ // to create paths that are much larger, see bug 744413);
+ // - if we attempt to call |GetCurrentDirectory| with a buffer that
+ // is too short, it returns the length of the current directory, but
+ // this length might be insufficient by the time we can call again
+ // the function with a larger buffer, in the (unlikely but possible)
+ // case in which the process changes directory to a directory with
+ // a longer name between both calls.
+ //
+ let buffer_size = 4096;
+ while (true) {
+ let array = new (ctypes.ArrayType(ctypes.char16_t, buffer_size))();
+ let expected_size = throw_on_zero("getCurrentDirectory",
+ WinFile.GetCurrentDirectory(buffer_size, array)
+ );
+ if (expected_size <= buffer_size) {
+ return array.readString();
+ }
+ // At this point, we are in a case in which our buffer was not
+ // large enough to hold the name of the current directory.
+ // Consequently, we need to increase the size of the buffer.
+ // Note that, even in crazy scenarios, the loop will eventually
+ // converge, as the length of the paths cannot increase infinitely.
+ buffer_size = expected_size + 1 /* to store \0 */;
+ }
+ };
+
+ /**
+ * Set the current directory by setCurrentDirectory.
+ */
+ File.setCurrentDirectory = function setCurrentDirectory(path) {
+ throw_on_zero("setCurrentDirectory",
+ WinFile.SetCurrentDirectory(path),
+ path);
+ };
+
+ /**
+ * Get/set the current directory by |curDir|.
+ */
+ Object.defineProperty(File, "curDir", {
+ set: function(path) {
+ this.setCurrentDirectory(path);
+ },
+ get: function() {
+ return this.getCurrentDirectory();
+ }
+ }
+ );
+
+ // Utility functions, used for error-handling
+
+ /**
+ * Turn the result of |open| into an Error or a File
+ * @param {number} maybe The result of the |open| operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns the opened file.
+ * @param {string=} path The path of the file.
+ */
+ function error_or_file(maybe, path) {
+ if (maybe == Const.INVALID_HANDLE_VALUE) {
+ throw new File.Error("open", ctypes.winLastError, path);
+ }
+ return new File(maybe, path);
+ }
+
+ /**
+ * Utility function to sort errors represented as "0" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If 0, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_zero(operation, result, path) {
+ if (result == 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as "-1" from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {number} result The result of the operation that may
+ * represent either an error or a success. If -1, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_negative(operation, result, path) {
+ if (result < 0) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Utility function to sort errors represented as |null| from successes.
+ *
+ * @param {string=} operation The name of the operation. If unspecified,
+ * the name of the caller function.
+ * @param {pointer} result The result of the operation that may
+ * represent either an error or a success. If |null|, this function raises
+ * an error holding ctypes.winLastError, otherwise it returns |result|.
+ * @param {string=} path The path of the file.
+ */
+ function throw_on_null(operation, result, path) {
+ if (result == null || (result.isNull && result.isNull())) {
+ throw new File.Error(operation, ctypes.winLastError, path);
+ }
+ return result;
+ }
+
+ /**
+ * Helper used by both versions of setPermissions
+ */
+ function toFileAttributes(winAttributes, oldDwAttrs) {
+ if ("readOnly" in winAttributes) {
+ if (winAttributes.readOnly) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_READONLY;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_READONLY;
+ }
+ }
+ if ("system" in winAttributes) {
+ if (winAttributes.system) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_SYSTEM;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_SYSTEM;
+ }
+ }
+ if ("hidden" in winAttributes) {
+ if (winAttributes.hidden) {
+ oldDwAttrs |= Const.FILE_ATTRIBUTE_HIDDEN;
+ } else {
+ oldDwAttrs &= ~Const.FILE_ATTRIBUTE_HIDDEN;
+ }
+ }
+ return oldDwAttrs;
+ }
+
+ File.Win = exports.OS.Win.File;
+ File.Error = SysAll.Error;
+ exports.OS.File = File;
+ exports.OS.Shared.Type = Type;
+
+ Object.defineProperty(File, "POS_START", { value: SysAll.POS_START });
+ Object.defineProperty(File, "POS_CURRENT", { value: SysAll.POS_CURRENT });
+ Object.defineProperty(File, "POS_END", { value: SysAll.POS_END });
+ })(this);
+}
diff --git a/toolkit/components/osfile/modules/ospath.jsm b/toolkit/components/osfile/modules/ospath.jsm
new file mode 100644
index 000000000..68bbe4345
--- /dev/null
+++ b/toolkit/components/osfile/modules/ospath.jsm
@@ -0,0 +1,45 @@
+/* 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/. */
+
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ */
+
+"use strict";
+
+if (typeof Components == "undefined") {
+ let Path;
+ if (OS.Constants.Win) {
+ Path = require("resource://gre/modules/osfile/ospath_win.jsm");
+ } else {
+ Path = require("resource://gre/modules/osfile/ospath_unix.jsm");
+ }
+ module.exports = Path;
+} else {
+ let Cu = Components.utils;
+ let Scope = {};
+ Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", Scope);
+
+ let Path = {};
+ if (Scope.OS.Constants.Win) {
+ Cu.import("resource://gre/modules/osfile/ospath_win.jsm", Path);
+ } else {
+ Cu.import("resource://gre/modules/osfile/ospath_unix.jsm", Path);
+ }
+
+ this.EXPORTED_SYMBOLS = [];
+ for (let k in Path) {
+ this.EXPORTED_SYMBOLS.push(k);
+ this[k] = Path[k];
+ }
+}
diff --git a/toolkit/components/osfile/modules/ospath_unix.jsm b/toolkit/components/osfile/modules/ospath_unix.jsm
new file mode 100644
index 000000000..1d574baed
--- /dev/null
+++ b/toolkit/components/osfile/modules/ospath_unix.jsm
@@ -0,0 +1,202 @@
+/* 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/. */
+
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ */
+
+"use strict";
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+if (typeof Components != "undefined") {
+ Components.utils.importGlobalProperties(["URL"]);
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+} else if (typeof module == "undefined" || typeof exports == "undefined") {
+ throw new Error("Please load this module using require()");
+}
+
+var EXPORTED_SYMBOLS = [
+ "basename",
+ "dirname",
+ "join",
+ "normalize",
+ "split",
+ "toFileURI",
+ "fromFileURI",
+];
+
+/**
+ * Return the final part of the path.
+ * The final part of the path is everything after the last "/".
+ */
+var basename = function(path) {
+ return path.slice(path.lastIndexOf("/") + 1);
+};
+exports.basename = basename;
+
+/**
+ * Return the directory part of the path.
+ * The directory part of the path is everything before the last
+ * "/". If the last few characters of this part are also "/",
+ * they are ignored.
+ *
+ * If the path contains no directory, return ".".
+ */
+var dirname = function(path) {
+ let index = path.lastIndexOf("/");
+ if (index == -1) {
+ return ".";
+ }
+ while (index >= 0 && path[index] == "/") {
+ --index;
+ }
+ return path.slice(0, index + 1);
+};
+exports.dirname = dirname;
+
+/**
+ * Join path components.
+ * This is the recommended manner of getting the path of a file/subdirectory
+ * in a directory.
+ *
+ * Example: Obtaining $TMP/foo/bar in an OS-independent manner
+ * var tmpDir = OS.Constants.Path.tmpDir;
+ * var path = OS.Path.join(tmpDir, "foo", "bar");
+ *
+ * Under Unix, this will return "/tmp/foo/bar".
+ *
+ * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
+ * same as `OS.Path.join("foo", "bar")`.
+ */
+var join = function(...path) {
+ // If there is a path that starts with a "/", eliminate everything before
+ let paths = [];
+ for (let subpath of path) {
+ if (subpath == null) {
+ throw new TypeError("invalid path component");
+ }
+ if (subpath.length == 0) {
+ continue;
+ } else if (subpath[0] == "/") {
+ paths = [subpath];
+ } else {
+ paths.push(subpath);
+ }
+ }
+ return paths.join("/");
+};
+exports.join = join;
+
+/**
+ * Normalize a path by removing any unneeded ".", "..", "//".
+ */
+var normalize = function(path) {
+ let stack = [];
+ let absolute;
+ if (path.length >= 0 && path[0] == "/") {
+ absolute = true;
+ } else {
+ absolute = false;
+ }
+ path.split("/").forEach(function(v) {
+ switch (v) {
+ case "": case ".":// fallthrough
+ break;
+ case "..":
+ if (stack.length == 0) {
+ if (absolute) {
+ throw new Error("Path is ill-formed: attempting to go past root");
+ } else {
+ stack.push("..");
+ }
+ } else {
+ if (stack[stack.length - 1] == "..") {
+ stack.push("..");
+ } else {
+ stack.pop();
+ }
+ }
+ break;
+ default:
+ stack.push(v);
+ }
+ });
+ let string = stack.join("/");
+ return absolute ? "/" + string : string;
+};
+exports.normalize = normalize;
+
+/**
+ * Return the components of a path.
+ * You should generally apply this function to a normalized path.
+ *
+ * @return {{
+ * {bool} absolute |true| if the path is absolute, |false| otherwise
+ * {array} components the string components of the path
+ * }}
+ *
+ * Other implementations may add additional OS-specific informations.
+ */
+var split = function(path) {
+ return {
+ absolute: path.length && path[0] == "/",
+ components: path.split("/")
+ };
+};
+exports.split = split;
+
+/**
+ * Returns the file:// URI file path of the given local file path.
+ */
+// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
+var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
+var toFileURI = function toFileURI(path) {
+ // Per https://url.spec.whatwg.org we should not encode [] in the path
+ let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
+ let uri = encodeURI(this.normalize(path)).replace(/%(5B|5D)/gi,
+ match => dontNeedEscaping[match]);
+
+ // add a prefix, and encodeURI doesn't escape a few characters that we do
+ // want to escape, so fix that up
+ let prefix = "file://";
+ uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);
+
+ return uri;
+};
+exports.toFileURI = toFileURI;
+
+/**
+ * Returns the local file path from a given file URI.
+ */
+var fromFileURI = function fromFileURI(uri) {
+ let url = new URL(uri);
+ if (url.protocol != 'file:') {
+ throw new Error("fromFileURI expects a file URI");
+ }
+ let path = this.normalize(decodeURIComponent(url.pathname));
+ return path;
+};
+exports.fromFileURI = fromFileURI;
+
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}
diff --git a/toolkit/components/osfile/modules/ospath_win.jsm b/toolkit/components/osfile/modules/ospath_win.jsm
new file mode 100644
index 000000000..31a87b115
--- /dev/null
+++ b/toolkit/components/osfile/modules/ospath_win.jsm
@@ -0,0 +1,373 @@
+/**
+ * Handling native paths.
+ *
+ * This module contains a number of functions destined to simplify
+ * working with native paths through a cross-platform API. Functions
+ * of this module will only work with the following assumptions:
+ *
+ * - paths are valid;
+ * - paths are defined with one of the grammars that this module can
+ * parse (see later);
+ * - all path concatenations go through function |join|.
+ *
+ * Limitations of this implementation.
+ *
+ * Windows supports 6 distinct grammars for paths. For the moment, this
+ * implementation supports the following subset:
+ *
+ * - drivename:backslash-separated components
+ * - backslash-separated components
+ * - \\drivename\ followed by backslash-separated components
+ *
+ * Additionally, |normalize| can convert a path containing slash-
+ * separated components to a path containing backslash-separated
+ * components.
+ */
+
+"use strict";
+
+// Boilerplate used to be able to import this module both from the main
+// thread and from worker threads.
+if (typeof Components != "undefined") {
+ Components.utils.importGlobalProperties(["URL"]);
+ // Global definition of |exports|, to keep everybody happy.
+ // In non-main thread, |exports| is provided by the module
+ // loader.
+ this.exports = {};
+} else if (typeof module == "undefined" || typeof exports == "undefined") {
+ throw new Error("Please load this module using require()");
+}
+
+var EXPORTED_SYMBOLS = [
+ "basename",
+ "dirname",
+ "join",
+ "normalize",
+ "split",
+ "winGetDrive",
+ "winIsAbsolute",
+ "toFileURI",
+ "fromFileURI",
+];
+
+/**
+ * Return the final part of the path.
+ * The final part of the path is everything after the last "\\".
+ */
+var basename = function(path) {
+ if (path.startsWith("\\\\")) {
+ // UNC-style path
+ let index = path.lastIndexOf("\\");
+ if (index != 1) {
+ return path.slice(index + 1);
+ }
+ return ""; // Degenerate case
+ }
+ return path.slice(Math.max(path.lastIndexOf("\\"),
+ path.lastIndexOf(":")) + 1);
+};
+exports.basename = basename;
+
+/**
+ * Return the directory part of the path.
+ *
+ * If the path contains no directory, return the drive letter,
+ * or "." if the path contains no drive letter or if option
+ * |winNoDrive| is set.
+ *
+ * Otherwise, return everything before the last backslash,
+ * including the drive/server name.
+ *
+ *
+ * @param {string} path The path.
+ * @param {*=} options Platform-specific options controlling the behavior
+ * of this function. This implementation supports the following options:
+ * - |winNoDrive| If |true|, also remove the letter from the path name.
+ */
+var dirname = function(path, options) {
+ let noDrive = (options && options.winNoDrive);
+
+ // Find the last occurrence of "\\"
+ let index = path.lastIndexOf("\\");
+ if (index == -1) {
+ // If there is no directory component...
+ if (!noDrive) {
+ // Return the drive path if possible, falling back to "."
+ return this.winGetDrive(path) || ".";
+ } else {
+ // Or just "."
+ return ".";
+ }
+ }
+
+ if (index == 1 && path.charAt(0) == "\\") {
+ // The path is reduced to a UNC drive
+ if (noDrive) {
+ return ".";
+ } else {
+ return path;
+ }
+ }
+
+ // Ignore any occurrence of "\\: immediately before that one
+ while (index >= 0 && path[index] == "\\") {
+ --index;
+ }
+
+ // Compute what is left, removing the drive name if necessary
+ let start;
+ if (noDrive) {
+ start = (this.winGetDrive(path) || "").length;
+ } else {
+ start = 0;
+ }
+ return path.slice(start, index + 1);
+};
+exports.dirname = dirname;
+
+/**
+ * Join path components.
+ * This is the recommended manner of getting the path of a file/subdirectory
+ * in a directory.
+ *
+ * Example: Obtaining $TMP/foo/bar in an OS-independent manner
+ * var tmpDir = OS.Constants.Path.tmpDir;
+ * var path = OS.Path.join(tmpDir, "foo", "bar");
+ *
+ * Under Windows, this will return "$TMP\foo\bar".
+ *
+ * Empty components are ignored, i.e. `OS.Path.join("foo", "", "bar)` is the
+ * same as `OS.Path.join("foo", "bar")`.
+ */
+var join = function(...path) {
+ let paths = [];
+ let root;
+ let absolute = false;
+ for (let subpath of path) {
+ if (subpath == null) {
+ throw new TypeError("invalid path component");
+ }
+ if (subpath == "") {
+ continue;
+ }
+ let drive = this.winGetDrive(subpath);
+ if (drive) {
+ root = drive;
+ let component = trimBackslashes(subpath.slice(drive.length));
+ if (component) {
+ paths = [component];
+ } else {
+ paths = [];
+ }
+ absolute = true;
+ } else if (this.winIsAbsolute(subpath)) {
+ paths = [trimBackslashes(subpath)];
+ absolute = true;
+ } else {
+ paths.push(trimBackslashes(subpath));
+ }
+ }
+ let result = "";
+ if (root) {
+ result += root;
+ }
+ if (absolute) {
+ result += "\\";
+ }
+ result += paths.join("\\");
+ return result;
+};
+exports.join = join;
+
+/**
+ * Return the drive name of a path, or |null| if the path does
+ * not contain a drive name.
+ *
+ * Drive name appear either as "DriveName:..." (the return drive
+ * name includes the ":") or "\\\\DriveName..." (the returned drive name
+ * includes "\\\\").
+ */
+var winGetDrive = function(path) {
+ if (path == null) {
+ throw new TypeError("path is invalid");
+ }
+
+ if (path.startsWith("\\\\")) {
+ // UNC path
+ if (path.length == 2) {
+ return null;
+ }
+ let index = path.indexOf("\\", 2);
+ if (index == -1) {
+ return path;
+ }
+ return path.slice(0, index);
+ }
+ // Non-UNC path
+ let index = path.indexOf(":");
+ if (index <= 0) return null;
+ return path.slice(0, index + 1);
+};
+exports.winGetDrive = winGetDrive;
+
+/**
+ * Return |true| if the path is absolute, |false| otherwise.
+ *
+ * We consider that a path is absolute if it starts with "\\"
+ * or "driveletter:\\".
+ */
+var winIsAbsolute = function(path) {
+ let index = path.indexOf(":");
+ return path.length > index + 1 && path[index + 1] == "\\";
+};
+exports.winIsAbsolute = winIsAbsolute;
+
+/**
+ * Normalize a path by removing any unneeded ".", "..", "\\".
+ * Also convert any "/" to a "\\".
+ */
+var normalize = function(path) {
+ let stack = [];
+
+ if (!path.startsWith("\\\\")) {
+ // Normalize "/" to "\\"
+ path = path.replace(/\//g, "\\");
+ }
+
+ // Remove the drive (we will put it back at the end)
+ let root = this.winGetDrive(path);
+ if (root) {
+ path = path.slice(root.length);
+ }
+
+ // Remember whether we need to restore a leading "\\" or drive name.
+ let absolute = this.winIsAbsolute(path);
+
+ // And now, fill |stack| from the components,
+ // popping whenever there is a ".."
+ path.split("\\").forEach(function loop(v) {
+ switch (v) {
+ case "": case ".": // Ignore
+ break;
+ case "..":
+ if (stack.length == 0) {
+ if (absolute) {
+ throw new Error("Path is ill-formed: attempting to go past root");
+ } else {
+ stack.push("..");
+ }
+ } else {
+ if (stack[stack.length - 1] == "..") {
+ stack.push("..");
+ } else {
+ stack.pop();
+ }
+ }
+ break;
+ default:
+ stack.push(v);
+ }
+ });
+
+ // Put everything back together
+ let result = stack.join("\\");
+ if (absolute || root) {
+ result = "\\" + result;
+ }
+ if (root) {
+ result = root + result;
+ }
+ return result;
+};
+exports.normalize = normalize;
+
+/**
+ * Return the components of a path.
+ * You should generally apply this function to a normalized path.
+ *
+ * @return {{
+ * {bool} absolute |true| if the path is absolute, |false| otherwise
+ * {array} components the string components of the path
+ * {string?} winDrive the drive or server for this path
+ * }}
+ *
+ * Other implementations may add additional OS-specific informations.
+ */
+var split = function(path) {
+ return {
+ absolute: this.winIsAbsolute(path),
+ winDrive: this.winGetDrive(path),
+ components: path.split("\\")
+ };
+};
+exports.split = split;
+
+/**
+ * Return the file:// URI file path of the given local file path.
+ */
+// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
+var toFileURIExtraEncodings = {';': '%3b', '?': '%3F', '#': '%23'};
+var toFileURI = function toFileURI(path) {
+ // URI-escape forward slashes and convert backward slashes to forward
+ path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F');
+ // Per https://url.spec.whatwg.org we should not encode [] in the path
+ let dontNeedEscaping = {'%5B': '[', '%5D': ']'};
+ let uri = encodeURI(path).replace(/%(5B|5D)/gi,
+ match => dontNeedEscaping[match]);
+
+ // add a prefix, and encodeURI doesn't escape a few characters that we do
+ // want to escape, so fix that up
+ let prefix = "file:///";
+ uri = prefix + uri.replace(/[;?#]/g, match => toFileURIExtraEncodings[match]);
+
+ // turn e.g., file:///C: into file:///C:/
+ if (uri.charAt(uri.length - 1) === ':') {
+ uri += "/"
+ }
+
+ return uri;
+};
+exports.toFileURI = toFileURI;
+
+/**
+ * Returns the local file path from a given file URI.
+ */
+var fromFileURI = function fromFileURI(uri) {
+ let url = new URL(uri);
+ if (url.protocol != 'file:') {
+ throw new Error("fromFileURI expects a file URI");
+ }
+
+ // strip leading slash, since Windows paths don't start with one
+ uri = url.pathname.substr(1);
+
+ let path = decodeURI(uri);
+ // decode a few characters where URL's parsing is overzealous
+ path = path.replace(/%(3b|3f|23)/gi,
+ match => decodeURIComponent(match));
+ path = this.normalize(path);
+
+ // this.normalize() does not remove the trailing slash if the path
+ // component is a drive letter. eg. 'C:\'' will not get normalized.
+ if (path.endsWith(":\\")) {
+ path = path.substr(0, path.length - 1);
+ }
+ return this.normalize(path);
+};
+exports.fromFileURI = fromFileURI;
+
+/**
+* Utility function: Remove any leading/trailing backslashes
+* from a string.
+*/
+var trimBackslashes = function trimBackslashes(string) {
+ return string.replace(/^\\+|\\+$/g,'');
+};
+
+//////////// Boilerplate
+if (typeof Components != "undefined") {
+ this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
+ for (let symbol of EXPORTED_SYMBOLS) {
+ this[symbol] = exports[symbol];
+ }
+}