b2g/simulator/packages/subprocess/lib/subprocess.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/b2g/simulator/packages/subprocess/lib/subprocess.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1543 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 + */
     1.8 +
     1.9 +'use strict';
    1.10 +
    1.11 +const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
    1.12 +
    1.13 +Cu.import("resource://gre/modules/ctypes.jsm");
    1.14 +
    1.15 +const NS_LOCAL_FILE = "@mozilla.org/file/local;1";
    1.16 +
    1.17 +const Runtime = require("sdk/system/runtime");
    1.18 +const Environment = require("sdk/system/environment").env;
    1.19 +const DEFAULT_ENVIRONMENT = [];
    1.20 +if (Runtime.OS == "Linux" && "DISPLAY" in Environment) {
    1.21 +  DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY);
    1.22 +}
    1.23 +
    1.24 +/*
    1.25 +Fake require statements to ensure worker scripts are packaged:
    1.26 +require("subprocess_worker_win.js");
    1.27 +require("subprocess_worker_unix.js");
    1.28 +*/
    1.29 +const URL_PREFIX = module.uri.replace(/subprocess\.js/, "");
    1.30 +const WORKER_URL_WIN = URL_PREFIX + "subprocess_worker_win.js";
    1.31 +const WORKER_URL_UNIX = URL_PREFIX + "subprocess_worker_unix.js";
    1.32 +
    1.33 +//Windows API definitions
    1.34 +if (ctypes.size_t.size == 8) {
    1.35 +    var WinABI = ctypes.default_abi;
    1.36 +} else {
    1.37 +    var WinABI = ctypes.winapi_abi;
    1.38 +}
    1.39 +const WORD = ctypes.uint16_t;
    1.40 +const DWORD = ctypes.uint32_t;
    1.41 +const LPDWORD = DWORD.ptr;
    1.42 +
    1.43 +const UINT = ctypes.unsigned_int;
    1.44 +const BOOL = ctypes.bool;
    1.45 +const HANDLE = ctypes.size_t;
    1.46 +const HWND = HANDLE;
    1.47 +const HMODULE = HANDLE;
    1.48 +const WPARAM = ctypes.size_t;
    1.49 +const LPARAM = ctypes.size_t;
    1.50 +const LRESULT = ctypes.size_t;
    1.51 +const ULONG_PTR = ctypes.uintptr_t;
    1.52 +const PVOID = ctypes.voidptr_t;
    1.53 +const LPVOID = PVOID;
    1.54 +const LPCTSTR = ctypes.jschar.ptr;
    1.55 +const LPCWSTR = ctypes.jschar.ptr;
    1.56 +const LPTSTR = ctypes.jschar.ptr;
    1.57 +const LPSTR = ctypes.char.ptr;
    1.58 +const LPCSTR = ctypes.char.ptr;
    1.59 +const LPBYTE = ctypes.char.ptr;
    1.60 +
    1.61 +const CREATE_NEW_CONSOLE = 0x00000010;
    1.62 +const CREATE_NO_WINDOW = 0x08000000;
    1.63 +const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
    1.64 +const STARTF_USESHOWWINDOW = 0x00000001;
    1.65 +const STARTF_USESTDHANDLES = 0x00000100;
    1.66 +const SW_HIDE = 0;
    1.67 +const DUPLICATE_SAME_ACCESS = 0x00000002;
    1.68 +const STILL_ACTIVE = 259;
    1.69 +const INFINITE = DWORD(0xFFFFFFFF);
    1.70 +const WAIT_TIMEOUT = 0x00000102;
    1.71 +
    1.72 +/*
    1.73 +typedef struct _SECURITY_ATTRIBUTES {
    1.74 + DWORD  nLength;
    1.75 + LPVOID lpSecurityDescriptor;
    1.76 + BOOL   bInheritHandle;
    1.77 +} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
    1.78 +*/
    1.79 +const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
    1.80 +    {"nLength": DWORD},
    1.81 +    {"lpSecurityDescriptor": LPVOID},
    1.82 +    {"bInheritHandle": BOOL},
    1.83 +]);
    1.84 +
    1.85 +/*
    1.86 +typedef struct _STARTUPINFO {
    1.87 +  DWORD  cb;
    1.88 +  LPTSTR lpReserved;
    1.89 +  LPTSTR lpDesktop;
    1.90 +  LPTSTR lpTitle;
    1.91 +  DWORD  dwX;
    1.92 +  DWORD  dwY;
    1.93 +  DWORD  dwXSize;
    1.94 +  DWORD  dwYSize;
    1.95 +  DWORD  dwXCountChars;
    1.96 +  DWORD  dwYCountChars;
    1.97 +  DWORD  dwFillAttribute;
    1.98 +  DWORD  dwFlags;
    1.99 +  WORD   wShowWindow;
   1.100 +  WORD   cbReserved2;
   1.101 +  LPBYTE lpReserved2;
   1.102 +  HANDLE hStdInput;
   1.103 +  HANDLE hStdOutput;
   1.104 +  HANDLE hStdError;
   1.105 +} STARTUPINFO, *LPSTARTUPINFO;
   1.106 +*/
   1.107 +const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
   1.108 +    {"cb": DWORD},
   1.109 +    {"lpReserved": LPTSTR},
   1.110 +    {"lpDesktop": LPTSTR},
   1.111 +    {"lpTitle": LPTSTR},
   1.112 +    {"dwX": DWORD},
   1.113 +    {"dwY": DWORD},
   1.114 +    {"dwXSize": DWORD},
   1.115 +    {"dwYSize": DWORD},
   1.116 +    {"dwXCountChars": DWORD},
   1.117 +    {"dwYCountChars": DWORD},
   1.118 +    {"dwFillAttribute": DWORD},
   1.119 +    {"dwFlags": DWORD},
   1.120 +    {"wShowWindow": WORD},
   1.121 +    {"cbReserved2": WORD},
   1.122 +    {"lpReserved2": LPBYTE},
   1.123 +    {"hStdInput": HANDLE},
   1.124 +    {"hStdOutput": HANDLE},
   1.125 +    {"hStdError": HANDLE},
   1.126 +]);
   1.127 +
   1.128 +/*
   1.129 +typedef struct _PROCESS_INFORMATION {
   1.130 +  HANDLE hProcess;
   1.131 +  HANDLE hThread;
   1.132 +  DWORD  dwProcessId;
   1.133 +  DWORD  dwThreadId;
   1.134 +} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
   1.135 +*/
   1.136 +const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
   1.137 +    {"hProcess": HANDLE},
   1.138 +    {"hThread": HANDLE},
   1.139 +    {"dwProcessId": DWORD},
   1.140 +    {"dwThreadId": DWORD},
   1.141 +]);
   1.142 +
   1.143 +/*
   1.144 +typedef struct _OVERLAPPED {
   1.145 +  ULONG_PTR Internal;
   1.146 +  ULONG_PTR InternalHigh;
   1.147 +  union {
   1.148 +    struct {
   1.149 +      DWORD Offset;
   1.150 +      DWORD OffsetHigh;
   1.151 +    };
   1.152 +    PVOID  Pointer;
   1.153 +  };
   1.154 +  HANDLE    hEvent;
   1.155 +} OVERLAPPED, *LPOVERLAPPED;
   1.156 +*/
   1.157 +const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
   1.158 +
   1.159 +//UNIX definitions
   1.160 +const pid_t = ctypes.int32_t;
   1.161 +const WNOHANG = 1;
   1.162 +const F_GETFD = 1;
   1.163 +const F_SETFL = 4;
   1.164 +
   1.165 +const LIBNAME       = 0;
   1.166 +const O_NONBLOCK    = 1;
   1.167 +const RLIM_T        = 2;
   1.168 +const RLIMIT_NOFILE = 3;
   1.169 +
   1.170 +function getPlatformValue(valueType) {
   1.171 +
   1.172 +    if (! gXulRuntime)
   1.173 +        gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
   1.174 +
   1.175 +    const platformDefaults = {
   1.176 +        // Windows API:
   1.177 +        'winnt':   [ 'kernel32.dll' ],
   1.178 +
   1.179 +        // Unix API:
   1.180 +        //            library name   O_NONBLOCK RLIM_T                RLIMIT_NOFILE
   1.181 +        'darwin':  [ 'libc.dylib',   0x04     , ctypes.uint64_t     , 8 ],
   1.182 +        'linux':   [ 'libc.so.6',    2024     , ctypes.unsigned_long, 7 ],
   1.183 +        'freebsd': [ 'libc.so.7',    0x04     , ctypes.int64_t      , 8 ],
   1.184 +        'openbsd': [ 'libc.so.61.0', 0x04     , ctypes.int64_t      , 8 ],
   1.185 +        'sunos':   [ 'libc.so',      0x80     , ctypes.unsigned_long, 5 ]
   1.186 +    }
   1.187 +
   1.188 +    return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
   1.189 +}
   1.190 +
   1.191 +
   1.192 +var gDebugFunc = null,
   1.193 +    gLogFunc = null,
   1.194 +    gXulRuntime = null;
   1.195 +
   1.196 +function LogError(s) {
   1.197 +    if (gLogFunc)
   1.198 +        gLogFunc(s);
   1.199 +    else
   1.200 +        dump(s);
   1.201 +}
   1.202 +
   1.203 +function debugLog(s) {
   1.204 +    if (gDebugFunc)
   1.205 +        gDebugFunc(s);
   1.206 +}
   1.207 +
   1.208 +function setTimeout(callback, timeout) {
   1.209 +    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.210 +    timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
   1.211 +};
   1.212 +
   1.213 +function getBytes(data) {
   1.214 +  var string = '';
   1.215 +  data.forEach(function(x) { string += String.fromCharCode(x < 0 ? x + 256 : x) });
   1.216 +  return string;
   1.217 +}
   1.218 +
   1.219 +function readString(data, length, charset) {
   1.220 +    var string = '', bytes = [];
   1.221 +    for(var i = 0;i < length; i++) {
   1.222 +        if(data[i] == 0 && charset !== null) // stop on NULL character for non-binary data
   1.223 +           break
   1.224 +        bytes.push(data[i]);
   1.225 +    }
   1.226 +    if (!bytes || bytes.length == 0)
   1.227 +        return string;
   1.228 +    if(charset === null) {
   1.229 +        return bytes;
   1.230 +    }
   1.231 +    return convertBytes(bytes, charset);
   1.232 +}
   1.233 +
   1.234 +function convertBytes(bytes, charset) {
   1.235 +    var string = '';
   1.236 +    charset = charset || 'UTF-8';
   1.237 +    var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   1.238 +                        .getService(Ci.nsIScriptableUnicodeConverter);
   1.239 +    try {
   1.240 +        unicodeConv.charset = charset;
   1.241 +        string = unicodeConv.convertFromByteArray(bytes, bytes.length);
   1.242 +    } catch (ex) {
   1.243 +        LogError("String conversion failed: "+ex.toString()+"\n")
   1.244 +        string = '';
   1.245 +    }
   1.246 +    string += unicodeConv.Finish();
   1.247 +    return string;
   1.248 +}
   1.249 +
   1.250 +
   1.251 +// temporary solution for removal of nsILocalFile
   1.252 +function getLocalFileApi() {
   1.253 +  if ("nsILocalFile" in Ci) {
   1.254 +    return Ci.nsILocalFile;
   1.255 +  }
   1.256 +  else
   1.257 +    return Ci.nsIFile;
   1.258 +}
   1.259 +
   1.260 +function getCommandStr(command) {
   1.261 +    let commandStr = null;
   1.262 +    if (typeof(command) == "string") {
   1.263 +        let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
   1.264 +        file.initWithPath(command);
   1.265 +        if (! (file.isExecutable() && file.isFile()))
   1.266 +            throw("File '"+command+"' is not an executable file");
   1.267 +        commandStr = command;
   1.268 +    }
   1.269 +    else {
   1.270 +        if (! (command.isExecutable() && command.isFile()))
   1.271 +            throw("File '"+command.path+"' is not an executable file");
   1.272 +        commandStr = command.path;
   1.273 +    }
   1.274 +
   1.275 +    return commandStr;
   1.276 +}
   1.277 +
   1.278 +function getWorkDir(workdir) {
   1.279 +    let workdirStr = null;
   1.280 +    if (typeof(workdir) == "string") {
   1.281 +        let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
   1.282 +        file.initWithPath(workdir);
   1.283 +        if (! (file.isDirectory()))
   1.284 +            throw("Directory '"+workdir+"' does not exist");
   1.285 +        workdirStr = workdir;
   1.286 +    }
   1.287 +    else if (workdir) {
   1.288 +        if (! workdir.isDirectory())
   1.289 +            throw("Directory '"+workdir.path+"' does not exist");
   1.290 +        workdirStr = workdir.path;
   1.291 +    }
   1.292 +    return workdirStr;
   1.293 +}
   1.294 +
   1.295 +
   1.296 +var subprocess = {
   1.297 +    call: function(options) {
   1.298 +        options.mergeStderr = options.mergeStderr || false;
   1.299 +        options.workdir = options.workdir ||  null;
   1.300 +        options.environment = options.environment || DEFAULT_ENVIRONMENT;
   1.301 +        if (options.arguments) {
   1.302 +            var args = options.arguments;
   1.303 +            options.arguments = [];
   1.304 +            args.forEach(function(argument) {
   1.305 +                options.arguments.push(argument);
   1.306 +            });
   1.307 +        } else {
   1.308 +            options.arguments = [];
   1.309 +        }
   1.310 +
   1.311 +        options.libc = getPlatformValue(LIBNAME);
   1.312 +
   1.313 +        if (gXulRuntime.OS.substring(0, 3) == "WIN") {
   1.314 +            return subprocess_win32(options);
   1.315 +        } else {
   1.316 +            return subprocess_unix(options);
   1.317 +        }
   1.318 +
   1.319 +    },
   1.320 +    registerDebugHandler: function(func) {
   1.321 +        gDebugFunc = func;
   1.322 +    },
   1.323 +    registerLogHandler: function(func) {
   1.324 +        gLogFunc = func;
   1.325 +    }
   1.326 +};
   1.327 +
   1.328 +
   1.329 +
   1.330 +function subprocess_win32(options) {
   1.331 +    var kernel32dll = ctypes.open(options.libc),
   1.332 +        hChildProcess,
   1.333 +        active = true,
   1.334 +        done = false,
   1.335 +        exitCode = -1,
   1.336 +        child = {},
   1.337 +        stdinWorker = null,
   1.338 +        stdoutWorker = null,
   1.339 +        stderrWorker = null,
   1.340 +        pendingWriteCount = 0,
   1.341 +        readers = options.mergeStderr ? 1 : 2,
   1.342 +        stdinOpenState = 2,
   1.343 +        error = '',
   1.344 +        output = '';
   1.345 +
   1.346 +    // stdin pipe states
   1.347 +    const OPEN = 2;
   1.348 +    const CLOSEABLE = 1;
   1.349 +    const CLOSED = 0;
   1.350 +
   1.351 +    //api declarations
   1.352 +    /*
   1.353 +    BOOL WINAPI CloseHandle(
   1.354 +      __in  HANDLE hObject
   1.355 +    );
   1.356 +    */
   1.357 +    var CloseHandle = kernel32dll.declare("CloseHandle",
   1.358 +                                            WinABI,
   1.359 +                                            BOOL,
   1.360 +                                            HANDLE
   1.361 +    );
   1.362 +
   1.363 +    /*
   1.364 +    BOOL WINAPI CreateProcess(
   1.365 +      __in_opt     LPCTSTR lpApplicationName,
   1.366 +      __inout_opt  LPTSTR lpCommandLine,
   1.367 +      __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   1.368 +      __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   1.369 +      __in         BOOL bInheritHandles,
   1.370 +      __in         DWORD dwCreationFlags,
   1.371 +      __in_opt     LPVOID lpEnvironment,
   1.372 +      __in_opt     LPCTSTR lpCurrentDirectory,
   1.373 +      __in         LPSTARTUPINFO lpStartupInfo,
   1.374 +      __out        LPPROCESS_INFORMATION lpProcessInformation
   1.375 +    );
   1.376 +     */
   1.377 +    var CreateProcessW = kernel32dll.declare("CreateProcessW",
   1.378 +                                            WinABI,
   1.379 +                                            BOOL,
   1.380 +                                            LPCTSTR,
   1.381 +                                            LPTSTR,
   1.382 +                                            SECURITY_ATTRIBUTES.ptr,
   1.383 +                                            SECURITY_ATTRIBUTES.ptr,
   1.384 +                                            BOOL,
   1.385 +                                            DWORD,
   1.386 +                                            LPVOID,
   1.387 +                                            LPCTSTR,
   1.388 +                                            STARTUPINFO.ptr,
   1.389 +                                            PROCESS_INFORMATION.ptr
   1.390 +                                         );
   1.391 +
   1.392 +//     /*
   1.393 +//     BOOL WINAPI ReadFile(
   1.394 +//       __in         HANDLE hFile,
   1.395 +//       __out        LPVOID ReadFileBuffer,
   1.396 +//       __in         DWORD nNumberOfBytesToRead,
   1.397 +//       __out_opt    LPDWORD lpNumberOfBytesRead,
   1.398 +//       __inout_opt  LPOVERLAPPED lpOverlapped
   1.399 +//     );
   1.400 +//     */
   1.401 +//     var ReadFileBufferSize = 1024,
   1.402 +//         ReadFileBuffer = ctypes.char.array(ReadFileBufferSize),
   1.403 +//         ReadFile = kernel32dll.declare("ReadFile",
   1.404 +//                                         WinABI,
   1.405 +//                                         BOOL,
   1.406 +//                                         HANDLE,
   1.407 +//                                         ReadFileBuffer,
   1.408 +//                                         DWORD,
   1.409 +//                                         LPDWORD,
   1.410 +//                                         OVERLAPPED.ptr
   1.411 +//     );
   1.412 +//
   1.413 +//     /*
   1.414 +//     BOOL WINAPI PeekNamedPipe(
   1.415 +//       __in       HANDLE hNamedPipe,
   1.416 +//       __out_opt  LPVOID lpBuffer,
   1.417 +//       __in       DWORD nBufferSize,
   1.418 +//       __out_opt  LPDWORD lpBytesRead,
   1.419 +//       __out_opt  LPDWORD lpTotalBytesAvail,
   1.420 +//       __out_opt  LPDWORD lpBytesLeftThisMessage
   1.421 +//     );
   1.422 +//     */
   1.423 +//     var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe",
   1.424 +//                                         WinABI,
   1.425 +//                                         BOOL,
   1.426 +//                                         HANDLE,
   1.427 +//                                         ReadFileBuffer,
   1.428 +//                                         DWORD,
   1.429 +//                                         LPDWORD,
   1.430 +//                                         LPDWORD,
   1.431 +//                                         LPDWORD
   1.432 +//     );
   1.433 +//
   1.434 +//     /*
   1.435 +//     BOOL WINAPI WriteFile(
   1.436 +//       __in         HANDLE hFile,
   1.437 +//       __in         LPCVOID lpBuffer,
   1.438 +//       __in         DWORD nNumberOfBytesToWrite,
   1.439 +//       __out_opt    LPDWORD lpNumberOfBytesWritten,
   1.440 +//       __inout_opt  LPOVERLAPPED lpOverlapped
   1.441 +//     );
   1.442 +//     */
   1.443 +//     var WriteFile = kernel32dll.declare("WriteFile",
   1.444 +//                                         WinABI,
   1.445 +//                                         BOOL,
   1.446 +//                                         HANDLE,
   1.447 +//                                         ctypes.char.ptr,
   1.448 +//                                         DWORD,
   1.449 +//                                         LPDWORD,
   1.450 +//                                         OVERLAPPED.ptr
   1.451 +//     );
   1.452 +
   1.453 +    /*
   1.454 +    BOOL WINAPI CreatePipe(
   1.455 +      __out     PHANDLE hReadPipe,
   1.456 +      __out     PHANDLE hWritePipe,
   1.457 +      __in_opt  LPSECURITY_ATTRIBUTES lpPipeAttributes,
   1.458 +      __in      DWORD nSize
   1.459 +    );
   1.460 +    */
   1.461 +    var CreatePipe = kernel32dll.declare("CreatePipe",
   1.462 +                                        WinABI,
   1.463 +                                        BOOL,
   1.464 +                                        HANDLE.ptr,
   1.465 +                                        HANDLE.ptr,
   1.466 +                                        SECURITY_ATTRIBUTES.ptr,
   1.467 +                                        DWORD
   1.468 +    );
   1.469 +
   1.470 +    /*
   1.471 +    HANDLE WINAPI GetCurrentProcess(void);
   1.472 +    */
   1.473 +    var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess",
   1.474 +                                        WinABI,
   1.475 +                                        HANDLE
   1.476 +    );
   1.477 +
   1.478 +    /*
   1.479 +    DWORD WINAPI GetLastError(void);
   1.480 +    */
   1.481 +    var GetLastError = kernel32dll.declare("GetLastError",
   1.482 +                                        WinABI,
   1.483 +                                        DWORD
   1.484 +    );
   1.485 +
   1.486 +    /*
   1.487 +    BOOL WINAPI DuplicateHandle(
   1.488 +      __in   HANDLE hSourceProcessHandle,
   1.489 +      __in   HANDLE hSourceHandle,
   1.490 +      __in   HANDLE hTargetProcessHandle,
   1.491 +      __out  LPHANDLE lpTargetHandle,
   1.492 +      __in   DWORD dwDesiredAccess,
   1.493 +      __in   BOOL bInheritHandle,
   1.494 +      __in   DWORD dwOptions
   1.495 +    );
   1.496 +    */
   1.497 +    var DuplicateHandle = kernel32dll.declare("DuplicateHandle",
   1.498 +                                        WinABI,
   1.499 +                                        BOOL,
   1.500 +                                        HANDLE,
   1.501 +                                        HANDLE,
   1.502 +                                        HANDLE,
   1.503 +                                        HANDLE.ptr,
   1.504 +                                        DWORD,
   1.505 +                                        BOOL,
   1.506 +                                        DWORD
   1.507 +    );
   1.508 +
   1.509 +
   1.510 +    /*
   1.511 +    BOOL WINAPI GetExitCodeProcess(
   1.512 +      __in   HANDLE hProcess,
   1.513 +      __out  LPDWORD lpExitCode
   1.514 +    );
   1.515 +    */
   1.516 +    var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess",
   1.517 +                                        WinABI,
   1.518 +                                        BOOL,
   1.519 +                                        HANDLE,
   1.520 +                                        LPDWORD
   1.521 +    );
   1.522 +
   1.523 +    /*
   1.524 +    DWORD WINAPI WaitForSingleObject(
   1.525 +      __in  HANDLE hHandle,
   1.526 +      __in  DWORD dwMilliseconds
   1.527 +    );
   1.528 +    */
   1.529 +    var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject",
   1.530 +                                        WinABI,
   1.531 +                                        DWORD,
   1.532 +                                        HANDLE,
   1.533 +                                        DWORD
   1.534 +    );
   1.535 +
   1.536 +    /*
   1.537 +    BOOL WINAPI TerminateProcess(
   1.538 +      __in  HANDLE hProcess,
   1.539 +      __in  UINT uExitCode
   1.540 +    );
   1.541 +    */
   1.542 +    var TerminateProcess = kernel32dll.declare("TerminateProcess",
   1.543 +                                        WinABI,
   1.544 +                                        BOOL,
   1.545 +                                        HANDLE,
   1.546 +                                        UINT
   1.547 +    );
   1.548 +
   1.549 +    //functions
   1.550 +    function popen(command, workdir, args, environment, child) {
   1.551 +        //escape arguments
   1.552 +        args.unshift(command);
   1.553 +        for (var i = 0; i < args.length; i++) {
   1.554 +          if (typeof args[i] != "string") { args[i] = args[i].toString(); }
   1.555 +          /* quote arguments with spaces */
   1.556 +          if (args[i].match(/\s/)) {
   1.557 +            args[i] = "\"" + args[i] + "\"";
   1.558 +          }
   1.559 +          /* If backslash is followed by a quote, double it */
   1.560 +          args[i] = args[i].replace(/\\\"/g, "\\\\\"");
   1.561 +        }
   1.562 +        command = args.join(' ');
   1.563 +
   1.564 +        environment = environment || [];
   1.565 +        if(environment.length) {
   1.566 +            //An environment block consists of
   1.567 +            //a null-terminated block of null-terminated strings.
   1.568 +            //Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar
   1.569 +            environment = ctypes.jschar.array()(environment.join('\0') + '\0');
   1.570 +        } else {
   1.571 +            environment = null;
   1.572 +        }
   1.573 +
   1.574 +        var hOutputReadTmp = new HANDLE(),
   1.575 +            hOutputRead = new HANDLE(),
   1.576 +            hOutputWrite = new HANDLE();
   1.577 +
   1.578 +        var hErrorRead = new HANDLE(),
   1.579 +            hErrorReadTmp = new HANDLE(),
   1.580 +            hErrorWrite = new HANDLE();
   1.581 +
   1.582 +        var hInputRead = new HANDLE(),
   1.583 +            hInputWriteTmp = new HANDLE(),
   1.584 +            hInputWrite = new HANDLE();
   1.585 +
   1.586 +        // Set up the security attributes struct.
   1.587 +        var sa = new SECURITY_ATTRIBUTES();
   1.588 +        sa.nLength = SECURITY_ATTRIBUTES.size;
   1.589 +        sa.lpSecurityDescriptor = null;
   1.590 +        sa.bInheritHandle = true;
   1.591 +
   1.592 +        // Create output pipe.
   1.593 +
   1.594 +        if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0))
   1.595 +            LogError('CreatePipe hOutputReadTmp failed');
   1.596 +
   1.597 +        if(options.mergeStderr) {
   1.598 +          // Create a duplicate of the output write handle for the std error
   1.599 +          // write handle. This is necessary in case the child application
   1.600 +          // closes one of its std output handles.
   1.601 +          if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
   1.602 +                               GetCurrentProcess(), hErrorWrite.address(), 0,
   1.603 +                               true, DUPLICATE_SAME_ACCESS))
   1.604 +             LogError("DuplicateHandle hOutputWrite failed");
   1.605 +        } else {
   1.606 +            // Create error pipe.
   1.607 +            if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
   1.608 +                LogError('CreatePipe hErrorReadTmp failed');
   1.609 +        }
   1.610 +
   1.611 +        // Create input pipe.
   1.612 +        if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
   1.613 +            LogError("CreatePipe hInputRead failed");
   1.614 +
   1.615 +        // Create new output/error read handle and the input write handles. Set
   1.616 +        // the Properties to FALSE. Otherwise, the child inherits the
   1.617 +        // properties and, as a result, non-closeable handles to the pipes
   1.618 +        // are created.
   1.619 +        if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
   1.620 +                             GetCurrentProcess(),
   1.621 +                             hOutputRead.address(), // Address of new handle.
   1.622 +                             0, false, // Make it uninheritable.
   1.623 +                             DUPLICATE_SAME_ACCESS))
   1.624 +             LogError("DupliateHandle hOutputReadTmp failed");
   1.625 +
   1.626 +        if(!options.mergeStderr) {
   1.627 +            if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
   1.628 +                             GetCurrentProcess(),
   1.629 +                             hErrorRead.address(), // Address of new handle.
   1.630 +                             0, false, // Make it uninheritable.
   1.631 +                             DUPLICATE_SAME_ACCESS))
   1.632 +             LogError("DupliateHandle hErrorReadTmp failed");
   1.633 +        }
   1.634 +        if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
   1.635 +                             GetCurrentProcess(),
   1.636 +                             hInputWrite.address(), // Address of new handle.
   1.637 +                             0, false, // Make it uninheritable.
   1.638 +                             DUPLICATE_SAME_ACCESS))
   1.639 +          LogError("DupliateHandle hInputWriteTmp failed");
   1.640 +
   1.641 +        // Close inheritable copies of the handles.
   1.642 +        if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
   1.643 +        if(!options.mergeStderr)
   1.644 +            if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
   1.645 +        if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
   1.646 +
   1.647 +        var pi = new PROCESS_INFORMATION();
   1.648 +        var si = new STARTUPINFO();
   1.649 +
   1.650 +        si.cb = STARTUPINFO.size;
   1.651 +        si.dwFlags = STARTF_USESTDHANDLES;
   1.652 +        si.hStdInput  = hInputRead;
   1.653 +        si.hStdOutput = hOutputWrite;
   1.654 +        si.hStdError  = hErrorWrite;
   1.655 +
   1.656 +        // Launch the process
   1.657 +        if(!CreateProcessW(null,            // executable name
   1.658 +                           command,         // command buffer
   1.659 +                           null,            // process security attribute
   1.660 +                           null,            // thread security attribute
   1.661 +                           true,            // inherits system handles
   1.662 +                           CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
   1.663 +                           environment,     // envrionment block
   1.664 +                           workdir,          // set as current directory
   1.665 +                           si.address(),    // (in) startup information
   1.666 +                           pi.address()     // (out) process information
   1.667 +        ))
   1.668 +            throw("Fatal - Could not launch subprocess '"+command+"'");
   1.669 +
   1.670 +        // Close any unnecessary handles.
   1.671 +        if (!CloseHandle(pi.hThread))
   1.672 +            LogError("CloseHandle pi.hThread failed");
   1.673 +
   1.674 +        // Close pipe handles (do not continue to modify the parent).
   1.675 +        // You need to make sure that no handles to the write end of the
   1.676 +        // output pipe are maintained in this process or else the pipe will
   1.677 +        // not close when the child process exits and the ReadFile will hang.
   1.678 +        if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
   1.679 +        if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
   1.680 +        if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
   1.681 +
   1.682 +        //return values
   1.683 +        child.stdin = hInputWrite;
   1.684 +        child.stdout = hOutputRead;
   1.685 +        child.stderr = options.mergeStderr ? undefined : hErrorRead;
   1.686 +        child.process = pi.hProcess;
   1.687 +        return pi.hProcess;
   1.688 +    }
   1.689 +
   1.690 +    /*
   1.691 +     * createStdinWriter ()
   1.692 +     *
   1.693 +     * Create a ChromeWorker object for writing data to the subprocess' stdin
   1.694 +     * pipe. The ChromeWorker object lives on a separate thread; this avoids
   1.695 +     * internal deadlocks.
   1.696 +     */
   1.697 +    function createStdinWriter() {
   1.698 +        debugLog("Creating new stdin worker\n");
   1.699 +        stdinWorker = new ChromeWorker(WORKER_URL_WIN);
   1.700 +        stdinWorker.onmessage = function(event) {
   1.701 +            switch(event.data) {
   1.702 +            case "WriteOK":
   1.703 +                pendingWriteCount--;
   1.704 +                debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
   1.705 +                break;
   1.706 +            case "ClosedOK":
   1.707 +                stdinOpenState = CLOSED;
   1.708 +                debugLog("Stdin pipe closed\n");
   1.709 +                break;
   1.710 +            default:
   1.711 +                debugLog("got msg from stdinWorker: "+event.data+"\n");
   1.712 +            }
   1.713 +        }
   1.714 +        stdinWorker.onerror = function(error) {
   1.715 +            pendingWriteCount--;
   1.716 +            LogError("got error from stdinWorker: "+error.message+"\n");
   1.717 +        }
   1.718 +
   1.719 +        stdinWorker.postMessage({msg: "init", libc: options.libc});
   1.720 +    }
   1.721 +
   1.722 +    /*
   1.723 +     * writeStdin()
   1.724 +     * @data: String containing the data to write
   1.725 +     *
   1.726 +     * Write data to the subprocess' stdin (equals to sending a request to the
   1.727 +     * ChromeWorker object to write the data).
   1.728 +     */
   1.729 +    function writeStdin(data) {
   1.730 +        ++pendingWriteCount;
   1.731 +        debugLog("sending "+data.length+" bytes to stdinWorker\n");
   1.732 +        var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
   1.733 +
   1.734 +        stdinWorker.postMessage({
   1.735 +                msg: 'write',
   1.736 +                pipe: pipePtr,
   1.737 +                data: data
   1.738 +            });
   1.739 +    }
   1.740 +
   1.741 +    /*
   1.742 +     * closeStdinHandle()
   1.743 +     *
   1.744 +     * Close the stdin pipe, either directly or by requesting the ChromeWorker to
   1.745 +     * close the pipe. The ChromeWorker will only close the pipe after the last write
   1.746 +     * request process is done.
   1.747 +     */
   1.748 +
   1.749 +    function closeStdinHandle() {
   1.750 +        debugLog("trying to close stdin\n");
   1.751 +        if (stdinOpenState != OPEN) return;
   1.752 +        stdinOpenState = CLOSEABLE;
   1.753 +
   1.754 +        if (stdinWorker) {
   1.755 +            debugLog("sending close stdin to worker\n");
   1.756 +            var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
   1.757 +            stdinWorker.postMessage({
   1.758 +                msg: 'close',
   1.759 +                pipe: pipePtr
   1.760 +            });
   1.761 +        }
   1.762 +        else {
   1.763 +            stdinOpenState = CLOSED;
   1.764 +            debugLog("Closing Stdin\n");
   1.765 +            CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed");
   1.766 +        }
   1.767 +    }
   1.768 +
   1.769 +
   1.770 +    /*
   1.771 +     * createReader(pipe, name)
   1.772 +     *
   1.773 +     * @pipe: handle to the pipe
   1.774 +     * @name: String containing the pipe name (stdout or stderr)
   1.775 +     *
   1.776 +     * Create a ChromeWorker object for reading data asynchronously from
   1.777 +     * the pipe (i.e. on a separate thread), and passing the result back to
   1.778 +     * the caller.
   1.779 +     */
   1.780 +    function createReader(pipe, name, callbackFunc) {
   1.781 +        var worker = new ChromeWorker(WORKER_URL_WIN);
   1.782 +        worker.onmessage = function(event) {
   1.783 +            switch(event.data.msg) {
   1.784 +            case "data":
   1.785 +                debugLog("got "+event.data.count+" bytes from "+name+"\n");
   1.786 +                var data = '';
   1.787 +                if (options.charset === null) {
   1.788 +                    data = getBytes(event.data.data);
   1.789 +                }
   1.790 +                else {
   1.791 +                    try {
   1.792 +                        data = convertBytes(event.data.data, options.charset);
   1.793 +                    }
   1.794 +                    catch(ex) {
   1.795 +                        console.warn("error decoding output: " + ex);
   1.796 +                        data = getBytes(event.data.data);
   1.797 +                    }
   1.798 +                }
   1.799 +
   1.800 +                callbackFunc(data);
   1.801 +                break;
   1.802 +            case "done":
   1.803 +                debugLog("Pipe "+name+" closed\n");
   1.804 +                --readers;
   1.805 +                if (readers == 0) cleanup();
   1.806 +                break;
   1.807 +            default:
   1.808 +                debugLog("Got msg from "+name+": "+event.data.data+"\n");
   1.809 +            }
   1.810 +        }
   1.811 +
   1.812 +        worker.onerror = function(errorMsg) {
   1.813 +            LogError("Got error from "+name+": "+errorMsg.message);
   1.814 +        }
   1.815 +
   1.816 +        var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
   1.817 +
   1.818 +        worker.postMessage({
   1.819 +                msg: 'read',
   1.820 +                pipe: pipePtr,
   1.821 +                libc: options.libc,
   1.822 +                charset: options.charset === null ? "null" : options.charset,
   1.823 +                name: name
   1.824 +            });
   1.825 +
   1.826 +        return worker;
   1.827 +    }
   1.828 +
   1.829 +    /*
   1.830 +     * readPipes()
   1.831 +     *
   1.832 +     * Open the pipes for reading from stdout and stderr
   1.833 +     */
   1.834 +    function readPipes() {
   1.835 +
   1.836 +        stdoutWorker = createReader(child.stdout, "stdout", function (data) {
   1.837 +            if(options.stdout) {
   1.838 +                setTimeout(function() {
   1.839 +                    options.stdout(data);
   1.840 +                }, 0);
   1.841 +            } else {
   1.842 +                output += data;
   1.843 +            }
   1.844 +        });
   1.845 +
   1.846 +
   1.847 +        if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
   1.848 +            if(options.stderr) {
   1.849 +                setTimeout(function() {
   1.850 +                    options.stderr(data);
   1.851 +                }, 0);
   1.852 +            } else {
   1.853 +                error += data;
   1.854 +            }
   1.855 +        });
   1.856 +    }
   1.857 +
   1.858 +    /*
   1.859 +     * cleanup()
   1.860 +     *
   1.861 +     * close stdin if needed, get the exit code from the subprocess and invoke
   1.862 +     * the caller's done() function.
   1.863 +     *
   1.864 +     * Note: because stdout() and stderr() are called using setTimeout, we need to
   1.865 +     * do the same here in order to guarantee the message sequence.
   1.866 +     */
   1.867 +    function cleanup() {
   1.868 +        debugLog("Cleanup called\n");
   1.869 +        if(active) {
   1.870 +            active = false;
   1.871 +
   1.872 +            closeStdinHandle(); // should only be required in case of errors
   1.873 +
   1.874 +            var exit = new DWORD();
   1.875 +            GetExitCodeProcess(child.process, exit.address());
   1.876 +            exitCode = exit.value;
   1.877 +
   1.878 +            if (stdinWorker)
   1.879 +                stdinWorker.postMessage({msg: 'stop'})
   1.880 +
   1.881 +            setTimeout(function _done() {
   1.882 +                if (options.done) {
   1.883 +                    try {
   1.884 +                        options.done({
   1.885 +                            exitCode: exitCode,
   1.886 +                            stdout: output,
   1.887 +                            stderr: error,
   1.888 +                        });
   1.889 +                    }
   1.890 +                    catch (ex) {
   1.891 +                        // prevent from blocking if options.done() throws an error
   1.892 +                        done = true;
   1.893 +                        throw ex;
   1.894 +                    }
   1.895 +                }
   1.896 +                done = true;
   1.897 +            }, 0);
   1.898 +            kernel32dll.close();
   1.899 +        }
   1.900 +    }
   1.901 +
   1.902 +    var cmdStr = getCommandStr(options.command);
   1.903 +    var workDir = getWorkDir(options.workdir);
   1.904 +
   1.905 +    //main
   1.906 +    hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
   1.907 +
   1.908 +    readPipes();
   1.909 +
   1.910 +    if (options.stdin) {
   1.911 +       createStdinWriter();
   1.912 +
   1.913 +        if(typeof(options.stdin) == 'function') {
   1.914 +            try {
   1.915 +                options.stdin({
   1.916 +                    write: function(data) {
   1.917 +                        writeStdin(data);
   1.918 +                    },
   1.919 +                    close: function() {
   1.920 +                        closeStdinHandle();
   1.921 +                    }
   1.922 +                });
   1.923 +            }
   1.924 +            catch (ex) {
   1.925 +                // prevent from failing if options.stdin() throws an exception
   1.926 +                closeStdinHandle();
   1.927 +                throw ex;
   1.928 +            }
   1.929 +        } else {
   1.930 +            writeStdin(options.stdin);
   1.931 +            closeStdinHandle();
   1.932 +        }
   1.933 +    }
   1.934 +    else
   1.935 +        closeStdinHandle();
   1.936 +
   1.937 +    return {
   1.938 +        kill: function(hardKill) {
   1.939 +            // hardKill is currently ignored on Windows
   1.940 +            var r = !!TerminateProcess(child.process, 255);
   1.941 +            cleanup(-1);
   1.942 +            return r;
   1.943 +        },
   1.944 +        wait: function() {
   1.945 +            // wait for async operations to complete
   1.946 +            var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
   1.947 +            while (!done) thread.processNextEvent(true);
   1.948 +
   1.949 +            return exitCode;
   1.950 +        }
   1.951 +    }
   1.952 +}
   1.953 +
   1.954 +
   1.955 +function subprocess_unix(options) {
   1.956 +    // stdin pipe states
   1.957 +    const OPEN = 2;
   1.958 +    const CLOSEABLE = 1;
   1.959 +    const CLOSED = 0;
   1.960 +
   1.961 +    var libc = ctypes.open(options.libc),
   1.962 +        active = true,
   1.963 +        done = false,
   1.964 +        exitCode = -1,
   1.965 +        workerExitCode = 0,
   1.966 +        child = {},
   1.967 +        pid = -1,
   1.968 +        stdinWorker = null,
   1.969 +        stdoutWorker = null,
   1.970 +        stderrWorker = null,
   1.971 +        pendingWriteCount = 0,
   1.972 +        readers = options.mergeStderr ? 1 : 2,
   1.973 +        stdinOpenState = OPEN,
   1.974 +        error = '',
   1.975 +        output = '';
   1.976 +
   1.977 +    //api declarations
   1.978 +
   1.979 +    //pid_t fork(void);
   1.980 +    var fork = libc.declare("fork",
   1.981 +                         ctypes.default_abi,
   1.982 +                         pid_t
   1.983 +    );
   1.984 +
   1.985 +    //NULL terminated array of strings, argv[0] will be command >> + 2
   1.986 +    var argv = ctypes.char.ptr.array(options.arguments.length + 2);
   1.987 +    var envp = ctypes.char.ptr.array(options.environment.length + 1);
   1.988 +
   1.989 +    // posix_spawn_file_actions_t is a complex struct that may be different on
   1.990 +    // each platform. We do not care about its attributes, we don't need to
   1.991 +    // get access to them, but we do need to allocate the right amount
   1.992 +    // of memory for it.
   1.993 +    // Bug 936297 - Use OS.File internals to fetch
   1.994 +    // sizeof(posix_spawn_file_actions_t)
   1.995 +    var { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
   1.996 +    var sizeof_file_actions_t = OS.Constants.libc.OSFILE_SIZEOF_DIRENT;
   1.997 +    var posix_spawn_file_actions_t = ctypes.uint8_t.array(sizeof_file_actions_t);
   1.998 +
   1.999 +    //int posix_spawn(pid_t *restrict pid, const char *restrict path,
  1.1000 +    //   const posix_spawn_file_actions_t *file_actions,
  1.1001 +    //   const posix_spawnattr_t *restrict attrp,
  1.1002 +    //   char *const argv[restrict], char *const envp[restrict]);
  1.1003 +    var posix_spawn = libc.declare("posix_spawn",
  1.1004 +                         ctypes.default_abi,
  1.1005 +                         ctypes.int,
  1.1006 +                         pid_t.ptr,
  1.1007 +                         ctypes.char.ptr,
  1.1008 +                         posix_spawn_file_actions_t.ptr,
  1.1009 +                         ctypes.voidptr_t,
  1.1010 +                         argv,
  1.1011 +                         envp
  1.1012 +    );
  1.1013 +
  1.1014 +    //int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
  1.1015 +    var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init",
  1.1016 +                         ctypes.default_abi,
  1.1017 +                         ctypes.int,
  1.1018 +                         posix_spawn_file_actions_t.ptr
  1.1019 +    );
  1.1020 +
  1.1021 +    //int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
  1.1022 +    var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy",
  1.1023 +                         ctypes.default_abi,
  1.1024 +                         ctypes.int,
  1.1025 +                         posix_spawn_file_actions_t.ptr
  1.1026 +    );
  1.1027 +
  1.1028 +    // int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *
  1.1029 +    //                                      file_actions, int fildes, int newfildes);
  1.1030 +    var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2",
  1.1031 +                         ctypes.default_abi,
  1.1032 +                         ctypes.int,
  1.1033 +                         posix_spawn_file_actions_t.ptr,
  1.1034 +                         ctypes.int,
  1.1035 +                         ctypes.int
  1.1036 +    );
  1.1037 +
  1.1038 +    // int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *
  1.1039 +    //                                       file_actions, int fildes);
  1.1040 +    var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose",
  1.1041 +                         ctypes.default_abi,
  1.1042 +                         ctypes.int,
  1.1043 +                         posix_spawn_file_actions_t.ptr,
  1.1044 +                         ctypes.int
  1.1045 +    );
  1.1046 +
  1.1047 +    //int pipe(int pipefd[2]);
  1.1048 +    var pipefd = ctypes.int.array(2);
  1.1049 +    var pipe = libc.declare("pipe",
  1.1050 +                         ctypes.default_abi,
  1.1051 +                         ctypes.int,
  1.1052 +                         pipefd
  1.1053 +    );
  1.1054 +
  1.1055 +    //int close(int fd);
  1.1056 +    var close = libc.declare("close",
  1.1057 +                          ctypes.default_abi,
  1.1058 +                          ctypes.int,
  1.1059 +                          ctypes.int
  1.1060 +    );
  1.1061 +
  1.1062 +    //pid_t waitpid(pid_t pid, int *status, int options);
  1.1063 +    var waitpid = libc.declare("waitpid",
  1.1064 +                          ctypes.default_abi,
  1.1065 +                          pid_t,
  1.1066 +                          pid_t,
  1.1067 +                          ctypes.int.ptr,
  1.1068 +                          ctypes.int
  1.1069 +    );
  1.1070 +
  1.1071 +    //int kill(pid_t pid, int sig);
  1.1072 +    var kill = libc.declare("kill",
  1.1073 +                          ctypes.default_abi,
  1.1074 +                          ctypes.int,
  1.1075 +                          pid_t,
  1.1076 +                          ctypes.int
  1.1077 +    );
  1.1078 +
  1.1079 +    //int read(int fd, void *buf, size_t count);
  1.1080 +    var bufferSize = 1024;
  1.1081 +    var buffer = ctypes.char.array(bufferSize);
  1.1082 +    var read = libc.declare("read",
  1.1083 +                          ctypes.default_abi,
  1.1084 +                          ctypes.int,
  1.1085 +                          ctypes.int,
  1.1086 +                          buffer,
  1.1087 +                          ctypes.int
  1.1088 +    );
  1.1089 +
  1.1090 +    //ssize_t write(int fd, const void *buf, size_t count);
  1.1091 +    var write = libc.declare("write",
  1.1092 +                          ctypes.default_abi,
  1.1093 +                          ctypes.int,
  1.1094 +                          ctypes.int,
  1.1095 +                          ctypes.char.ptr,
  1.1096 +                          ctypes.int
  1.1097 +    );
  1.1098 +
  1.1099 +    //int chdir(const char *path);
  1.1100 +    var chdir = libc.declare("chdir",
  1.1101 +                          ctypes.default_abi,
  1.1102 +                          ctypes.int,
  1.1103 +                          ctypes.char.ptr
  1.1104 +    );
  1.1105 +
  1.1106 +    //int fcntl(int fd, int cmd, ... /* arg */ );
  1.1107 +    var fcntl = libc.declare("fcntl",
  1.1108 +                          ctypes.default_abi,
  1.1109 +                          ctypes.int,
  1.1110 +                          ctypes.int,
  1.1111 +                          ctypes.int,
  1.1112 +                          ctypes.int
  1.1113 +    );
  1.1114 +
  1.1115 +    function popen(command, workdir, args, environment, child) {
  1.1116 +        var _in,
  1.1117 +            _out,
  1.1118 +            _err,
  1.1119 +            pid,
  1.1120 +            rc;
  1.1121 +        _in = new pipefd();
  1.1122 +        _out = new pipefd();
  1.1123 +        if(!options.mergeStderr)
  1.1124 +            _err = new pipefd();
  1.1125 +
  1.1126 +        var _args = argv();
  1.1127 +        args.unshift(command);
  1.1128 +        for(var i=0;i<args.length;i++) {
  1.1129 +            _args[i] = ctypes.char.array()(args[i]);
  1.1130 +        }
  1.1131 +        var _envp = envp();
  1.1132 +        for(var i=0;i<environment.length;i++) {
  1.1133 +            _envp[i] = ctypes.char.array()(environment[i]);
  1.1134 +            // LogError(_envp);
  1.1135 +        }
  1.1136 +
  1.1137 +        rc = pipe(_in);
  1.1138 +        if (rc < 0) {
  1.1139 +            return -1;
  1.1140 +        }
  1.1141 +        rc = pipe(_out);
  1.1142 +        fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK));
  1.1143 +        if (rc < 0) {
  1.1144 +            close(_in[0]);
  1.1145 +            close(_in[1]);
  1.1146 +            return -1
  1.1147 +        }
  1.1148 +        if(!options.mergeStderr) {
  1.1149 +            rc = pipe(_err);
  1.1150 +            fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK));
  1.1151 +            if (rc < 0) {
  1.1152 +                close(_in[0]);
  1.1153 +                close(_in[1]);
  1.1154 +                close(_out[0]);
  1.1155 +                close(_out[1]);
  1.1156 +                return -1
  1.1157 +            }
  1.1158 +        }
  1.1159 +
  1.1160 +        let STDIN_FILENO = 0;
  1.1161 +        let STDOUT_FILENO = 1;
  1.1162 +        let STDERR_FILENO = 2;
  1.1163 +
  1.1164 +        let action = posix_spawn_file_actions_t();
  1.1165 +        posix_spawn_file_actions_init(action.address());
  1.1166 +
  1.1167 +        posix_spawn_file_actions_adddup2(action.address(), _in[0], STDIN_FILENO);
  1.1168 +        posix_spawn_file_actions_addclose(action.address(), _in[1]);
  1.1169 +        posix_spawn_file_actions_addclose(action.address(), _in[0]);
  1.1170 +
  1.1171 +        posix_spawn_file_actions_adddup2(action.address(), _out[1], STDOUT_FILENO);
  1.1172 +        posix_spawn_file_actions_addclose(action.address(), _out[1]);
  1.1173 +        posix_spawn_file_actions_addclose(action.address(), _out[0]);
  1.1174 +
  1.1175 +        if (!options.mergeStderr) {
  1.1176 +          posix_spawn_file_actions_adddup2(action.address(), _err[1], STDERR_FILENO);
  1.1177 +          posix_spawn_file_actions_addclose(action.address(), _err[1]);
  1.1178 +          posix_spawn_file_actions_addclose(action.address(), _err[0]);
  1.1179 +        }
  1.1180 +
  1.1181 +        // posix_spawn doesn't support setting a custom workdir for the child,
  1.1182 +        // so change the cwd in the parent process before launching the child process.
  1.1183 +        if (workdir) {
  1.1184 +          if (chdir(workdir) < 0) {
  1.1185 +            throw new Error("Unable to change workdir before launching child process");
  1.1186 +          }
  1.1187 +        }
  1.1188 +
  1.1189 +        closeOtherFds(action, _in[1], _out[0], options.mergeStderr ? undefined : _err[0]);
  1.1190 +
  1.1191 +        let id = pid_t(0);
  1.1192 +        let rv = posix_spawn(id.address(), command, action.address(), null, _args, _envp);
  1.1193 +        posix_spawn_file_actions_destroy(action.address());
  1.1194 +        if (rv != 0) {
  1.1195 +          // we should not really end up here
  1.1196 +          if(!options.mergeStderr) {
  1.1197 +            close(_err[0]);
  1.1198 +            close(_err[1]);
  1.1199 +          }
  1.1200 +          close(_out[0]);
  1.1201 +          close(_out[1]);
  1.1202 +          close(_in[0]);
  1.1203 +          close(_in[1]);
  1.1204 +          throw new Error("Fatal - failed to create subprocess '"+command+"'");
  1.1205 +        }
  1.1206 +        pid = id.value;
  1.1207 +
  1.1208 +        close(_in[0]);
  1.1209 +        close(_out[1]);
  1.1210 +        if (!options.mergeStderr)
  1.1211 +          close(_err[1]);
  1.1212 +        child.stdin  = _in[1];
  1.1213 +        child.stdout = _out[0];
  1.1214 +        child.stderr = options.mergeStderr ? undefined : _err[0];
  1.1215 +        child.pid = pid;
  1.1216 +
  1.1217 +        return pid;
  1.1218 +    }
  1.1219 +
  1.1220 +
  1.1221 +    // close any file descriptors that are not required for the process
  1.1222 +    function closeOtherFds(action, fdIn, fdOut, fdErr) {
  1.1223 +        // Unfortunately on mac, any fd registered in posix_spawn_file_actions_addclose
  1.1224 +        // that can't be closed correctly will make posix_spawn fail...
  1.1225 +        // Even if we ensure registering only still opened fds.
  1.1226 +        if (gXulRuntime.OS == "Darwin")
  1.1227 +            return;
  1.1228 +
  1.1229 +        var maxFD = 256; // arbitrary max
  1.1230 +
  1.1231 +
  1.1232 +        var rlim_t = getPlatformValue(RLIM_T);
  1.1233 +
  1.1234 +        const RLIMITS = new ctypes.StructType("RLIMITS", [
  1.1235 +            {"rlim_cur": rlim_t},
  1.1236 +            {"rlim_max": rlim_t}
  1.1237 +        ]);
  1.1238 +
  1.1239 +        try {
  1.1240 +            var getrlimit = libc.declare("getrlimit",
  1.1241 +                                  ctypes.default_abi,
  1.1242 +                                  ctypes.int,
  1.1243 +                                  ctypes.int,
  1.1244 +                                  RLIMITS.ptr
  1.1245 +            );
  1.1246 +
  1.1247 +            var rl = new RLIMITS();
  1.1248 +            if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) == 0) {
  1.1249 +                maxFD = rl.rlim_cur;
  1.1250 +            }
  1.1251 +            debugLog("getlimit: maxFD="+maxFD+"\n");
  1.1252 +
  1.1253 +        }
  1.1254 +        catch(ex) {
  1.1255 +            debugLog("getrlimit: no such function on this OS\n");
  1.1256 +            debugLog(ex.toString());
  1.1257 +        }
  1.1258 +
  1.1259 +        // close any file descriptors
  1.1260 +        // fd's 0-2 are already closed
  1.1261 +        for (var i = 3; i < maxFD; i++) {
  1.1262 +            if (i != fdIn && i != fdOut && i != fdErr && fcntl(i, F_GETFD, -1) >= 0) {
  1.1263 +                posix_spawn_file_actions_addclose(action.address(), i);
  1.1264 +            }
  1.1265 +        }
  1.1266 +    }
  1.1267 +
  1.1268 +    /*
  1.1269 +     * createStdinWriter ()
  1.1270 +     *
  1.1271 +     * Create a ChromeWorker object for writing data to the subprocess' stdin
  1.1272 +     * pipe. The ChromeWorker object lives on a separate thread; this avoids
  1.1273 +     * internal deadlocks.
  1.1274 +     */
  1.1275 +    function createStdinWriter() {
  1.1276 +        debugLog("Creating new stdin worker\n");
  1.1277 +        stdinWorker = new ChromeWorker(WORKER_URL_UNIX);
  1.1278 +        stdinWorker.onmessage = function(event) {
  1.1279 +            switch (event.data.msg) {
  1.1280 +            case "info":
  1.1281 +                switch(event.data.data) {
  1.1282 +                case "WriteOK":
  1.1283 +                    pendingWriteCount--;
  1.1284 +                    debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
  1.1285 +                    break;
  1.1286 +                case "ClosedOK":
  1.1287 +                    stdinOpenState = CLOSED;
  1.1288 +                    debugLog("Stdin pipe closed\n");
  1.1289 +                    break;
  1.1290 +                default:
  1.1291 +                    debugLog("got msg from stdinWorker: "+event.data.data+"\n");
  1.1292 +                }
  1.1293 +                break;
  1.1294 +            case "debug":
  1.1295 +                debugLog("stdinWorker: "+event.data.data+"\n");
  1.1296 +                break;
  1.1297 +            case "error":
  1.1298 +                LogError("got error from stdinWorker: "+event.data.data+"\n");
  1.1299 +                pendingWriteCount = 0;
  1.1300 +                stdinOpenState = CLOSED;
  1.1301 +            }
  1.1302 +        }
  1.1303 +        stdinWorker.onerror = function(error) {
  1.1304 +            pendingWriteCount = 0;
  1.1305 +            closeStdinHandle();
  1.1306 +            LogError("got error from stdinWorker: "+error.message+"\n");
  1.1307 +        }
  1.1308 +        stdinWorker.postMessage({msg: "init", libc: options.libc});
  1.1309 +    }
  1.1310 +
  1.1311 +    /*
  1.1312 +     * writeStdin()
  1.1313 +     * @data: String containing the data to write
  1.1314 +     *
  1.1315 +     * Write data to the subprocess' stdin (equals to sending a request to the
  1.1316 +     * ChromeWorker object to write the data).
  1.1317 +     */
  1.1318 +    function writeStdin(data) {
  1.1319 +        if (stdinOpenState == CLOSED) return; // do not write to closed pipes
  1.1320 +
  1.1321 +        ++pendingWriteCount;
  1.1322 +        debugLog("sending "+data.length+" bytes to stdinWorker\n");
  1.1323 +        var pipe = parseInt(child.stdin);
  1.1324 +
  1.1325 +        stdinWorker.postMessage({
  1.1326 +            msg: 'write',
  1.1327 +            pipe: pipe,
  1.1328 +            data: data
  1.1329 +        });
  1.1330 +    }
  1.1331 +
  1.1332 +
  1.1333 +    /*
  1.1334 +     * closeStdinHandle()
  1.1335 +     *
  1.1336 +     * Close the stdin pipe, either directly or by requesting the ChromeWorker to
  1.1337 +     * close the pipe. The ChromeWorker will only close the pipe after the last write
  1.1338 +     * request process is done.
  1.1339 +     */
  1.1340 +
  1.1341 +    function closeStdinHandle() {
  1.1342 +        debugLog("trying to close stdin\n");
  1.1343 +        if (stdinOpenState != OPEN) return;
  1.1344 +        stdinOpenState = CLOSEABLE;
  1.1345 +
  1.1346 +        if (stdinWorker) {
  1.1347 +            debugLog("sending close stdin to worker\n");
  1.1348 +            var pipePtr = parseInt(child.stdin);
  1.1349 +
  1.1350 +            stdinWorker.postMessage({
  1.1351 +                msg: 'close',
  1.1352 +                pipe: pipePtr
  1.1353 +            });
  1.1354 +        }
  1.1355 +        else {
  1.1356 +            stdinOpenState = CLOSED;
  1.1357 +            debugLog("Closing Stdin\n");
  1.1358 +            close(child.stdin) && LogError("CloseHandle stdin failed");
  1.1359 +        }
  1.1360 +    }
  1.1361 +
  1.1362 +
  1.1363 +    /*
  1.1364 +     * createReader(pipe, name)
  1.1365 +     *
  1.1366 +     * @pipe: handle to the pipe
  1.1367 +     * @name: String containing the pipe name (stdout or stderr)
  1.1368 +     * @callbackFunc: function to be called with the read data
  1.1369 +     *
  1.1370 +     * Create a ChromeWorker object for reading data asynchronously from
  1.1371 +     * the pipe (i.e. on a separate thread), and passing the result back to
  1.1372 +     * the caller.
  1.1373 +     *
  1.1374 +     */
  1.1375 +    function createReader(pipe, name, callbackFunc) {
  1.1376 +        var worker = new ChromeWorker(WORKER_URL_UNIX);
  1.1377 +        worker.onmessage = function(event) {
  1.1378 +            switch(event.data.msg) {
  1.1379 +            case "data":
  1.1380 +                debugLog("got "+event.data.count+" bytes from "+name+"\n");
  1.1381 +                var data = '';
  1.1382 +                if (options.charset === null) {
  1.1383 +                    data = getBytes(event.data.data);
  1.1384 +                }
  1.1385 +                else {
  1.1386 +                    try {
  1.1387 +                        data = convertBytes(event.data.data, options.charset);
  1.1388 +                    }
  1.1389 +                    catch(ex) {
  1.1390 +                        console.warn("error decoding output: " + ex);
  1.1391 +                        data = getBytes(event.data.data);
  1.1392 +                    }
  1.1393 +                }
  1.1394 +
  1.1395 +                callbackFunc(data);
  1.1396 +                break;
  1.1397 +            case "done":
  1.1398 +                debugLog("Pipe "+name+" closed\n");
  1.1399 +                if (event.data.data != 0) workerExitCode = event.data.data;
  1.1400 +                --readers;
  1.1401 +                if (readers == 0) cleanup();
  1.1402 +                break;
  1.1403 +            default:
  1.1404 +                debugLog("Got msg from "+name+": "+event.data.data+"\n");
  1.1405 +            }
  1.1406 +        }
  1.1407 +        worker.onerror = function(error) {
  1.1408 +            LogError("Got error from "+name+": "+error.message);
  1.1409 +        }
  1.1410 +
  1.1411 +        worker.postMessage({
  1.1412 +                msg: 'read',
  1.1413 +                pipe: pipe,
  1.1414 +                pid: pid,
  1.1415 +                libc: options.libc,
  1.1416 +                charset: options.charset === null ? "null" : options.charset,
  1.1417 +                name: name
  1.1418 +            });
  1.1419 +
  1.1420 +        return worker;
  1.1421 +    }
  1.1422 +
  1.1423 +    /*
  1.1424 +     * readPipes()
  1.1425 +     *
  1.1426 +     * Open the pipes for reading from stdout and stderr
  1.1427 +     */
  1.1428 +    function readPipes() {
  1.1429 +
  1.1430 +        stdoutWorker = createReader(child.stdout, "stdout", function (data) {
  1.1431 +            if(options.stdout) {
  1.1432 +                setTimeout(function() {
  1.1433 +                    options.stdout(data);
  1.1434 +                }, 0);
  1.1435 +            } else {
  1.1436 +                output += data;
  1.1437 +            }
  1.1438 +        });
  1.1439 +
  1.1440 +        if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
  1.1441 +            if(options.stderr) {
  1.1442 +                setTimeout(function() {
  1.1443 +                    options.stderr(data);
  1.1444 +                 }, 0);
  1.1445 +            } else {
  1.1446 +                error += data;
  1.1447 +            }
  1.1448 +        });
  1.1449 +    }
  1.1450 +
  1.1451 +    function cleanup() {
  1.1452 +        debugLog("Cleanup called\n");
  1.1453 +        if(active) {
  1.1454 +            active = false;
  1.1455 +
  1.1456 +            closeStdinHandle(); // should only be required in case of errors
  1.1457 +
  1.1458 +            var result, status = ctypes.int();
  1.1459 +            result = waitpid(child.pid, status.address(), 0);
  1.1460 +            if (result > 0)
  1.1461 +                exitCode = status.value
  1.1462 +            else
  1.1463 +                if (workerExitCode >= 0)
  1.1464 +                    exitCode = workerExitCode
  1.1465 +                else
  1.1466 +                    exitCode = status.value;
  1.1467 +
  1.1468 +            if (stdinWorker)
  1.1469 +                stdinWorker.postMessage({msg: 'stop'})
  1.1470 +
  1.1471 +            setTimeout(function _done() {
  1.1472 +                if (options.done) {
  1.1473 +                    try {
  1.1474 +                        options.done({
  1.1475 +                            exitCode: exitCode,
  1.1476 +                            stdout: output,
  1.1477 +                            stderr: error,
  1.1478 +                        });
  1.1479 +                    }
  1.1480 +                    catch(ex) {
  1.1481 +                        // prevent from blocking if options.done() throws an error
  1.1482 +                        done = true;
  1.1483 +                        throw ex;
  1.1484 +                    }
  1.1485 +
  1.1486 +                }
  1.1487 +                done = true;
  1.1488 +            }, 0);
  1.1489 +
  1.1490 +            libc.close();
  1.1491 +        }
  1.1492 +    }
  1.1493 +
  1.1494 +    //main
  1.1495 +
  1.1496 +    var cmdStr = getCommandStr(options.command);
  1.1497 +    var workDir = getWorkDir(options.workdir);
  1.1498 +
  1.1499 +    child = {};
  1.1500 +    pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
  1.1501 +
  1.1502 +    debugLog("subprocess started; got PID "+pid+"\n");
  1.1503 +
  1.1504 +    readPipes();
  1.1505 +
  1.1506 +    if (options.stdin) {
  1.1507 +        createStdinWriter();
  1.1508 +        if(typeof(options.stdin) == 'function') {
  1.1509 +            try {
  1.1510 +                options.stdin({
  1.1511 +                    write: function(data) {
  1.1512 +                        writeStdin(data);
  1.1513 +                    },
  1.1514 +                    close: function() {
  1.1515 +                        closeStdinHandle();
  1.1516 +                    }
  1.1517 +                });
  1.1518 +            }
  1.1519 +            catch(ex) {
  1.1520 +                // prevent from failing if options.stdin() throws an exception
  1.1521 +                closeStdinHandle();
  1.1522 +                throw ex;
  1.1523 +            }
  1.1524 +        } else {
  1.1525 +            writeStdin(options.stdin);
  1.1526 +            closeStdinHandle();
  1.1527 +        }
  1.1528 +    }
  1.1529 +
  1.1530 +    return {
  1.1531 +        wait: function() {
  1.1532 +            // wait for async operations to complete
  1.1533 +            var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
  1.1534 +            while (! done) thread.processNextEvent(true)
  1.1535 +            return exitCode;
  1.1536 +        },
  1.1537 +        kill: function(hardKill) {
  1.1538 +            var rv = kill(pid, (hardKill ? 9: 15));
  1.1539 +            cleanup(-1);
  1.1540 +            return rv;
  1.1541 +        }
  1.1542 +    }
  1.1543 +}
  1.1544 +
  1.1545 +
  1.1546 +module.exports = subprocess;

mercurial