diff --git a/b2g/build.mk b/b2g/build.mk
index 136eefe3a4a..9eafae5028d 100644
--- a/b2g/build.mk
+++ b/b2g/build.mk
@@ -2,11 +2,16 @@
# 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/.
+include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
+
installer:
@$(MAKE) -C b2g/installer installer
package:
@$(MAKE) -C b2g/installer
+#ifdef FXOS_SIMULATOR
+ $(PYTHON) $(srcdir)/b2g/simulator/build_xpi.py $(MOZ_PKG_PLATFORM)
+#endif
install::
@echo 'B2G can't be installed directly.'
diff --git a/b2g/simulator/build_xpi.py b/b2g/simulator/build_xpi.py
new file mode 100644
index 00000000000..847b7aaf8f7
--- /dev/null
+++ b/b2g/simulator/build_xpi.py
@@ -0,0 +1,145 @@
+# 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/.
+
+# Generate xpi for the simulator addon by:
+# - building a special gaia profile for it, as we need:
+# * more languages, and,
+# * less apps
+# than b2g desktop's one
+# - retrieve usefull app version metadata from the build system
+# - finally, use addon sdk's cfx tool to build the addon xpi
+# that ships:
+# * a small firefox addon registering to the app manager
+# * b2g desktop runtime
+# * gaia profile
+
+import sys, os, re, subprocess
+from mozbuild.preprocessor import Preprocessor
+from mozbuild.base import MozbuildObject
+from mozbuild.util import ensureParentDir
+from zipfile import ZipFile
+from distutils.version import LooseVersion
+
+ftp_root_path = "/pub/mozilla.org/labs/fxos-simulator"
+UPDATE_LINK = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/%(xpi_name)s"
+UPDATE_URL = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/update.rdf"
+XPI_NAME = "fxos-simulator-%(version)s-%(platform)s.xpi"
+
+class GaiaBuilder(object):
+ def __init__(self, build, gaia_path):
+ self.build = build
+ self.gaia_path = gaia_path
+
+ def clean(self):
+ self.build._run_make(target="clean", directory=self.gaia_path)
+
+ def profile(self, env):
+ self.build._run_make(target="profile", directory=self.gaia_path, num_jobs=1, silent=False, append_env=env)
+
+ def override_prefs(self, srcfile):
+ # Note that each time we call `make profile` in gaia, a fresh new pref file is created
+ # cat srcfile >> profile/user.js
+ with open(os.path.join(self.gaia_path, "profile", "user.js"), "a") as userJs:
+ userJs.write(open(srcfile).read())
+
+def process_package_overload(src, dst, version, app_buildid):
+ ensureParentDir(dst)
+ # First replace numeric version like '1.3'
+ # Then replace with 'slashed' version like '1_4'
+ # Finally set the full length addon version like 1.3.20131230
+ defines = {
+ "NUM_VERSION": version,
+ "SLASH_VERSION": version.replace(".", "_"),
+ "FULL_VERSION": ("%s.%s" % (version, app_buildid))
+ }
+ pp = Preprocessor(defines=defines)
+ pp.do_filter("substitution")
+ with open(dst, "w") as output:
+ with open(src, "r") as input:
+ pp.processFile(input=input, output=output)
+
+def add_dir_to_zip(zip, top, pathInZip, blacklist=()):
+ zf = ZipFile(zip, "a")
+ for dirpath, subdirs, files in os.walk(top):
+ dir_relpath = os.path.relpath(dirpath, top)
+ if dir_relpath.startswith(blacklist):
+ continue
+ zf.write(dirpath, os.path.join(pathInZip, dir_relpath))
+ for filename in files:
+ relpath = os.path.join(dir_relpath, filename)
+ if relpath in blacklist:
+ continue
+ zf.write(os.path.join(dirpath, filename),
+ os.path.join(pathInZip, relpath))
+ zf.close()
+
+def main(platform):
+ build = MozbuildObject.from_environment()
+ topsrcdir = build.topsrcdir
+ distdir = build.distdir
+
+ srcdir = os.path.join(topsrcdir, "b2g", "simulator")
+
+ app_buildid = open(os.path.join(build.topobjdir, "config", "buildid")).read().strip()
+
+ # The simulator uses a shorter version string,
+ # it only keeps the major version digits A.B
+ # whereas MOZ_B2G_VERSION is A.B.C.D
+ b2g_version = build.config_environment.defines["MOZ_B2G_VERSION"].replace('"', '')
+ version = ".".join(str(n) for n in LooseVersion(b2g_version).version[0:2])
+
+ # Build a gaia profile specific to the simulator in order to:
+ # - disable the FTU
+ # - set custom prefs to enable devtools debugger server
+ # - set custom settings to disable lockscreen and screen timeout
+ # - only ship production apps
+ gaia_path = build.config_environment.substs["GAIADIR"]
+ builder = GaiaBuilder(build, gaia_path)
+ builder.clean()
+ env = {
+ "NOFTU": "1",
+ "GAIA_APP_TARGET": "production",
+ "SETTINGS_PATH": os.path.join(srcdir, "custom-settings.json")
+ }
+ builder.profile(env)
+ builder.override_prefs(os.path.join(srcdir, "custom-prefs.js"))
+
+ # Substitute version strings in the package manifest overload file
+ manifest_overload = os.path.join(build.topobjdir, "b2g", "simulator", "package-overload.json")
+ process_package_overload(os.path.join(srcdir, "package-overload.json.in"),
+ manifest_overload,
+ version,
+ app_buildid)
+
+ # Build the simulator addon xpi
+ xpi_name = XPI_NAME % {"version": version, "platform": platform}
+ xpi_path = os.path.join(distdir, xpi_name)
+
+ update_path = "%s/%s" % (version, platform)
+ update_link = UPDATE_LINK % {"update_path": update_path, "xpi_name": xpi_name}
+ update_url = UPDATE_URL % {"update_path": update_path}
+ subprocess.check_call([
+ build.virtualenv_manager.python_path, os.path.join(topsrcdir, "addon-sdk", "source", "bin", "cfx"), "xpi", \
+ "--pkgdir", srcdir, \
+ "--manifest-overload", manifest_overload, \
+ "--strip-sdk", \
+ "--update-link", update_link, \
+ "--update-url", update_url, \
+ "--static-args", "{\"label\": \"Firefox OS %s\"}" % version, \
+ "--output-file", xpi_path \
+ ])
+
+ # Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
+ add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia"))
+ # Then ship our own gaia profile
+ add_dir_to_zip(xpi_path, os.path.join(gaia_path, "profile"), "profile")
+
+if __name__ == '__main__':
+ if 2 != len(sys.argv):
+ print("""Usage:
+ python {0} MOZ_PKG_PLATFORM
+""".format(sys.argv[0]))
+ sys.exit(1)
+ main(*sys.argv[1:])
+
diff --git a/b2g/simulator/custom-prefs.js b/b2g/simulator/custom-prefs.js
new file mode 100644
index 00000000000..c3b4cb6fb61
--- /dev/null
+++ b/b2g/simulator/custom-prefs.js
@@ -0,0 +1,2 @@
+user_pref("devtools.debugger.prompt-connection", false);
+user_pref("devtools.debugger.forbid-certified-apps", false);
diff --git a/b2g/simulator/custom-settings.json b/b2g/simulator/custom-settings.json
new file mode 100644
index 00000000000..ea0264d9a50
--- /dev/null
+++ b/b2g/simulator/custom-settings.json
@@ -0,0 +1,6 @@
+{
+ "debugger.remote-mode": "adb-devtools",
+ "screen.timeout": 0,
+ "lockscreen.enabled": false,
+ "lockscreen.locked": false
+}
diff --git a/b2g/simulator/icon.png b/b2g/simulator/icon.png
new file mode 100644
index 00000000000..c4307fc8418
Binary files /dev/null and b/b2g/simulator/icon.png differ
diff --git a/b2g/simulator/icon64.png b/b2g/simulator/icon64.png
new file mode 100644
index 00000000000..275cd04a5ea
Binary files /dev/null and b/b2g/simulator/icon64.png differ
diff --git a/b2g/simulator/lib/main.js b/b2g/simulator/lib/main.js
new file mode 100644
index 00000000000..73333e075b4
--- /dev/null
+++ b/b2g/simulator/lib/main.js
@@ -0,0 +1,52 @@
+/* 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/.
+ */
+
+const { Cc, Ci, Cu } = require("chrome");
+
+const { SimulatorProcess } = require("./simulator-process");
+const Promise = require("sdk/core/promise");
+const Self = require("sdk/self");
+const System = require("sdk/system");
+const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
+
+let process;
+
+function launch({ port }) {
+ // Close already opened simulation
+ if (process) {
+ return close().then(launch.bind(null, { port: port }));
+ }
+
+ process = SimulatorProcess();
+ process.remoteDebuggerPort = port;
+ process.run();
+
+ return Promise.resolve();
+}
+
+function close() {
+ if (!process) {
+ return Promise.resolve();
+ }
+ let p = process;
+ process = null;
+ return p.kill();
+}
+
+
+// Load data generated at build time that
+// expose various information about the runtime we ship
+let appinfo = System.staticArgs;
+
+Simulator.register(appinfo.label, {
+ appinfo: appinfo,
+ launch: launch,
+ close: close
+});
+
+require("sdk/system/unload").when(function () {
+ Simulator.unregister(appinfo.label);
+ close();
+});
diff --git a/b2g/simulator/lib/simulator-process.js b/b2g/simulator/lib/simulator-process.js
new file mode 100644
index 00000000000..6d35a08330f
--- /dev/null
+++ b/b2g/simulator/lib/simulator-process.js
@@ -0,0 +1,182 @@
+/* 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';
+
+const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const { EventTarget } = require("sdk/event/target");
+const { emit, off } = require("sdk/event/core");
+const { Class } = require("sdk/core/heritage");
+const Environment = require("sdk/system/environment").env;
+const Runtime = require("sdk/system/runtime");
+const Self = require("sdk/self");
+const URL = require("sdk/url");
+const Subprocess = require("subprocess");
+const Promise = require("sdk/core/promise");
+
+const { rootURI: ROOT_URI } = require('@loader/options');
+const PROFILE_URL = ROOT_URI + "profile/";
+const BIN_URL = ROOT_URI + "b2g/";
+
+// Log subprocess error and debug messages to the console. This logs messages
+// for all consumers of the API. We trim the messages because they sometimes
+// have trailing newlines. And note that registerLogHandler actually registers
+// an error handler, despite its name.
+Subprocess.registerLogHandler(
+ function(s) console.error("subprocess: " + s.trim())
+);
+Subprocess.registerDebugHandler(
+ function(s) console.debug("subprocess: " + s.trim())
+);
+
+exports.SimulatorProcess = Class({
+ extends: EventTarget,
+ initialize: function initialize(options) {
+ EventTarget.prototype.initialize.call(this, options);
+
+ this.on("stdout", function onStdout(data) console.log(data.trim()));
+ this.on("stderr", function onStderr(data) console.error(data.trim()));
+ },
+
+ // check if b2g is running
+ get isRunning() !!this.process,
+
+ /**
+ * Start the process and connect the debugger client.
+ */
+ run: function() {
+ // kill before start if already running
+ if (this.process != null) {
+ this.process
+ .kill()
+ .then(this.run.bind(this));
+ return;
+ }
+
+ // resolve b2g binaries path (raise exception if not found)
+ let b2gExecutable = this.b2gExecutable;
+
+ this.once("stdout", function () {
+ if (Runtime.OS == "Darwin") {
+ console.debug("WORKAROUND run osascript to show b2g-desktop window"+
+ " on Runtime.OS=='Darwin'");
+ // Escape double quotes and escape characters for use in AppleScript.
+ let path = b2gExecutable.path
+ .replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
+
+ Subprocess.call({
+ command: "/usr/bin/osascript",
+ arguments: ["-e", 'tell application "' + path + '" to activate'],
+ });
+ }
+ });
+
+ let environment;
+ if (Runtime.OS == "Linux") {
+ environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path];
+ if ("DISPLAY" in Environment) {
+ environment.push("DISPLAY=" + Environment.DISPLAY);
+ }
+ }
+
+ // spawn a b2g instance
+ this.process = Subprocess.call({
+ command: b2gExecutable,
+ arguments: this.b2gArguments,
+ environment: environment,
+
+ // emit stdout event
+ stdout: (function(data) {
+ emit(this, "stdout", data);
+ }).bind(this),
+
+ // emit stderr event
+ stderr: (function(data) {
+ emit(this, "stderr", data);
+ }).bind(this),
+
+ // on b2g instance exit, reset tracked process, remoteDebuggerPort and
+ // shuttingDown flag, then finally emit an exit event
+ done: (function(result) {
+ console.log(this.b2gFilename + " terminated with " + result.exitCode);
+ this.process = null;
+ emit(this, "exit", result.exitCode);
+ }).bind(this)
+ });
+ },
+
+ // request a b2g instance kill
+ kill: function() {
+ let deferred = Promise.defer();
+ if (this.process) {
+ this.once("exit", (exitCode) => {
+ this.shuttingDown = false;
+ deferred.resolve(exitCode);
+ });
+ if (!this.shuttingDown) {
+ this.shuttingDown = true;
+ emit(this, "kill", null);
+ this.process.kill();
+ }
+ return deferred.promise;
+ } else {
+ return Promise.resolve(undefined);
+ }
+ },
+
+ // compute current b2g filename
+ get b2gFilename() {
+ return this._executable ? this._executableFilename : "B2G";
+ },
+
+ // compute current b2g file handle
+ get b2gExecutable() {
+ if (this._executable) return this._executable;
+
+ let bin = URL.toFilename(BIN_URL);
+ let executables = {
+ WINNT: "b2g-bin.exe",
+ Darwin: "Contents/MacOS/b2g-bin",
+ Linux: "b2g-bin",
+ };
+
+ console.log("bin url: "+bin+"/"+executables[Runtime.OS]);
+ let path = bin + "/" + executables[Runtime.OS];
+
+ let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ executable.initWithPath(path);
+
+ if (!executable.exists()) {
+ // B2G binaries not found
+ throw Error("b2g-desktop Executable not found.");
+ }
+
+ this._executable = executable;
+ this._executableFilename = "b2g-bin";
+
+ return executable;
+ },
+
+ // compute b2g CLI arguments
+ get b2gArguments() {
+ let args = [];
+
+ let profile = URL.toFilename(PROFILE_URL);
+ args.push("-profile", profile);
+ Cu.reportError(profile);
+
+ // NOTE: push dbgport option on the b2g-desktop commandline
+ args.push("-dbgport", "" + this.remoteDebuggerPort);
+
+ // Ignore eventual zombie instances of b2g that are left over
+ args.push("-no-remote");
+
+ return args;
+ },
+});
+
diff --git a/b2g/simulator/package-overload.json.in b/b2g/simulator/package-overload.json.in
new file mode 100644
index 00000000000..b107ba98c3f
--- /dev/null
+++ b/b2g/simulator/package-overload.json.in
@@ -0,0 +1,7 @@
+{
+ "id": "fxos_@SLASH_VERSION@_simulator@mozilla.org",
+ "name": "fxos_@SLASH_VERSION@_simulator",
+ "version": "@FULL_VERSION@",
+ "fullName": "Firefox OS @NUM_VERSION@ Simulator",
+ "description": "a Firefox OS @NUM_VERSION@ simulator"
+}
diff --git a/b2g/simulator/package.json b/b2g/simulator/package.json
new file mode 100644
index 00000000000..c2893e4cd4c
--- /dev/null
+++ b/b2g/simulator/package.json
@@ -0,0 +1,37 @@
+{
+ "id": "fxos_simulator@mozilla.org",
+ "name": "fxos_simulator",
+ "version": "1.0.dev",
+ "fullName": "Firefox OS Simulator",
+ "label": "Firefox OS",
+ "description": "a Firefox OS simulator",
+ "author": "Myk Melez (https://github.com/mykmelez)",
+ "contributors": [
+ "Alexandre Poirot (https://github.com/ochameau)",
+ "Anant Narayanan (https://github.com/anantn)",
+ "Brandon Kase (https://github.com/bkase)",
+ "Breck Yunits (https://github.com/breck7)",
+ "César Carruitero (https://github.com/ccarruitero)",
+ "David Gomes (https://github.com/davidgomes)",
+ "Fabrice Desré (https://github.com/fabricedesre)",
+ "Fraser Tweedale (https://github.com/frasertweedale)",
+ "Harald Kirschner (https://github.com/digitarald)",
+ "Jérémie Patonnier (https://github.com/JeremiePat)",
+ "J. Ryan Stinnett (https://github.com/jryans)",
+ "Kan-Ru Chen (陳侃如) (https://github.com/kanru)",
+ "Louis Stowasser (https://github.com/louisstow)",
+ "Luca Greco (https://github.com/rpl)",
+ "Matthew Claypotch (https://github.com/potch)",
+ "Matthew Riley MacPherson (https://github.com/tofumatt)",
+ "Nick Desaulniers (https://github.com/nickdesaulniers)",
+ "Soumen Ganguly (https://github.com/SoumenG)",
+ "Sudheesh Singanamalla (https://github.com/sudheesh001)",
+ "Victor Bjelkholm (https://github.com/VictorBjelkholm)"
+ ],
+ "permissions": {
+ "private-browsing": true
+ },
+ "license": "MPL 2.0",
+ "unpack": true,
+ "dependencies": ["subprocess"]
+}
diff --git a/b2g/simulator/packages/subprocess/README.md b/b2g/simulator/packages/subprocess/README.md
new file mode 100644
index 00000000000..7f13df6f9f2
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/README.md
@@ -0,0 +1,124 @@
+
What's that?
+Simply package enigmail hard work on providing IPC feature in mozilla platform.
+So we are able to launch child proccesses from javascript,
+and in our case, from addon-sdk libraries :)
+
+Sample of code:
+ This object allows to start a process, and read/write data to/from it
+ using stdin/stdout/stderr streams.
+ Usage example:
+
+ const subprocess = require("subprocess");
+ var p = subprocess.call({
+ command: '/bin/foo',
+ arguments: ['-v', 'foo'],
+ environment: [ "XYZ=abc", "MYVAR=def" ],
+ charset: 'UTF-8',
+ workdir: '/home/foo',
+ //stdin: "some value to write to stdin\nfoobar",
+ stdin: function(stdin) {
+ stdin.write("some value to write to stdin\nfoobar");
+ stdin.close();
+ },
+ stdout: function(data) {
+ dump("got data on stdout:" + data + "\n");
+ },
+ stderr: function(data) {
+ dump("got data on stderr:" + data + "\n");
+ },
+ done: function(result) {
+ dump("process terminated with " + result.exitCode + "\n");
+ },
+ mergeStderr: false
+ });
+ p.wait(); // wait for the subprocess to terminate
+ // this will block the main thread,
+ // only do if you can wait that long
+
+
+ Description of parameters:
+ --------------------------
+ Apart from , all arguments are optional.
+
+ command: either a |nsIFile| object pointing to an executable file or a
+ String containing the platform-dependent path to an executable
+ file.
+
+ arguments: optional string array containing the arguments to the command.
+
+ environment: optional string array containing environment variables to pass
+ to the command. The array elements must have the form
+ "VAR=data". Please note that if environment is defined, it
+ replaces any existing environment variables for the subprocess.
+
+ charset: Output is decoded with given charset and a string is returned.
+ If charset is undefined, "UTF-8" is used as default.
+ To get binary data, set this to null and the returned string
+ is not decoded in any way.
+
+ workdir: optional; String containing the platform-dependent path to a
+ directory to become the current working directory of the subprocess.
+
+ stdin: optional input data for the process to be passed on standard
+ input. stdin can either be a string or a function.
+ A |string| gets written to stdin and stdin gets closed;
+ A |function| gets passed an object with write and close function.
+ Please note that the write() function will return almost immediately;
+ data is always written asynchronously on a separate thread.
+
+ stdout: an optional function that can receive output data from the
+ process. The stdout-function is called asynchronously; it can be
+ called mutliple times during the execution of a process.
+ At a minimum at each occurance of \n or \r.
+ Please note that null-characters might need to be escaped
+ with something like 'data.replace(/\0/g, "\\0");'.
+
+ stderr: an optional function that can receive stderr data from the
+ process. The stderr-function is called asynchronously; it can be
+ called mutliple times during the execution of a process. Please
+ note that null-characters might need to be escaped with
+ something like 'data.replace(/\0/g, "\\0");'.
+ (on windows it only gets called once right now)
+
+ done: optional function that is called when the process has terminated.
+ The exit code from the process available via result.exitCode. If
+ stdout is not defined, then the output from stdout is available
+ via result.stdout. stderr data is in result.stderr
+
+ mergeStderr: optional boolean value. If true, stderr is merged with stdout;
+ no data will be provided to stderr.
+
+
+ Description of object returned by subprocess.call(...)
+ ------------------------------------------------------
+ The object returned by subprocess.call offers a few methods that can be
+ executed:
+
+ wait(): waits for the subprocess to terminate. It is not required to use
+ wait; done will be called in any case when the subprocess terminated.
+
+ kill(hardKill): kill the subprocess. Any open pipes will be closed and
+ done will be called.
+ hardKill [ignored on Windows]:
+ - false: signal the process terminate (SIGTERM)
+ - true: kill the process (SIGKILL)
+
+
+ Other methods in subprocess
+ ---------------------------
+
+ registerDebugHandler(functionRef): register a handler that is called to get
+ debugging information
+ registerLogHandler(functionRef): register a handler that is called to get error
+ messages
+
+ example:
+ subprocess.registerLogHandler( function(s) { dump(s); } );
+
+
+Credits:
+All enigmail team working on IPC component.
+ The Initial Developer of this code is Jan Gerber.
+ Portions created by Jan Gerber ,
+ Patrick Brunschwig (author of almost all code) ,
+ Ramalingam Saravanan (from enigmail team)
diff --git a/b2g/simulator/packages/subprocess/lib/subprocess.js b/b2g/simulator/packages/subprocess/lib/subprocess.js
new file mode 100644
index 00000000000..0f88a860bd8
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/lib/subprocess.js
@@ -0,0 +1,1543 @@
+/* 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';
+
+const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
+
+Cu.import("resource://gre/modules/ctypes.jsm");
+
+const NS_LOCAL_FILE = "@mozilla.org/file/local;1";
+
+const Runtime = require("sdk/system/runtime");
+const Environment = require("sdk/system/environment").env;
+const DEFAULT_ENVIRONMENT = [];
+if (Runtime.OS == "Linux" && "DISPLAY" in Environment) {
+ DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY);
+}
+
+/*
+Fake require statements to ensure worker scripts are packaged:
+require("subprocess_worker_win.js");
+require("subprocess_worker_unix.js");
+*/
+const URL_PREFIX = module.uri.replace(/subprocess\.js/, "");
+const WORKER_URL_WIN = URL_PREFIX + "subprocess_worker_win.js";
+const WORKER_URL_UNIX = URL_PREFIX + "subprocess_worker_unix.js";
+
+//Windows API definitions
+if (ctypes.size_t.size == 8) {
+ var WinABI = ctypes.default_abi;
+} else {
+ var WinABI = ctypes.winapi_abi;
+}
+const WORD = ctypes.uint16_t;
+const DWORD = ctypes.uint32_t;
+const LPDWORD = DWORD.ptr;
+
+const UINT = ctypes.unsigned_int;
+const BOOL = ctypes.bool;
+const HANDLE = ctypes.size_t;
+const HWND = HANDLE;
+const HMODULE = HANDLE;
+const WPARAM = ctypes.size_t;
+const LPARAM = ctypes.size_t;
+const LRESULT = ctypes.size_t;
+const ULONG_PTR = ctypes.uintptr_t;
+const PVOID = ctypes.voidptr_t;
+const LPVOID = PVOID;
+const LPCTSTR = ctypes.jschar.ptr;
+const LPCWSTR = ctypes.jschar.ptr;
+const LPTSTR = ctypes.jschar.ptr;
+const LPSTR = ctypes.char.ptr;
+const LPCSTR = ctypes.char.ptr;
+const LPBYTE = ctypes.char.ptr;
+
+const CREATE_NEW_CONSOLE = 0x00000010;
+const CREATE_NO_WINDOW = 0x08000000;
+const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
+const STARTF_USESHOWWINDOW = 0x00000001;
+const STARTF_USESTDHANDLES = 0x00000100;
+const SW_HIDE = 0;
+const DUPLICATE_SAME_ACCESS = 0x00000002;
+const STILL_ACTIVE = 259;
+const INFINITE = DWORD(0xFFFFFFFF);
+const WAIT_TIMEOUT = 0x00000102;
+
+/*
+typedef struct _SECURITY_ATTRIBUTES {
+ DWORD nLength;
+ LPVOID lpSecurityDescriptor;
+ BOOL bInheritHandle;
+} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
+*/
+const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
+ {"nLength": DWORD},
+ {"lpSecurityDescriptor": LPVOID},
+ {"bInheritHandle": BOOL},
+]);
+
+/*
+typedef struct _STARTUPINFO {
+ DWORD cb;
+ LPTSTR lpReserved;
+ LPTSTR lpDesktop;
+ LPTSTR lpTitle;
+ DWORD dwX;
+ DWORD dwY;
+ DWORD dwXSize;
+ DWORD dwYSize;
+ DWORD dwXCountChars;
+ DWORD dwYCountChars;
+ DWORD dwFillAttribute;
+ DWORD dwFlags;
+ WORD wShowWindow;
+ WORD cbReserved2;
+ LPBYTE lpReserved2;
+ HANDLE hStdInput;
+ HANDLE hStdOutput;
+ HANDLE hStdError;
+} STARTUPINFO, *LPSTARTUPINFO;
+*/
+const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
+ {"cb": DWORD},
+ {"lpReserved": LPTSTR},
+ {"lpDesktop": LPTSTR},
+ {"lpTitle": LPTSTR},
+ {"dwX": DWORD},
+ {"dwY": DWORD},
+ {"dwXSize": DWORD},
+ {"dwYSize": DWORD},
+ {"dwXCountChars": DWORD},
+ {"dwYCountChars": DWORD},
+ {"dwFillAttribute": DWORD},
+ {"dwFlags": DWORD},
+ {"wShowWindow": WORD},
+ {"cbReserved2": WORD},
+ {"lpReserved2": LPBYTE},
+ {"hStdInput": HANDLE},
+ {"hStdOutput": HANDLE},
+ {"hStdError": HANDLE},
+]);
+
+/*
+typedef struct _PROCESS_INFORMATION {
+ HANDLE hProcess;
+ HANDLE hThread;
+ DWORD dwProcessId;
+ DWORD dwThreadId;
+} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
+*/
+const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
+ {"hProcess": HANDLE},
+ {"hThread": HANDLE},
+ {"dwProcessId": DWORD},
+ {"dwThreadId": DWORD},
+]);
+
+/*
+typedef struct _OVERLAPPED {
+ ULONG_PTR Internal;
+ ULONG_PTR InternalHigh;
+ union {
+ struct {
+ DWORD Offset;
+ DWORD OffsetHigh;
+ };
+ PVOID Pointer;
+ };
+ HANDLE hEvent;
+} OVERLAPPED, *LPOVERLAPPED;
+*/
+const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
+
+//UNIX definitions
+const pid_t = ctypes.int32_t;
+const WNOHANG = 1;
+const F_GETFD = 1;
+const F_SETFL = 4;
+
+const LIBNAME = 0;
+const O_NONBLOCK = 1;
+const RLIM_T = 2;
+const RLIMIT_NOFILE = 3;
+
+function getPlatformValue(valueType) {
+
+ if (! gXulRuntime)
+ gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+
+ const platformDefaults = {
+ // Windows API:
+ 'winnt': [ 'kernel32.dll' ],
+
+ // Unix API:
+ // library name O_NONBLOCK RLIM_T RLIMIT_NOFILE
+ 'darwin': [ 'libc.dylib', 0x04 , ctypes.uint64_t , 8 ],
+ 'linux': [ 'libc.so.6', 2024 , ctypes.unsigned_long, 7 ],
+ 'freebsd': [ 'libc.so.7', 0x04 , ctypes.int64_t , 8 ],
+ 'openbsd': [ 'libc.so.61.0', 0x04 , ctypes.int64_t , 8 ],
+ 'sunos': [ 'libc.so', 0x80 , ctypes.unsigned_long, 5 ]
+ }
+
+ return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
+}
+
+
+var gDebugFunc = null,
+ gLogFunc = null,
+ gXulRuntime = null;
+
+function LogError(s) {
+ if (gLogFunc)
+ gLogFunc(s);
+ else
+ dump(s);
+}
+
+function debugLog(s) {
+ if (gDebugFunc)
+ gDebugFunc(s);
+}
+
+function setTimeout(callback, timeout) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+};
+
+function getBytes(data) {
+ var string = '';
+ data.forEach(function(x) { string += String.fromCharCode(x < 0 ? x + 256 : x) });
+ return string;
+}
+
+function readString(data, length, charset) {
+ var string = '', bytes = [];
+ for(var i = 0;i < length; i++) {
+ if(data[i] == 0 && charset !== null) // stop on NULL character for non-binary data
+ break
+ bytes.push(data[i]);
+ }
+ if (!bytes || bytes.length == 0)
+ return string;
+ if(charset === null) {
+ return bytes;
+ }
+ return convertBytes(bytes, charset);
+}
+
+function convertBytes(bytes, charset) {
+ var string = '';
+ charset = charset || 'UTF-8';
+ var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .getService(Ci.nsIScriptableUnicodeConverter);
+ try {
+ unicodeConv.charset = charset;
+ string = unicodeConv.convertFromByteArray(bytes, bytes.length);
+ } catch (ex) {
+ LogError("String conversion failed: "+ex.toString()+"\n")
+ string = '';
+ }
+ string += unicodeConv.Finish();
+ return string;
+}
+
+
+// temporary solution for removal of nsILocalFile
+function getLocalFileApi() {
+ if ("nsILocalFile" in Ci) {
+ return Ci.nsILocalFile;
+ }
+ else
+ return Ci.nsIFile;
+}
+
+function getCommandStr(command) {
+ let commandStr = null;
+ if (typeof(command) == "string") {
+ let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
+ file.initWithPath(command);
+ if (! (file.isExecutable() && file.isFile()))
+ throw("File '"+command+"' is not an executable file");
+ commandStr = command;
+ }
+ else {
+ if (! (command.isExecutable() && command.isFile()))
+ throw("File '"+command.path+"' is not an executable file");
+ commandStr = command.path;
+ }
+
+ return commandStr;
+}
+
+function getWorkDir(workdir) {
+ let workdirStr = null;
+ if (typeof(workdir) == "string") {
+ let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
+ file.initWithPath(workdir);
+ if (! (file.isDirectory()))
+ throw("Directory '"+workdir+"' does not exist");
+ workdirStr = workdir;
+ }
+ else if (workdir) {
+ if (! workdir.isDirectory())
+ throw("Directory '"+workdir.path+"' does not exist");
+ workdirStr = workdir.path;
+ }
+ return workdirStr;
+}
+
+
+var subprocess = {
+ call: function(options) {
+ options.mergeStderr = options.mergeStderr || false;
+ options.workdir = options.workdir || null;
+ options.environment = options.environment || DEFAULT_ENVIRONMENT;
+ if (options.arguments) {
+ var args = options.arguments;
+ options.arguments = [];
+ args.forEach(function(argument) {
+ options.arguments.push(argument);
+ });
+ } else {
+ options.arguments = [];
+ }
+
+ options.libc = getPlatformValue(LIBNAME);
+
+ if (gXulRuntime.OS.substring(0, 3) == "WIN") {
+ return subprocess_win32(options);
+ } else {
+ return subprocess_unix(options);
+ }
+
+ },
+ registerDebugHandler: function(func) {
+ gDebugFunc = func;
+ },
+ registerLogHandler: function(func) {
+ gLogFunc = func;
+ }
+};
+
+
+
+function subprocess_win32(options) {
+ var kernel32dll = ctypes.open(options.libc),
+ hChildProcess,
+ active = true,
+ done = false,
+ exitCode = -1,
+ child = {},
+ stdinWorker = null,
+ stdoutWorker = null,
+ stderrWorker = null,
+ pendingWriteCount = 0,
+ readers = options.mergeStderr ? 1 : 2,
+ stdinOpenState = 2,
+ error = '',
+ output = '';
+
+ // stdin pipe states
+ const OPEN = 2;
+ const CLOSEABLE = 1;
+ const CLOSED = 0;
+
+ //api declarations
+ /*
+ BOOL WINAPI CloseHandle(
+ __in HANDLE hObject
+ );
+ */
+ var CloseHandle = kernel32dll.declare("CloseHandle",
+ WinABI,
+ BOOL,
+ HANDLE
+ );
+
+ /*
+ BOOL WINAPI CreateProcess(
+ __in_opt LPCTSTR lpApplicationName,
+ __inout_opt LPTSTR lpCommandLine,
+ __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ __in BOOL bInheritHandles,
+ __in DWORD dwCreationFlags,
+ __in_opt LPVOID lpEnvironment,
+ __in_opt LPCTSTR lpCurrentDirectory,
+ __in LPSTARTUPINFO lpStartupInfo,
+ __out LPPROCESS_INFORMATION lpProcessInformation
+ );
+ */
+ var CreateProcessW = kernel32dll.declare("CreateProcessW",
+ WinABI,
+ BOOL,
+ LPCTSTR,
+ LPTSTR,
+ SECURITY_ATTRIBUTES.ptr,
+ SECURITY_ATTRIBUTES.ptr,
+ BOOL,
+ DWORD,
+ LPVOID,
+ LPCTSTR,
+ STARTUPINFO.ptr,
+ PROCESS_INFORMATION.ptr
+ );
+
+// /*
+// BOOL WINAPI ReadFile(
+// __in HANDLE hFile,
+// __out LPVOID ReadFileBuffer,
+// __in DWORD nNumberOfBytesToRead,
+// __out_opt LPDWORD lpNumberOfBytesRead,
+// __inout_opt LPOVERLAPPED lpOverlapped
+// );
+// */
+// var ReadFileBufferSize = 1024,
+// ReadFileBuffer = ctypes.char.array(ReadFileBufferSize),
+// ReadFile = kernel32dll.declare("ReadFile",
+// WinABI,
+// BOOL,
+// HANDLE,
+// ReadFileBuffer,
+// DWORD,
+// LPDWORD,
+// OVERLAPPED.ptr
+// );
+//
+// /*
+// BOOL WINAPI PeekNamedPipe(
+// __in HANDLE hNamedPipe,
+// __out_opt LPVOID lpBuffer,
+// __in DWORD nBufferSize,
+// __out_opt LPDWORD lpBytesRead,
+// __out_opt LPDWORD lpTotalBytesAvail,
+// __out_opt LPDWORD lpBytesLeftThisMessage
+// );
+// */
+// var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe",
+// WinABI,
+// BOOL,
+// HANDLE,
+// ReadFileBuffer,
+// DWORD,
+// LPDWORD,
+// LPDWORD,
+// LPDWORD
+// );
+//
+// /*
+// BOOL WINAPI WriteFile(
+// __in HANDLE hFile,
+// __in LPCVOID lpBuffer,
+// __in DWORD nNumberOfBytesToWrite,
+// __out_opt LPDWORD lpNumberOfBytesWritten,
+// __inout_opt LPOVERLAPPED lpOverlapped
+// );
+// */
+// var WriteFile = kernel32dll.declare("WriteFile",
+// WinABI,
+// BOOL,
+// HANDLE,
+// ctypes.char.ptr,
+// DWORD,
+// LPDWORD,
+// OVERLAPPED.ptr
+// );
+
+ /*
+ BOOL WINAPI CreatePipe(
+ __out PHANDLE hReadPipe,
+ __out PHANDLE hWritePipe,
+ __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
+ __in DWORD nSize
+ );
+ */
+ var CreatePipe = kernel32dll.declare("CreatePipe",
+ WinABI,
+ BOOL,
+ HANDLE.ptr,
+ HANDLE.ptr,
+ SECURITY_ATTRIBUTES.ptr,
+ DWORD
+ );
+
+ /*
+ HANDLE WINAPI GetCurrentProcess(void);
+ */
+ var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess",
+ WinABI,
+ HANDLE
+ );
+
+ /*
+ DWORD WINAPI GetLastError(void);
+ */
+ var GetLastError = kernel32dll.declare("GetLastError",
+ WinABI,
+ DWORD
+ );
+
+ /*
+ BOOL WINAPI DuplicateHandle(
+ __in HANDLE hSourceProcessHandle,
+ __in HANDLE hSourceHandle,
+ __in HANDLE hTargetProcessHandle,
+ __out LPHANDLE lpTargetHandle,
+ __in DWORD dwDesiredAccess,
+ __in BOOL bInheritHandle,
+ __in DWORD dwOptions
+ );
+ */
+ var DuplicateHandle = kernel32dll.declare("DuplicateHandle",
+ WinABI,
+ BOOL,
+ HANDLE,
+ HANDLE,
+ HANDLE,
+ HANDLE.ptr,
+ DWORD,
+ BOOL,
+ DWORD
+ );
+
+
+ /*
+ BOOL WINAPI GetExitCodeProcess(
+ __in HANDLE hProcess,
+ __out LPDWORD lpExitCode
+ );
+ */
+ var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess",
+ WinABI,
+ BOOL,
+ HANDLE,
+ LPDWORD
+ );
+
+ /*
+ DWORD WINAPI WaitForSingleObject(
+ __in HANDLE hHandle,
+ __in DWORD dwMilliseconds
+ );
+ */
+ var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject",
+ WinABI,
+ DWORD,
+ HANDLE,
+ DWORD
+ );
+
+ /*
+ BOOL WINAPI TerminateProcess(
+ __in HANDLE hProcess,
+ __in UINT uExitCode
+ );
+ */
+ var TerminateProcess = kernel32dll.declare("TerminateProcess",
+ WinABI,
+ BOOL,
+ HANDLE,
+ UINT
+ );
+
+ //functions
+ function popen(command, workdir, args, environment, child) {
+ //escape arguments
+ args.unshift(command);
+ for (var i = 0; i < args.length; i++) {
+ if (typeof args[i] != "string") { args[i] = args[i].toString(); }
+ /* quote arguments with spaces */
+ if (args[i].match(/\s/)) {
+ args[i] = "\"" + args[i] + "\"";
+ }
+ /* If backslash is followed by a quote, double it */
+ args[i] = args[i].replace(/\\\"/g, "\\\\\"");
+ }
+ command = args.join(' ');
+
+ environment = environment || [];
+ if(environment.length) {
+ //An environment block consists of
+ //a null-terminated block of null-terminated strings.
+ //Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar
+ environment = ctypes.jschar.array()(environment.join('\0') + '\0');
+ } else {
+ environment = null;
+ }
+
+ var hOutputReadTmp = new HANDLE(),
+ hOutputRead = new HANDLE(),
+ hOutputWrite = new HANDLE();
+
+ var hErrorRead = new HANDLE(),
+ hErrorReadTmp = new HANDLE(),
+ hErrorWrite = new HANDLE();
+
+ var hInputRead = new HANDLE(),
+ hInputWriteTmp = new HANDLE(),
+ hInputWrite = new HANDLE();
+
+ // Set up the security attributes struct.
+ var sa = new SECURITY_ATTRIBUTES();
+ sa.nLength = SECURITY_ATTRIBUTES.size;
+ sa.lpSecurityDescriptor = null;
+ sa.bInheritHandle = true;
+
+ // Create output pipe.
+
+ if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0))
+ LogError('CreatePipe hOutputReadTmp failed');
+
+ if(options.mergeStderr) {
+ // Create a duplicate of the output write handle for the std error
+ // write handle. This is necessary in case the child application
+ // closes one of its std output handles.
+ if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
+ GetCurrentProcess(), hErrorWrite.address(), 0,
+ true, DUPLICATE_SAME_ACCESS))
+ LogError("DuplicateHandle hOutputWrite failed");
+ } else {
+ // Create error pipe.
+ if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
+ LogError('CreatePipe hErrorReadTmp failed');
+ }
+
+ // Create input pipe.
+ if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
+ LogError("CreatePipe hInputRead failed");
+
+ // Create new output/error read handle and the input write handles. Set
+ // the Properties to FALSE. Otherwise, the child inherits the
+ // properties and, as a result, non-closeable handles to the pipes
+ // are created.
+ if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
+ GetCurrentProcess(),
+ hOutputRead.address(), // Address of new handle.
+ 0, false, // Make it uninheritable.
+ DUPLICATE_SAME_ACCESS))
+ LogError("DupliateHandle hOutputReadTmp failed");
+
+ if(!options.mergeStderr) {
+ if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
+ GetCurrentProcess(),
+ hErrorRead.address(), // Address of new handle.
+ 0, false, // Make it uninheritable.
+ DUPLICATE_SAME_ACCESS))
+ LogError("DupliateHandle hErrorReadTmp failed");
+ }
+ if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
+ GetCurrentProcess(),
+ hInputWrite.address(), // Address of new handle.
+ 0, false, // Make it uninheritable.
+ DUPLICATE_SAME_ACCESS))
+ LogError("DupliateHandle hInputWriteTmp failed");
+
+ // Close inheritable copies of the handles.
+ if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
+ if(!options.mergeStderr)
+ if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
+ if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
+
+ var pi = new PROCESS_INFORMATION();
+ var si = new STARTUPINFO();
+
+ si.cb = STARTUPINFO.size;
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = hInputRead;
+ si.hStdOutput = hOutputWrite;
+ si.hStdError = hErrorWrite;
+
+ // Launch the process
+ if(!CreateProcessW(null, // executable name
+ command, // command buffer
+ null, // process security attribute
+ null, // thread security attribute
+ true, // inherits system handles
+ CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
+ environment, // envrionment block
+ workdir, // set as current directory
+ si.address(), // (in) startup information
+ pi.address() // (out) process information
+ ))
+ throw("Fatal - Could not launch subprocess '"+command+"'");
+
+ // Close any unnecessary handles.
+ if (!CloseHandle(pi.hThread))
+ LogError("CloseHandle pi.hThread failed");
+
+ // Close pipe handles (do not continue to modify the parent).
+ // You need to make sure that no handles to the write end of the
+ // output pipe are maintained in this process or else the pipe will
+ // not close when the child process exits and the ReadFile will hang.
+ if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
+ if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
+ if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
+
+ //return values
+ child.stdin = hInputWrite;
+ child.stdout = hOutputRead;
+ child.stderr = options.mergeStderr ? undefined : hErrorRead;
+ child.process = pi.hProcess;
+ return pi.hProcess;
+ }
+
+ /*
+ * createStdinWriter ()
+ *
+ * Create a ChromeWorker object for writing data to the subprocess' stdin
+ * pipe. The ChromeWorker object lives on a separate thread; this avoids
+ * internal deadlocks.
+ */
+ function createStdinWriter() {
+ debugLog("Creating new stdin worker\n");
+ stdinWorker = new ChromeWorker(WORKER_URL_WIN);
+ stdinWorker.onmessage = function(event) {
+ switch(event.data) {
+ case "WriteOK":
+ pendingWriteCount--;
+ debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
+ break;
+ case "ClosedOK":
+ stdinOpenState = CLOSED;
+ debugLog("Stdin pipe closed\n");
+ break;
+ default:
+ debugLog("got msg from stdinWorker: "+event.data+"\n");
+ }
+ }
+ stdinWorker.onerror = function(error) {
+ pendingWriteCount--;
+ LogError("got error from stdinWorker: "+error.message+"\n");
+ }
+
+ stdinWorker.postMessage({msg: "init", libc: options.libc});
+ }
+
+ /*
+ * writeStdin()
+ * @data: String containing the data to write
+ *
+ * Write data to the subprocess' stdin (equals to sending a request to the
+ * ChromeWorker object to write the data).
+ */
+ function writeStdin(data) {
+ ++pendingWriteCount;
+ debugLog("sending "+data.length+" bytes to stdinWorker\n");
+ var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
+
+ stdinWorker.postMessage({
+ msg: 'write',
+ pipe: pipePtr,
+ data: data
+ });
+ }
+
+ /*
+ * closeStdinHandle()
+ *
+ * Close the stdin pipe, either directly or by requesting the ChromeWorker to
+ * close the pipe. The ChromeWorker will only close the pipe after the last write
+ * request process is done.
+ */
+
+ function closeStdinHandle() {
+ debugLog("trying to close stdin\n");
+ if (stdinOpenState != OPEN) return;
+ stdinOpenState = CLOSEABLE;
+
+ if (stdinWorker) {
+ debugLog("sending close stdin to worker\n");
+ var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
+ stdinWorker.postMessage({
+ msg: 'close',
+ pipe: pipePtr
+ });
+ }
+ else {
+ stdinOpenState = CLOSED;
+ debugLog("Closing Stdin\n");
+ CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed");
+ }
+ }
+
+
+ /*
+ * createReader(pipe, name)
+ *
+ * @pipe: handle to the pipe
+ * @name: String containing the pipe name (stdout or stderr)
+ *
+ * Create a ChromeWorker object for reading data asynchronously from
+ * the pipe (i.e. on a separate thread), and passing the result back to
+ * the caller.
+ */
+ function createReader(pipe, name, callbackFunc) {
+ var worker = new ChromeWorker(WORKER_URL_WIN);
+ worker.onmessage = function(event) {
+ switch(event.data.msg) {
+ case "data":
+ debugLog("got "+event.data.count+" bytes from "+name+"\n");
+ var data = '';
+ if (options.charset === null) {
+ data = getBytes(event.data.data);
+ }
+ else {
+ try {
+ data = convertBytes(event.data.data, options.charset);
+ }
+ catch(ex) {
+ console.warn("error decoding output: " + ex);
+ data = getBytes(event.data.data);
+ }
+ }
+
+ callbackFunc(data);
+ break;
+ case "done":
+ debugLog("Pipe "+name+" closed\n");
+ --readers;
+ if (readers == 0) cleanup();
+ break;
+ default:
+ debugLog("Got msg from "+name+": "+event.data.data+"\n");
+ }
+ }
+
+ worker.onerror = function(errorMsg) {
+ LogError("Got error from "+name+": "+errorMsg.message);
+ }
+
+ var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
+
+ worker.postMessage({
+ msg: 'read',
+ pipe: pipePtr,
+ libc: options.libc,
+ charset: options.charset === null ? "null" : options.charset,
+ name: name
+ });
+
+ return worker;
+ }
+
+ /*
+ * readPipes()
+ *
+ * Open the pipes for reading from stdout and stderr
+ */
+ function readPipes() {
+
+ stdoutWorker = createReader(child.stdout, "stdout", function (data) {
+ if(options.stdout) {
+ setTimeout(function() {
+ options.stdout(data);
+ }, 0);
+ } else {
+ output += data;
+ }
+ });
+
+
+ if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
+ if(options.stderr) {
+ setTimeout(function() {
+ options.stderr(data);
+ }, 0);
+ } else {
+ error += data;
+ }
+ });
+ }
+
+ /*
+ * cleanup()
+ *
+ * close stdin if needed, get the exit code from the subprocess and invoke
+ * the caller's done() function.
+ *
+ * Note: because stdout() and stderr() are called using setTimeout, we need to
+ * do the same here in order to guarantee the message sequence.
+ */
+ function cleanup() {
+ debugLog("Cleanup called\n");
+ if(active) {
+ active = false;
+
+ closeStdinHandle(); // should only be required in case of errors
+
+ var exit = new DWORD();
+ GetExitCodeProcess(child.process, exit.address());
+ exitCode = exit.value;
+
+ if (stdinWorker)
+ stdinWorker.postMessage({msg: 'stop'})
+
+ setTimeout(function _done() {
+ if (options.done) {
+ try {
+ options.done({
+ exitCode: exitCode,
+ stdout: output,
+ stderr: error,
+ });
+ }
+ catch (ex) {
+ // prevent from blocking if options.done() throws an error
+ done = true;
+ throw ex;
+ }
+ }
+ done = true;
+ }, 0);
+ kernel32dll.close();
+ }
+ }
+
+ var cmdStr = getCommandStr(options.command);
+ var workDir = getWorkDir(options.workdir);
+
+ //main
+ hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
+
+ readPipes();
+
+ if (options.stdin) {
+ createStdinWriter();
+
+ if(typeof(options.stdin) == 'function') {
+ try {
+ options.stdin({
+ write: function(data) {
+ writeStdin(data);
+ },
+ close: function() {
+ closeStdinHandle();
+ }
+ });
+ }
+ catch (ex) {
+ // prevent from failing if options.stdin() throws an exception
+ closeStdinHandle();
+ throw ex;
+ }
+ } else {
+ writeStdin(options.stdin);
+ closeStdinHandle();
+ }
+ }
+ else
+ closeStdinHandle();
+
+ return {
+ kill: function(hardKill) {
+ // hardKill is currently ignored on Windows
+ var r = !!TerminateProcess(child.process, 255);
+ cleanup(-1);
+ return r;
+ },
+ wait: function() {
+ // wait for async operations to complete
+ var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
+ while (!done) thread.processNextEvent(true);
+
+ return exitCode;
+ }
+ }
+}
+
+
+function subprocess_unix(options) {
+ // stdin pipe states
+ const OPEN = 2;
+ const CLOSEABLE = 1;
+ const CLOSED = 0;
+
+ var libc = ctypes.open(options.libc),
+ active = true,
+ done = false,
+ exitCode = -1,
+ workerExitCode = 0,
+ child = {},
+ pid = -1,
+ stdinWorker = null,
+ stdoutWorker = null,
+ stderrWorker = null,
+ pendingWriteCount = 0,
+ readers = options.mergeStderr ? 1 : 2,
+ stdinOpenState = OPEN,
+ error = '',
+ output = '';
+
+ //api declarations
+
+ //pid_t fork(void);
+ var fork = libc.declare("fork",
+ ctypes.default_abi,
+ pid_t
+ );
+
+ //NULL terminated array of strings, argv[0] will be command >> + 2
+ var argv = ctypes.char.ptr.array(options.arguments.length + 2);
+ var envp = ctypes.char.ptr.array(options.environment.length + 1);
+
+ // posix_spawn_file_actions_t is a complex struct that may be different on
+ // each platform. We do not care about its attributes, we don't need to
+ // get access to them, but we do need to allocate the right amount
+ // of memory for it.
+ // Bug 936297 - Use OS.File internals to fetch
+ // sizeof(posix_spawn_file_actions_t)
+ var { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+ var sizeof_file_actions_t = OS.Constants.libc.OSFILE_SIZEOF_DIRENT;
+ var posix_spawn_file_actions_t = ctypes.uint8_t.array(sizeof_file_actions_t);
+
+ //int posix_spawn(pid_t *restrict pid, const char *restrict path,
+ // const posix_spawn_file_actions_t *file_actions,
+ // const posix_spawnattr_t *restrict attrp,
+ // char *const argv[restrict], char *const envp[restrict]);
+ var posix_spawn = libc.declare("posix_spawn",
+ ctypes.default_abi,
+ ctypes.int,
+ pid_t.ptr,
+ ctypes.char.ptr,
+ posix_spawn_file_actions_t.ptr,
+ ctypes.voidptr_t,
+ argv,
+ envp
+ );
+
+ //int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
+ var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init",
+ ctypes.default_abi,
+ ctypes.int,
+ posix_spawn_file_actions_t.ptr
+ );
+
+ //int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
+ var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy",
+ ctypes.default_abi,
+ ctypes.int,
+ posix_spawn_file_actions_t.ptr
+ );
+
+ // int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *
+ // file_actions, int fildes, int newfildes);
+ var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2",
+ ctypes.default_abi,
+ ctypes.int,
+ posix_spawn_file_actions_t.ptr,
+ ctypes.int,
+ ctypes.int
+ );
+
+ // int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *
+ // file_actions, int fildes);
+ var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose",
+ ctypes.default_abi,
+ ctypes.int,
+ posix_spawn_file_actions_t.ptr,
+ ctypes.int
+ );
+
+ //int pipe(int pipefd[2]);
+ var pipefd = ctypes.int.array(2);
+ var pipe = libc.declare("pipe",
+ ctypes.default_abi,
+ ctypes.int,
+ pipefd
+ );
+
+ //int close(int fd);
+ var close = libc.declare("close",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int
+ );
+
+ //pid_t waitpid(pid_t pid, int *status, int options);
+ var waitpid = libc.declare("waitpid",
+ ctypes.default_abi,
+ pid_t,
+ pid_t,
+ ctypes.int.ptr,
+ ctypes.int
+ );
+
+ //int kill(pid_t pid, int sig);
+ var kill = libc.declare("kill",
+ ctypes.default_abi,
+ ctypes.int,
+ pid_t,
+ ctypes.int
+ );
+
+ //int read(int fd, void *buf, size_t count);
+ var bufferSize = 1024;
+ var buffer = ctypes.char.array(bufferSize);
+ var read = libc.declare("read",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ buffer,
+ ctypes.int
+ );
+
+ //ssize_t write(int fd, const void *buf, size_t count);
+ var write = libc.declare("write",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ ctypes.char.ptr,
+ ctypes.int
+ );
+
+ //int chdir(const char *path);
+ var chdir = libc.declare("chdir",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.char.ptr
+ );
+
+ //int fcntl(int fd, int cmd, ... /* arg */ );
+ var fcntl = libc.declare("fcntl",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ ctypes.int,
+ ctypes.int
+ );
+
+ function popen(command, workdir, args, environment, child) {
+ var _in,
+ _out,
+ _err,
+ pid,
+ rc;
+ _in = new pipefd();
+ _out = new pipefd();
+ if(!options.mergeStderr)
+ _err = new pipefd();
+
+ var _args = argv();
+ args.unshift(command);
+ for(var i=0;i= 0) {
+ posix_spawn_file_actions_addclose(action.address(), i);
+ }
+ }
+ }
+
+ /*
+ * createStdinWriter ()
+ *
+ * Create a ChromeWorker object for writing data to the subprocess' stdin
+ * pipe. The ChromeWorker object lives on a separate thread; this avoids
+ * internal deadlocks.
+ */
+ function createStdinWriter() {
+ debugLog("Creating new stdin worker\n");
+ stdinWorker = new ChromeWorker(WORKER_URL_UNIX);
+ stdinWorker.onmessage = function(event) {
+ switch (event.data.msg) {
+ case "info":
+ switch(event.data.data) {
+ case "WriteOK":
+ pendingWriteCount--;
+ debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
+ break;
+ case "ClosedOK":
+ stdinOpenState = CLOSED;
+ debugLog("Stdin pipe closed\n");
+ break;
+ default:
+ debugLog("got msg from stdinWorker: "+event.data.data+"\n");
+ }
+ break;
+ case "debug":
+ debugLog("stdinWorker: "+event.data.data+"\n");
+ break;
+ case "error":
+ LogError("got error from stdinWorker: "+event.data.data+"\n");
+ pendingWriteCount = 0;
+ stdinOpenState = CLOSED;
+ }
+ }
+ stdinWorker.onerror = function(error) {
+ pendingWriteCount = 0;
+ closeStdinHandle();
+ LogError("got error from stdinWorker: "+error.message+"\n");
+ }
+ stdinWorker.postMessage({msg: "init", libc: options.libc});
+ }
+
+ /*
+ * writeStdin()
+ * @data: String containing the data to write
+ *
+ * Write data to the subprocess' stdin (equals to sending a request to the
+ * ChromeWorker object to write the data).
+ */
+ function writeStdin(data) {
+ if (stdinOpenState == CLOSED) return; // do not write to closed pipes
+
+ ++pendingWriteCount;
+ debugLog("sending "+data.length+" bytes to stdinWorker\n");
+ var pipe = parseInt(child.stdin);
+
+ stdinWorker.postMessage({
+ msg: 'write',
+ pipe: pipe,
+ data: data
+ });
+ }
+
+
+ /*
+ * closeStdinHandle()
+ *
+ * Close the stdin pipe, either directly or by requesting the ChromeWorker to
+ * close the pipe. The ChromeWorker will only close the pipe after the last write
+ * request process is done.
+ */
+
+ function closeStdinHandle() {
+ debugLog("trying to close stdin\n");
+ if (stdinOpenState != OPEN) return;
+ stdinOpenState = CLOSEABLE;
+
+ if (stdinWorker) {
+ debugLog("sending close stdin to worker\n");
+ var pipePtr = parseInt(child.stdin);
+
+ stdinWorker.postMessage({
+ msg: 'close',
+ pipe: pipePtr
+ });
+ }
+ else {
+ stdinOpenState = CLOSED;
+ debugLog("Closing Stdin\n");
+ close(child.stdin) && LogError("CloseHandle stdin failed");
+ }
+ }
+
+
+ /*
+ * createReader(pipe, name)
+ *
+ * @pipe: handle to the pipe
+ * @name: String containing the pipe name (stdout or stderr)
+ * @callbackFunc: function to be called with the read data
+ *
+ * Create a ChromeWorker object for reading data asynchronously from
+ * the pipe (i.e. on a separate thread), and passing the result back to
+ * the caller.
+ *
+ */
+ function createReader(pipe, name, callbackFunc) {
+ var worker = new ChromeWorker(WORKER_URL_UNIX);
+ worker.onmessage = function(event) {
+ switch(event.data.msg) {
+ case "data":
+ debugLog("got "+event.data.count+" bytes from "+name+"\n");
+ var data = '';
+ if (options.charset === null) {
+ data = getBytes(event.data.data);
+ }
+ else {
+ try {
+ data = convertBytes(event.data.data, options.charset);
+ }
+ catch(ex) {
+ console.warn("error decoding output: " + ex);
+ data = getBytes(event.data.data);
+ }
+ }
+
+ callbackFunc(data);
+ break;
+ case "done":
+ debugLog("Pipe "+name+" closed\n");
+ if (event.data.data != 0) workerExitCode = event.data.data;
+ --readers;
+ if (readers == 0) cleanup();
+ break;
+ default:
+ debugLog("Got msg from "+name+": "+event.data.data+"\n");
+ }
+ }
+ worker.onerror = function(error) {
+ LogError("Got error from "+name+": "+error.message);
+ }
+
+ worker.postMessage({
+ msg: 'read',
+ pipe: pipe,
+ pid: pid,
+ libc: options.libc,
+ charset: options.charset === null ? "null" : options.charset,
+ name: name
+ });
+
+ return worker;
+ }
+
+ /*
+ * readPipes()
+ *
+ * Open the pipes for reading from stdout and stderr
+ */
+ function readPipes() {
+
+ stdoutWorker = createReader(child.stdout, "stdout", function (data) {
+ if(options.stdout) {
+ setTimeout(function() {
+ options.stdout(data);
+ }, 0);
+ } else {
+ output += data;
+ }
+ });
+
+ if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
+ if(options.stderr) {
+ setTimeout(function() {
+ options.stderr(data);
+ }, 0);
+ } else {
+ error += data;
+ }
+ });
+ }
+
+ function cleanup() {
+ debugLog("Cleanup called\n");
+ if(active) {
+ active = false;
+
+ closeStdinHandle(); // should only be required in case of errors
+
+ var result, status = ctypes.int();
+ result = waitpid(child.pid, status.address(), 0);
+ if (result > 0)
+ exitCode = status.value
+ else
+ if (workerExitCode >= 0)
+ exitCode = workerExitCode
+ else
+ exitCode = status.value;
+
+ if (stdinWorker)
+ stdinWorker.postMessage({msg: 'stop'})
+
+ setTimeout(function _done() {
+ if (options.done) {
+ try {
+ options.done({
+ exitCode: exitCode,
+ stdout: output,
+ stderr: error,
+ });
+ }
+ catch(ex) {
+ // prevent from blocking if options.done() throws an error
+ done = true;
+ throw ex;
+ }
+
+ }
+ done = true;
+ }, 0);
+
+ libc.close();
+ }
+ }
+
+ //main
+
+ var cmdStr = getCommandStr(options.command);
+ var workDir = getWorkDir(options.workdir);
+
+ child = {};
+ pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
+
+ debugLog("subprocess started; got PID "+pid+"\n");
+
+ readPipes();
+
+ if (options.stdin) {
+ createStdinWriter();
+ if(typeof(options.stdin) == 'function') {
+ try {
+ options.stdin({
+ write: function(data) {
+ writeStdin(data);
+ },
+ close: function() {
+ closeStdinHandle();
+ }
+ });
+ }
+ catch(ex) {
+ // prevent from failing if options.stdin() throws an exception
+ closeStdinHandle();
+ throw ex;
+ }
+ } else {
+ writeStdin(options.stdin);
+ closeStdinHandle();
+ }
+ }
+
+ return {
+ wait: function() {
+ // wait for async operations to complete
+ var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
+ while (! done) thread.processNextEvent(true)
+ return exitCode;
+ },
+ kill: function(hardKill) {
+ var rv = kill(pid, (hardKill ? 9: 15));
+ cleanup(-1);
+ return rv;
+ }
+ }
+}
+
+
+module.exports = subprocess;
diff --git a/b2g/simulator/packages/subprocess/lib/subprocess_worker_unix.js b/b2g/simulator/packages/subprocess/lib/subprocess_worker_unix.js
new file mode 100644
index 00000000000..a502959a5f2
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/lib/subprocess_worker_unix.js
@@ -0,0 +1,248 @@
+/* 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/.
+ */
+
+/*
+ * ChromeWorker Object subprocess.jsm on Unix-like systems (Linux, Mac OS X, ...)
+ * to process stdin/stdout/stderr on separate threads.
+ *
+ */
+
+// Being a ChromeWorker object, implicitly uses the following:
+// Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+'use strict';
+
+const BufferSize = 1024;
+
+var libc = null;
+var libcFunc = {};
+
+
+/*
+ struct pollfd {
+ int fd; // file descriptor
+ short events; // events to look for
+ short revents; // events returned
+ };
+*/
+
+var pollfd = new ctypes.StructType("pollfd",
+ [ {'fd': ctypes.int},
+ {'events': ctypes.short},
+ {'revents': ctypes.short}
+ ]);
+
+var WriteBuffer = ctypes.uint8_t.array(BufferSize);
+var ReadBuffer = ctypes.char.array(BufferSize);
+
+
+const POLLIN = 0x0001;
+const POLLOUT = 0x0004;
+
+const POLLERR = 0x0008; // some poll error occurred
+const POLLHUP = 0x0010; // file descriptor was "hung up"
+const POLLNVAL = 0x0020; // requested events "invalid"
+
+const WNOHANG = 0x01;
+
+const pid_t = ctypes.int32_t;
+
+const INDEFINITE = -1;
+const NOWAIT = 0;
+const WAITTIME = 200 // wait time for poll() in ms
+
+function initLibc(libName) {
+ postMessage({msg: "debug", data: "initialising library with "+ libName});
+
+ libc = ctypes.open(libName);
+
+ libcFunc.pollFds = pollfd.array(1);
+
+ // int poll(struct pollfd fds[], nfds_t nfds, int timeout);
+ libcFunc.poll = libc.declare("poll",
+ ctypes.default_abi,
+ ctypes.int,
+ libcFunc.pollFds,
+ ctypes.unsigned_int,
+ ctypes.int);
+
+ //ssize_t write(int fd, const void *buf, size_t count);
+ // NOTE: buf is declared as array of unsigned int8 instead of char to avoid
+ // implicit charset conversion
+ libcFunc.write = libc.declare("write",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ WriteBuffer,
+ ctypes.int);
+
+ //int read(int fd, void *buf, size_t count);
+ libcFunc.read = libc.declare("read",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int,
+ ReadBuffer,
+ ctypes.int);
+
+ //int pipe(int pipefd[2]);
+ libcFunc.pipefd = ctypes.int.array(2);
+
+ //int close(int fd);
+ libcFunc.close = libc.declare("close",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int);
+
+ //pid_t waitpid(pid_t pid, int *status, int options);
+ libcFunc.waitpid = libc.declare("waitpid",
+ ctypes.default_abi,
+ pid_t,
+ pid_t,
+ ctypes.int.ptr,
+ ctypes.int);
+}
+
+function closePipe(pipe) {
+ libcFunc.close(pipe);
+}
+
+function writePipe(pipe, data) {
+
+ postMessage({msg: "debug", data: "trying to write to "+pipe});
+
+ let numChunks = Math.floor(data.length / BufferSize);
+ let pData = new WriteBuffer();
+
+ for (var chunk = 0; chunk <= numChunks; chunk ++) {
+ let numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
+ for (var i=0; i < numBytes; i++) {
+ pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
+ }
+
+ let bytesWritten = libcFunc.write(pipe, pData, numBytes);
+ if (bytesWritten != numBytes) {
+ closePipe();
+ libc.close();
+ postMessage({ msg: "error", data: "error: wrote "+bytesWritten+" instead of "+numBytes+" bytes"});
+ close();
+ }
+ }
+ postMessage({msg: "info", data: "wrote "+data.length+" bytes of data"});
+}
+
+
+function readString(data, length, charset) {
+ var string = '', bytes = [];
+ for(var i = 0;i < length; i++) {
+ if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
+ break;
+
+ bytes.push(data[i]);
+ }
+
+ return bytes;
+}
+
+function readPipe(pipe, charset, pid) {
+ var p = new libcFunc.pollFds;
+ p[0].fd = pipe;
+ p[0].events = POLLIN | POLLERR | POLLHUP;
+ p[0].revents = 0;
+ var pollTimeout = WAITTIME;
+ var exitCode = -1;
+ var readCount = 0;
+ var result, status = ctypes.int();
+ result = 0;
+
+
+ const i=0;
+ while (true) {
+ if (result == 0) {
+ result = libcFunc.waitpid(pid, status.address(), WNOHANG);
+ if (result > 0) {
+ pollTimeout = NOWAIT;
+ exitCode = parseInt(status.value);
+ postMessage({msg: "debug", data: "waitpid signaled subprocess stop, exitcode="+status.value });
+ }
+ }
+ var r = libcFunc.poll(p, 1, pollTimeout);
+ if (r > 0) {
+ if (p[i].revents & POLLIN) {
+ postMessage({msg: "debug", data: "reading next chunk"});
+ readCount = readPolledFd(p[i].fd, charset);
+ if (readCount == 0) break;
+ }
+
+ if (p[i].revents & POLLHUP) {
+ postMessage({msg: "debug", data: "poll returned HUP"});
+ break;
+ }
+ else if (p[i].revents & POLLERR) {
+ postMessage({msg: "error", data: "poll returned error"});
+ break;
+ }
+ else if (p[i].revents != POLLIN) {
+ postMessage({msg: "error", data: "poll returned "+p[i]});
+ break;
+ }
+ }
+ else
+ if (pollTimeout == 0 || r < 0) break;
+ }
+
+ // continue reading until the buffer is empty
+ while (readCount > 0) {
+ readCount = readPolledFd(pipe, charset);
+ }
+
+ libcFunc.close(pipe);
+ postMessage({msg: "done", data: exitCode });
+ libc.close();
+ close();
+}
+
+function readPolledFd(pipe, charset) {
+ var line = new ReadBuffer();
+ var r = libcFunc.read(pipe, line, BufferSize);
+
+ if (r > 0) {
+ var c = readString(line, r, charset);
+ postMessage({msg: "data", data: c, count: c.length});
+ }
+ return r;
+}
+
+onmessage = function (event) {
+ switch (event.data.msg) {
+ case "init":
+ initLibc(event.data.libc);
+ break;
+ case "read":
+ initLibc(event.data.libc);
+ readPipe(event.data.pipe, event.data.charset, event.data.pid);
+ break;
+ case "write":
+ // data contents:
+ // msg: 'write'
+ // data: the data (string) to write
+ // pipe: ptr to pipe
+ writePipe(event.data.pipe, event.data.data);
+ postMessage({msg: "info", data: "WriteOK"});
+ break;
+ case "close":
+ postMessage({msg: "debug", data: "closing stdin\n"});
+
+ closePipe(event.data.pipe);
+ postMessage({msg: "info", data: "ClosedOK"});
+ break;
+ case "stop":
+ libc.close(); // do not use libc after this point
+ close();
+ break;
+ default:
+ throw("error: Unknown command"+event.data.msg+"\n");
+ }
+ return;
+};
diff --git a/b2g/simulator/packages/subprocess/lib/subprocess_worker_win.js b/b2g/simulator/packages/subprocess/lib/subprocess_worker_win.js
new file mode 100644
index 00000000000..69c3d8714bd
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/lib/subprocess_worker_win.js
@@ -0,0 +1,206 @@
+/* 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/.
+ */
+
+/*
+ * ChromeWorker Object subprocess.jsm on Windows to process stdin/stdout/stderr
+ * on separate threads.
+ *
+ */
+
+// Being a ChromeWorker object, implicitly uses the following:
+// Components.utils.import("resource://gre/modules/ctypes.jsm");
+
+'use strict';
+
+const BufferSize = 1024;
+
+const BOOL = ctypes.bool;
+const HANDLE = ctypes.size_t;
+const DWORD = ctypes.uint32_t;
+const LPDWORD = DWORD.ptr;
+const PVOID = ctypes.voidptr_t;
+const LPVOID = PVOID;
+
+/*
+typedef struct _OVERLAPPED {
+ ULONG_PTR Internal;
+ ULONG_PTR InternalHigh;
+ union {
+ struct {
+ DWORD Offset;
+ DWORD OffsetHigh;
+ };
+ PVOID Pointer;
+ };
+ HANDLE hEvent;
+} OVERLAPPED, *LPOVERLAPPED;
+*/
+const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
+
+var ReadFileBuffer = ctypes.char.array(BufferSize);
+var WriteFileBuffer = ctypes.uint8_t.array(BufferSize);
+
+var kernel32dll = null;
+var libFunc = {};
+
+function initLib(libName) {
+ if (ctypes.size_t.size == 8) {
+ var WinABI = ctypes.default_abi;
+ } else {
+ var WinABI = ctypes.winapi_abi;
+ }
+
+ kernel32dll = ctypes.open(libName);
+
+ /*
+ BOOL WINAPI WriteFile(
+ __in HANDLE hFile,
+ __in LPCVOID lpBuffer,
+ __in DWORD nNumberOfBytesToWrite,
+ __out_opt LPDWORD lpNumberOfBytesWritten,
+ __inout_opt LPOVERLAPPED lpOverlapped
+ );
+
+ NOTE: lpBuffer is declared as array of unsigned int8 instead of char to avoid
+ implicit charset conversion
+ */
+ libFunc.WriteFile = kernel32dll.declare("WriteFile",
+ WinABI,
+ BOOL,
+ HANDLE,
+ WriteFileBuffer,
+ DWORD,
+ LPDWORD,
+ OVERLAPPED.ptr
+ );
+
+ /*
+ BOOL WINAPI ReadFile(
+ __in HANDLE hFile,
+ __out LPVOID ReadFileBuffer,
+ __in DWORD nNumberOfBytesToRead,
+ __out_opt LPDWORD lpNumberOfBytesRead,
+ __inout_opt LPOVERLAPPED lpOverlapped
+ );
+ */
+ libFunc.ReadFile = kernel32dll.declare("ReadFile",
+ WinABI,
+ BOOL,
+ HANDLE,
+ ReadFileBuffer,
+ DWORD,
+ LPDWORD,
+ OVERLAPPED.ptr
+ );
+
+ /*
+ BOOL WINAPI CloseHandle(
+ __in HANDLE hObject
+ );
+ */
+ libFunc.CloseHandle = kernel32dll.declare("CloseHandle",
+ WinABI,
+ BOOL,
+ HANDLE
+ );
+}
+
+
+function writePipe(pipe, data) {
+ var bytesWritten = DWORD(0);
+
+ var pData = new WriteFileBuffer();
+
+ var numChunks = Math.floor(data.length / BufferSize);
+ for (var chunk = 0; chunk <= numChunks; chunk ++) {
+ var numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
+ for (var i=0; i < numBytes; i++) {
+ pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
+ }
+
+ var r = libFunc.WriteFile(pipe, pData, numBytes, bytesWritten.address(), null);
+ if (bytesWritten.value != numBytes)
+ throw("error: wrote "+bytesWritten.value+" instead of "+numBytes+" bytes");
+ }
+ postMessage("wrote "+data.length+" bytes of data");
+}
+
+function readString(data, length, charset) {
+ var string = '', bytes = [];
+ for(var i = 0;i < length; i++) {
+ if(data[i] == 0 && charset != "null") // stop on NULL character for non-binary data
+ break;
+
+ bytes.push(data[i]);
+ }
+
+ return bytes;
+}
+
+function readPipe(pipe, charset) {
+ while (true) {
+ var bytesRead = DWORD(0);
+ var line = new ReadFileBuffer();
+ var r = libFunc.ReadFile(pipe, line, BufferSize, bytesRead.address(), null);
+
+ if (!r) {
+ // stop if we get an error (such as EOF reached)
+ postMessage({msg: "info", data: "ReadFile failed"});
+ break;
+ }
+
+ if (bytesRead.value > 0) {
+ var c = readString(line, bytesRead.value, charset);
+ postMessage({msg: "data", data: c, count: c.length});
+ }
+ else {
+ break;
+ }
+ }
+ libFunc.CloseHandle(pipe);
+ postMessage({msg: "done"});
+ kernel32dll.close();
+ close();
+}
+
+onmessage = function (event) {
+ let pipePtr;
+ switch (event.data.msg) {
+ case "init":
+ initLib(event.data.libc);
+ break;
+ case "write":
+ // data contents:
+ // msg: 'write'
+ // data: the data (string) to write
+ // pipe: ptr to pipe
+ pipePtr = HANDLE.ptr(event.data.pipe);
+ writePipe(pipePtr.contents, event.data.data);
+ postMessage("WriteOK");
+ break;
+ case "read":
+ initLib(event.data.libc);
+ pipePtr = HANDLE.ptr(event.data.pipe);
+ readPipe(pipePtr.contents, event.data.charset);
+ break;
+ case "close":
+ pipePtr = HANDLE.ptr(event.data.pipe);
+ postMessage("closing stdin\n");
+
+ if (libFunc.CloseHandle(pipePtr.contents)) {
+ postMessage("ClosedOK");
+ }
+ else
+ postMessage("Could not close stdin handle");
+ break;
+ case "stop":
+ kernel32dll.close();
+ close();
+ break;
+ default:
+ throw("error: Unknown command"+event.data.msg+"\n");
+ }
+ return;
+};
diff --git a/b2g/simulator/packages/subprocess/package.json b/b2g/simulator/packages/subprocess/package.json
new file mode 100644
index 00000000000..f828476cefa
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "subprocess",
+ "license": "MPL 1.1/GPL 2.0/LGPL 2.1",
+ "author": "Alexandre Poirot",
+ "contributors": [
+ "Jan Gerber (original creator) ",
+ "Patrick Brunschwig (author of almost all code) ",
+ "Ramalingam Saravanan (from enigmail team) "
+ ],
+ "version": "0.1.1",
+ "dependencies": [
+ "api-utils"
+ ],
+ "description": "Addon-sdk package for subprocess xpcom components from enigmail. Allow to run process, manipulate stdin/out and kill it."
+}
diff --git a/b2g/simulator/packages/subprocess/tests/test-subprocess.js b/b2g/simulator/packages/subprocess/tests/test-subprocess.js
new file mode 100644
index 00000000000..5130dd3c9b6
--- /dev/null
+++ b/b2g/simulator/packages/subprocess/tests/test-subprocess.js
@@ -0,0 +1,144 @@
+/* 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/.
+ */
+
+const { Cc, Ci } = require("chrome");
+const subprocess = require("subprocess");
+const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+
+// For now, only test on windows
+if (env.get('OS') && env.get('OS').match(/Windows/)) {
+
+exports.testWindows = function (test) {
+ test.waitUntilDone();
+ let envTestValue = "OK";
+ let gotStdout = false;
+
+ var p = subprocess.call({
+ // Retrieve windows cmd.exe path from env
+ command: env.get('ComSpec'),
+ // In order to execute a simple "echo" function
+ arguments: ['/C', 'echo %ENV_TEST%'], // ' & type CON' should display stdin, but doesn't work
+ // Printing an environnement variable set here by the parent process
+ environment: ['ENV_TEST='+envTestValue],
+
+ stdin: function(stdin) {
+ // Win32 command line is not really made for stdin
+ // So it doesn't seems to work as it's hard to retrieve stdin
+ stdin.write("stdin");
+ stdin.close();
+ },
+ stdout: function(data) {
+ test.assert(!gotStdout,"don't get stdout twice");
+ test.assertEqual(data,envTestValue+"\r\n","stdout contains the environment variable");
+ gotStdout = true;
+ },
+ stderr: function(data) {
+ test.fail("shouldn't get stderr");
+ },
+ done: function() {
+ test.assert(gotStdout, "got stdout before finished");
+ test.done();
+ },
+ mergeStderr: false
+ });
+
+}
+
+exports.testWindowsStderr = function (test) {
+ test.waitUntilDone();
+ let gotStderr = false;
+
+ var p = subprocess.call({
+ command: env.get('ComSpec'),
+ arguments: ['/C', 'nonexistent'],
+
+ stdout: function(data) {
+ test.fail("shouldn't get stdout");
+ },
+ stderr: function(data) {
+ test.assert(!gotStderr,"don't get stderr twice");
+ test.assertEqual(
+ data,
+ "'nonexistent' is not recognized as an internal or external command,\r\n" +
+ "operable program or batch file.\r\n",
+ "stderr contains the error message"
+ );
+ gotStderr = true;
+ },
+ done: function() {
+ test.assert(gotStderr, "got stderr before finished");
+ test.done();
+ },
+ mergeStderr: false
+ });
+
+}
+
+}
+
+if (env.get('USER') && env.get('SHELL')) {
+
+exports.testUnix = function (test) {
+ test.waitUntilDone();
+ let envTestValue = "OK";
+ let gotStdout = false;
+
+ var p = subprocess.call({
+ command: '/bin/sh',
+ // Print stdin and our env variable
+ //arguments: ['-c', 'echo $@ $ENV_TEST'],
+ environment: ['ENV_TEST='+envTestValue],
+
+ stdin: function(stdin) {
+ stdin.write("echo $ENV_TEST");
+ stdin.close();
+ },
+ stdout: function(data) {
+ test.assert(!gotStdout,"don't get stdout twice");
+ test.assertEqual(data,envTestValue+"\n","stdout contains the environment variable");
+ gotStdout = true;
+ },
+ stderr: function(data) {
+ test.fail("shouldn't get stderr");
+ },
+ done: function() {
+ test.assert(gotStdout, "got stdout before finished");
+ test.done();
+ },
+ mergeStderr: false
+ });
+}
+
+exports.testUnixStderr = function (test) {
+ test.waitUntilDone();
+ let gotStderr = false;
+
+ var p = subprocess.call({
+ // Hope that we don't have to give absolute path on linux ...
+ command: '/bin/sh',
+ arguments: ['nonexistent'],
+
+ stdout: function(data) {
+ test.fail("shouldn't get stdout");
+ },
+ stderr: function(data) {
+ test.assert(!gotStderr,"don't get stderr twice");
+ // There is two variant of error message
+ if (data == "/bin/sh: 0: Can't open nonexistent\n")
+ test.pass("stderr containes the expected error message");
+ else
+ test.assertEqual(data, "/bin/sh: nonexistent: No such file or directory\n",
+ "stderr contains the error message");
+ gotStderr = true;
+ },
+ done: function() {
+ test.assert(gotStderr, "got stderr before finished");
+ test.done();
+ },
+ mergeStderr: false
+ });
+}
+
+}
diff --git a/configure.in b/configure.in
index 79af440144b..37923dbf1a2 100644
--- a/configure.in
+++ b/configure.in
@@ -165,11 +165,20 @@ if test -z "$PERL" -o "$PERL" = ":"; then
AC_MSG_ERROR([perl not found in \$PATH])
fi
+if test -n "$GAIADIR" -a ! -d "$GAIADIR" ; then
+ AC_MSG_ERROR([GAIADIR '$GAIADIR' isn't a valid directory])
+fi
+
AC_SUBST(GAIADIR)
if test -n "$GAIADIR" ; then
AC_DEFINE(PACKAGE_GAIA)
fi
+if test -n "$FXOS_SIMULATOR" -a -z "$GAIADIR" ; then
+ AC_MSG_ERROR([FXOS_SIMULATOR=1 requires GAIADIR to be defined])
+fi
+AC_SUBST(FXOS_SIMULATOR)
+
MOZ_ARG_WITH_STRING(gonk,
[ --with-gonk=DIR
location of gonk dir],
@@ -4241,6 +4250,7 @@ AC_SUBST(MOZ_BUILD_APP)
AC_SUBST(MOZ_PHOENIX)
AC_SUBST(MOZ_XULRUNNER)
AC_SUBST(MOZ_B2G)
+AC_SUBST(MOZ_B2G_VERSION)
AC_DEFINE_UNQUOTED(MOZ_BUILD_APP,$MOZ_BUILD_APP)