From d87a2923e9a1f8141eaa730d91a4d270a0bc1f69 Mon Sep 17 00:00:00 2001 From: Alexandre Poirot Date: Mon, 10 Feb 2014 09:16:57 -0500 Subject: [PATCH] Bug 944451 - Land the simulator addon into mozilla-central. r=vingtetun, r=gps, r=paul --- b2g/build.mk | 5 + b2g/simulator/build_xpi.py | 145 ++ b2g/simulator/custom-prefs.js | 2 + b2g/simulator/custom-settings.json | 6 + b2g/simulator/icon.png | Bin 0 -> 4762 bytes b2g/simulator/icon64.png | Bin 0 -> 7858 bytes b2g/simulator/lib/main.js | 52 + b2g/simulator/lib/simulator-process.js | 182 ++ b2g/simulator/package-overload.json.in | 7 + b2g/simulator/package.json | 37 + b2g/simulator/packages/subprocess/README.md | 124 ++ .../packages/subprocess/lib/subprocess.js | 1543 +++++++++++++++++ .../subprocess/lib/subprocess_worker_unix.js | 248 +++ .../subprocess/lib/subprocess_worker_win.js | 206 +++ .../packages/subprocess/package.json | 15 + .../subprocess/tests/test-subprocess.js | 144 ++ configure.in | 10 + 17 files changed, 2726 insertions(+) create mode 100644 b2g/simulator/build_xpi.py create mode 100644 b2g/simulator/custom-prefs.js create mode 100644 b2g/simulator/custom-settings.json create mode 100644 b2g/simulator/icon.png create mode 100644 b2g/simulator/icon64.png create mode 100644 b2g/simulator/lib/main.js create mode 100644 b2g/simulator/lib/simulator-process.js create mode 100644 b2g/simulator/package-overload.json.in create mode 100644 b2g/simulator/package.json create mode 100644 b2g/simulator/packages/subprocess/README.md create mode 100644 b2g/simulator/packages/subprocess/lib/subprocess.js create mode 100644 b2g/simulator/packages/subprocess/lib/subprocess_worker_unix.js create mode 100644 b2g/simulator/packages/subprocess/lib/subprocess_worker_win.js create mode 100644 b2g/simulator/packages/subprocess/package.json create mode 100644 b2g/simulator/packages/subprocess/tests/test-subprocess.js 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 0000000000000000000000000000000000000000..c4307fc8418436bb6b2fd3a6afc702c2db28aa77 GIT binary patch literal 4762 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 { + 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)