addon-sdk/source/lib/sdk/system/child_process/subprocess.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 // -*- coding: utf-8 -*-
     2 // vim: et:ts=4:sw=4:sts=4:ft=javascript
     3 /* ***** BEGIN LICENSE BLOCK *****
     4  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
     5  *
     6  * The contents of this file are subject to the Mozilla Public
     7  * License Version 1.1 (the "MPL"); you may not use this file
     8  * except in compliance with the MPL. You may obtain a copy of
     9  * the MPL at http://www.mozilla.org/MPL/
    10  *
    11  * Software distributed under the MPL is distributed on an "AS
    12  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
    13  * implied. See the MPL for the specific language governing
    14  * rights and limitations under the MPL.
    15  *
    16  * The Original Code is subprocess.jsm.
    17  *
    18  * The Initial Developer of this code is Jan Gerber.
    19  * Portions created by Jan Gerber <j@mailb.org>
    20  * are Copyright (C) 2011 Jan Gerber.
    21  * All Rights Reserved.
    22  *
    23  * Contributor(s):
    24  * Patrick Brunschwig <patrick@enigmail.net>
    25  *
    26  * Alternatively, the contents of this file may be used under the terms of
    27  * either the GNU General Public License Version 2 or later (the "GPL"), or
    28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    29  * in which case the provisions of the GPL or the LGPL are applicable instead
    30  * of those above. If you wish to allow use of your version of this file only
    31  * under the terms of either the GPL or the LGPL, and not to allow others to
    32  * use your version of this file under the terms of the MPL, indicate your
    33  * decision by deleting the provisions above and replace them with the notice
    34  * and other provisions required by the GPL or the LGPL. If you do not delete
    35  * the provisions above, a recipient may use your version of this file under
    36  * the terms of any one of the MPL, the GPL or the LGPL.
    37  * ***** END LICENSE BLOCK ***** */
    39 /*
    40  * This object allows to start a process, and read/write data to/from it
    41  * using stdin/stdout/stderr streams.
    42  * Usage example:
    43  *
    44  *  var p = subprocess.call({
    45  *    command:     '/bin/foo',
    46  *    arguments:   ['-v', 'foo'],
    47  *    environment: [ "XYZ=abc", "MYVAR=def" ],
    48  *    charset: 'UTF-8',
    49  *    workdir: '/home/foo',
    50  *    //stdin: "some value to write to stdin\nfoobar",
    51  *    stdin: function(stdin) {
    52  *      stdin.write("some value to write to stdin\nfoobar");
    53  *      stdin.close();
    54  *    },
    55  *    stdout: function(data) {
    56  *      dump("got data on stdout:" + data + "\n");
    57  *    },
    58  *    stderr: function(data) {
    59  *      dump("got data on stderr:" + data + "\n");
    60  *    },
    61  *    done: function(result) {
    62  *      dump("process terminated with " + result.exitCode + "\n");
    63  *    },
    64  *    mergeStderr: false
    65  *  });
    66  *  p.wait(); // wait for the subprocess to terminate
    67  *            // this will block the main thread,
    68  *            // only do if you can wait that long
    69  *
    70  *
    71  * Description of parameters:
    72  * --------------------------
    73  * Apart from <command>, all arguments are optional.
    74  *
    75  * command:     either a |nsIFile| object pointing to an executable file or a
    76  *              String containing the platform-dependent path to an executable
    77  *              file.
    78  *
    79  * arguments:   optional string array containing the arguments to the command.
    80  *
    81  * environment: optional string array containing environment variables to pass
    82  *              to the command. The array elements must have the form
    83  *              "VAR=data". Please note that if environment is defined, it
    84  *              replaces any existing environment variables for the subprocess.
    85  *
    86  * charset:     Output is decoded with given charset and a string is returned.
    87  *              If charset is undefined, "UTF-8" is used as default.
    88  *              To get binary data, set this to null and the returned string
    89  *              is not decoded in any way.
    90  *
    91  * workdir:     optional; String containing the platform-dependent path to a
    92  *              directory to become the current working directory of the subprocess.
    93  *
    94  * stdin:       optional input data for the process to be passed on standard
    95  *              input. stdin can either be a string or a function.
    96  *              A |string| gets written to stdin and stdin gets closed;
    97  *              A |function| gets passed an object with write and close function.
    98  *              Please note that the write() function will return almost immediately;
    99  *              data is always written asynchronously on a separate thread.
   100  *
   101  * stdout:      an optional function that can receive output data from the
   102  *              process. The stdout-function is called asynchronously; it can be
   103  *              called mutliple times during the execution of a process.
   104  *              At a minimum at each occurance of \n or \r.
   105  *              Please note that null-characters might need to be escaped
   106  *              with something like 'data.replace(/\0/g, "\\0");'.
   107  *
   108  * stderr:      an optional function that can receive stderr data from the
   109  *              process. The stderr-function is called asynchronously; it can be
   110  *              called mutliple times during the execution of a process. Please
   111  *              note that null-characters might need to be escaped with
   112  *              something like 'data.replace(/\0/g, "\\0");'.
   113  *              (on windows it only gets called once right now)
   114  *
   115  * done:        optional function that is called when the process has terminated.
   116  *              The exit code from the process available via result.exitCode. If
   117  *              stdout is not defined, then the output from stdout is available
   118  *              via result.stdout. stderr data is in result.stderr
   119  *
   120  * mergeStderr: optional boolean value. If true, stderr is merged with stdout;
   121  *              no data will be provided to stderr.
   122  *
   123  *
   124  * Description of object returned by subprocess.call(...)
   125  * ------------------------------------------------------
   126  * The object returned by subprocess.call offers a few methods that can be
   127  * executed:
   128  *
   129  * wait():         waits for the subprocess to terminate. It is not required to use
   130  *                 wait; done will be called in any case when the subprocess terminated.
   131  *
   132  * kill(hardKill): kill the subprocess. Any open pipes will be closed and
   133  *                 done will be called.
   134  *                 hardKill [ignored on Windows]:
   135  *                  - false: signal the process terminate (SIGTERM)
   136  *                  - true:  kill the process (SIGKILL)
   137  *
   138  *
   139  * Other methods in subprocess
   140  * ---------------------------
   141  *
   142  * registerDebugHandler(functionRef):   register a handler that is called to get
   143  *                                      debugging information
   144  * registerLogHandler(functionRef):     register a handler that is called to get error
   145  *                                      messages
   146  *
   147  * example:
   148  *    subprocess.registerLogHandler( function(s) { dump(s); } );
   149  */
   151 'use strict';
   153 const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
   155 Cu.import("resource://gre/modules/ctypes.jsm");
   157 const NS_LOCAL_FILE = "@mozilla.org/file/local;1";
   159 const Runtime = require("sdk/system/runtime");
   160 const Environment = require("sdk/system/environment").env;
   161 const DEFAULT_ENVIRONMENT = [];
   162 if (Runtime.OS == "Linux" && "DISPLAY" in Environment) {
   163   DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY);
   164 }
   166 /*
   167 Fake require statements to ensure worker scripts are packaged:
   168 require("./subprocess_worker_win.js");
   169 require("./subprocess_worker_unix.js");
   170 */
   171 const URL_PREFIX = module.uri.replace(/subprocess\.js/, "");
   172 const WORKER_URL_WIN = URL_PREFIX + "subprocess_worker_win.js";
   173 const WORKER_URL_UNIX = URL_PREFIX + "subprocess_worker_unix.js";
   175 //Windows API definitions
   176 if (ctypes.size_t.size == 8) {
   177     var WinABI = ctypes.default_abi;
   178 } else {
   179     var WinABI = ctypes.winapi_abi;
   180 }
   181 const WORD = ctypes.uint16_t;
   182 const DWORD = ctypes.uint32_t;
   183 const LPDWORD = DWORD.ptr;
   185 const UINT = ctypes.unsigned_int;
   186 const BOOL = ctypes.bool;
   187 const HANDLE = ctypes.size_t;
   188 const HWND = HANDLE;
   189 const HMODULE = HANDLE;
   190 const WPARAM = ctypes.size_t;
   191 const LPARAM = ctypes.size_t;
   192 const LRESULT = ctypes.size_t;
   193 const ULONG_PTR = ctypes.uintptr_t;
   194 const PVOID = ctypes.voidptr_t;
   195 const LPVOID = PVOID;
   196 const LPCTSTR = ctypes.jschar.ptr;
   197 const LPCWSTR = ctypes.jschar.ptr;
   198 const LPTSTR = ctypes.jschar.ptr;
   199 const LPSTR = ctypes.char.ptr;
   200 const LPCSTR = ctypes.char.ptr;
   201 const LPBYTE = ctypes.char.ptr;
   203 const CREATE_NEW_CONSOLE = 0x00000010;
   204 const CREATE_NO_WINDOW = 0x08000000;
   205 const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
   206 const STARTF_USESHOWWINDOW = 0x00000001;
   207 const STARTF_USESTDHANDLES = 0x00000100;
   208 const SW_HIDE = 0;
   209 const DUPLICATE_SAME_ACCESS = 0x00000002;
   210 const STILL_ACTIVE = 259;
   211 const INFINITE = DWORD(0xFFFFFFFF);
   212 const WAIT_TIMEOUT = 0x00000102;
   214 /*
   215 typedef struct _SECURITY_ATTRIBUTES {
   216  DWORD  nLength;
   217  LPVOID lpSecurityDescriptor;
   218  BOOL   bInheritHandle;
   219 } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
   220 */
   221 const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
   222     {"nLength": DWORD},
   223     {"lpSecurityDescriptor": LPVOID},
   224     {"bInheritHandle": BOOL},
   225 ]);
   227 /*
   228 typedef struct _STARTUPINFO {
   229   DWORD  cb;
   230   LPTSTR lpReserved;
   231   LPTSTR lpDesktop;
   232   LPTSTR lpTitle;
   233   DWORD  dwX;
   234   DWORD  dwY;
   235   DWORD  dwXSize;
   236   DWORD  dwYSize;
   237   DWORD  dwXCountChars;
   238   DWORD  dwYCountChars;
   239   DWORD  dwFillAttribute;
   240   DWORD  dwFlags;
   241   WORD   wShowWindow;
   242   WORD   cbReserved2;
   243   LPBYTE lpReserved2;
   244   HANDLE hStdInput;
   245   HANDLE hStdOutput;
   246   HANDLE hStdError;
   247 } STARTUPINFO, *LPSTARTUPINFO;
   248 */
   249 const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
   250     {"cb": DWORD},
   251     {"lpReserved": LPTSTR},
   252     {"lpDesktop": LPTSTR},
   253     {"lpTitle": LPTSTR},
   254     {"dwX": DWORD},
   255     {"dwY": DWORD},
   256     {"dwXSize": DWORD},
   257     {"dwYSize": DWORD},
   258     {"dwXCountChars": DWORD},
   259     {"dwYCountChars": DWORD},
   260     {"dwFillAttribute": DWORD},
   261     {"dwFlags": DWORD},
   262     {"wShowWindow": WORD},
   263     {"cbReserved2": WORD},
   264     {"lpReserved2": LPBYTE},
   265     {"hStdInput": HANDLE},
   266     {"hStdOutput": HANDLE},
   267     {"hStdError": HANDLE},
   268 ]);
   270 /*
   271 typedef struct _PROCESS_INFORMATION {
   272   HANDLE hProcess;
   273   HANDLE hThread;
   274   DWORD  dwProcessId;
   275   DWORD  dwThreadId;
   276 } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
   277 */
   278 const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
   279     {"hProcess": HANDLE},
   280     {"hThread": HANDLE},
   281     {"dwProcessId": DWORD},
   282     {"dwThreadId": DWORD},
   283 ]);
   285 /*
   286 typedef struct _OVERLAPPED {
   287   ULONG_PTR Internal;
   288   ULONG_PTR InternalHigh;
   289   union {
   290     struct {
   291       DWORD Offset;
   292       DWORD OffsetHigh;
   293     };
   294     PVOID  Pointer;
   295   };
   296   HANDLE    hEvent;
   297 } OVERLAPPED, *LPOVERLAPPED;
   298 */
   299 const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
   301 //UNIX definitions
   302 const pid_t = ctypes.int32_t;
   303 const WNOHANG = 1;
   304 const F_GETFD = 1;
   305 const F_SETFL = 4;
   307 const LIBNAME       = 0;
   308 const O_NONBLOCK    = 1;
   309 const RLIM_T        = 2;
   310 const RLIMIT_NOFILE = 3;
   312 function getPlatformValue(valueType) {
   314     if (! gXulRuntime)
   315         gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
   317     const platformDefaults = {
   318         // Windows API:
   319         'winnt':   [ 'kernel32.dll' ],
   321         // Unix API:
   322         //            library name   O_NONBLOCK RLIM_T                RLIMIT_NOFILE
   323         'darwin':  [ 'libc.dylib',   0x04     , ctypes.uint64_t     , 8 ],
   324         'linux':   [ 'libc.so.6',    2024     , ctypes.unsigned_long, 7 ],
   325         'freebsd': [ 'libc.so.7',    0x04     , ctypes.int64_t      , 8 ],
   326         'openbsd': [ 'libc.so.61.0', 0x04     , ctypes.int64_t      , 8 ],
   327         'sunos':   [ 'libc.so',      0x80     , ctypes.unsigned_long, 5 ]
   328     }
   330     return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
   331 }
   334 var gDebugFunc = null,
   335     gLogFunc = null,
   336     gXulRuntime = null;
   338 function LogError(s) {
   339     if (gLogFunc)
   340         gLogFunc(s);
   341     else
   342         dump(s);
   343 }
   345 function debugLog(s) {
   346     if (gDebugFunc)
   347         gDebugFunc(s);
   348 }
   350 function setTimeout(callback, timeout) {
   351     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   352     timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
   353 };
   355 function getBytes(data) {
   356   var string = '';
   357   data.forEach(function(x) { string += String.fromCharCode(x < 0 ? x + 256 : x) });
   358   return string;
   359 }
   361 function readString(data, length, charset) {
   362     var string = '', bytes = [];
   363     for(var i = 0;i < length; i++) {
   364         if(data[i] == 0 && charset !== null) // stop on NULL character for non-binary data
   365            break
   366         bytes.push(data[i]);
   367     }
   368     if (!bytes || bytes.length == 0)
   369         return string;
   370     if(charset === null) {
   371         return bytes;
   372     }
   373     return convertBytes(bytes, charset);
   374 }
   376 function convertBytes(bytes, charset) {
   377     var string = '';
   378     charset = charset || 'UTF-8';
   379     var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   380                         .getService(Ci.nsIScriptableUnicodeConverter);
   381     try {
   382         unicodeConv.charset = charset;
   383         string = unicodeConv.convertFromByteArray(bytes, bytes.length);
   384     } catch (ex) {
   385         LogError("String conversion failed: "+ex.toString()+"\n")
   386         string = '';
   387     }
   388     string += unicodeConv.Finish();
   389     return string;
   390 }
   393 // temporary solution for removal of nsILocalFile
   394 function getLocalFileApi() {
   395   if ("nsILocalFile" in Ci) {
   396     return Ci.nsILocalFile;
   397   }
   398   else
   399     return Ci.nsIFile;
   400 }
   402 function getCommandStr(command) {
   403     let commandStr = null;
   404     if (typeof(command) == "string") {
   405         let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
   406         file.initWithPath(command);
   407         if (! (file.isExecutable() && file.isFile()))
   408             throw("File '"+command+"' is not an executable file");
   409         commandStr = command;
   410     }
   411     else {
   412         if (! (command.isExecutable() && command.isFile()))
   413             throw("File '"+command.path+"' is not an executable file");
   414         commandStr = command.path;
   415     }
   417     return commandStr;
   418 }
   420 function getWorkDir(workdir) {
   421     let workdirStr = null;
   422     if (typeof(workdir) == "string") {
   423         let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi());
   424         file.initWithPath(workdir);
   425         if (! (file.isDirectory()))
   426             throw("Directory '"+workdir+"' does not exist");
   427         workdirStr = workdir;
   428     }
   429     else if (workdir) {
   430         if (! workdir.isDirectory())
   431             throw("Directory '"+workdir.path+"' does not exist");
   432         workdirStr = workdir.path;
   433     }
   434     return workdirStr;
   435 }
   438 var subprocess = {
   439     call: function(options) {
   440         options.mergeStderr = options.mergeStderr || false;
   441         options.workdir = options.workdir ||  null;
   442         options.environment = options.environment || DEFAULT_ENVIRONMENT;
   443         if (options.arguments) {
   444             var args = options.arguments;
   445             options.arguments = [];
   446             args.forEach(function(argument) {
   447                 options.arguments.push(argument);
   448             });
   449         } else {
   450             options.arguments = [];
   451         }
   453         options.libc = getPlatformValue(LIBNAME);
   455         if (gXulRuntime.OS.substring(0, 3) == "WIN") {
   456             return subprocess_win32(options);
   457         } else {
   458             return subprocess_unix(options);
   459         }
   461     },
   462     registerDebugHandler: function(func) {
   463         gDebugFunc = func;
   464     },
   465     registerLogHandler: function(func) {
   466         gLogFunc = func;
   467     }
   468 };
   472 function subprocess_win32(options) {
   473     var kernel32dll = ctypes.open(options.libc),
   474         hChildProcess,
   475         active = true,
   476         done = false,
   477         exitCode = -1,
   478         child = {},
   479         stdinWorker = null,
   480         stdoutWorker = null,
   481         stderrWorker = null,
   482         pendingWriteCount = 0,
   483         readers = options.mergeStderr ? 1 : 2,
   484         stdinOpenState = 2,
   485         error = '',
   486         output = '';
   488     // stdin pipe states
   489     const OPEN = 2;
   490     const CLOSEABLE = 1;
   491     const CLOSED = 0;
   493     //api declarations
   494     /*
   495     BOOL WINAPI CloseHandle(
   496       __in  HANDLE hObject
   497     );
   498     */
   499     var CloseHandle = kernel32dll.declare("CloseHandle",
   500                                             WinABI,
   501                                             BOOL,
   502                                             HANDLE
   503     );
   505     /*
   506     BOOL WINAPI CreateProcess(
   507       __in_opt     LPCTSTR lpApplicationName,
   508       __inout_opt  LPTSTR lpCommandLine,
   509       __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   510       __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   511       __in         BOOL bInheritHandles,
   512       __in         DWORD dwCreationFlags,
   513       __in_opt     LPVOID lpEnvironment,
   514       __in_opt     LPCTSTR lpCurrentDirectory,
   515       __in         LPSTARTUPINFO lpStartupInfo,
   516       __out        LPPROCESS_INFORMATION lpProcessInformation
   517     );
   518      */
   519     var CreateProcessW = kernel32dll.declare("CreateProcessW",
   520                                             WinABI,
   521                                             BOOL,
   522                                             LPCTSTR,
   523                                             LPTSTR,
   524                                             SECURITY_ATTRIBUTES.ptr,
   525                                             SECURITY_ATTRIBUTES.ptr,
   526                                             BOOL,
   527                                             DWORD,
   528                                             LPVOID,
   529                                             LPCTSTR,
   530                                             STARTUPINFO.ptr,
   531                                             PROCESS_INFORMATION.ptr
   532                                          );
   534 //     /*
   535 //     BOOL WINAPI ReadFile(
   536 //       __in         HANDLE hFile,
   537 //       __out        LPVOID ReadFileBuffer,
   538 //       __in         DWORD nNumberOfBytesToRead,
   539 //       __out_opt    LPDWORD lpNumberOfBytesRead,
   540 //       __inout_opt  LPOVERLAPPED lpOverlapped
   541 //     );
   542 //     */
   543 //     var ReadFileBufferSize = 1024,
   544 //         ReadFileBuffer = ctypes.char.array(ReadFileBufferSize),
   545 //         ReadFile = kernel32dll.declare("ReadFile",
   546 //                                         WinABI,
   547 //                                         BOOL,
   548 //                                         HANDLE,
   549 //                                         ReadFileBuffer,
   550 //                                         DWORD,
   551 //                                         LPDWORD,
   552 //                                         OVERLAPPED.ptr
   553 //     );
   554 //
   555 //     /*
   556 //     BOOL WINAPI PeekNamedPipe(
   557 //       __in       HANDLE hNamedPipe,
   558 //       __out_opt  LPVOID lpBuffer,
   559 //       __in       DWORD nBufferSize,
   560 //       __out_opt  LPDWORD lpBytesRead,
   561 //       __out_opt  LPDWORD lpTotalBytesAvail,
   562 //       __out_opt  LPDWORD lpBytesLeftThisMessage
   563 //     );
   564 //     */
   565 //     var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe",
   566 //                                         WinABI,
   567 //                                         BOOL,
   568 //                                         HANDLE,
   569 //                                         ReadFileBuffer,
   570 //                                         DWORD,
   571 //                                         LPDWORD,
   572 //                                         LPDWORD,
   573 //                                         LPDWORD
   574 //     );
   575 //
   576 //     /*
   577 //     BOOL WINAPI WriteFile(
   578 //       __in         HANDLE hFile,
   579 //       __in         LPCVOID lpBuffer,
   580 //       __in         DWORD nNumberOfBytesToWrite,
   581 //       __out_opt    LPDWORD lpNumberOfBytesWritten,
   582 //       __inout_opt  LPOVERLAPPED lpOverlapped
   583 //     );
   584 //     */
   585 //     var WriteFile = kernel32dll.declare("WriteFile",
   586 //                                         WinABI,
   587 //                                         BOOL,
   588 //                                         HANDLE,
   589 //                                         ctypes.char.ptr,
   590 //                                         DWORD,
   591 //                                         LPDWORD,
   592 //                                         OVERLAPPED.ptr
   593 //     );
   595     /*
   596     BOOL WINAPI CreatePipe(
   597       __out     PHANDLE hReadPipe,
   598       __out     PHANDLE hWritePipe,
   599       __in_opt  LPSECURITY_ATTRIBUTES lpPipeAttributes,
   600       __in      DWORD nSize
   601     );
   602     */
   603     var CreatePipe = kernel32dll.declare("CreatePipe",
   604                                         WinABI,
   605                                         BOOL,
   606                                         HANDLE.ptr,
   607                                         HANDLE.ptr,
   608                                         SECURITY_ATTRIBUTES.ptr,
   609                                         DWORD
   610     );
   612     /*
   613     HANDLE WINAPI GetCurrentProcess(void);
   614     */
   615     var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess",
   616                                         WinABI,
   617                                         HANDLE
   618     );
   620     /*
   621     DWORD WINAPI GetLastError(void);
   622     */
   623     var GetLastError = kernel32dll.declare("GetLastError",
   624                                         WinABI,
   625                                         DWORD
   626     );
   628     /*
   629     BOOL WINAPI DuplicateHandle(
   630       __in   HANDLE hSourceProcessHandle,
   631       __in   HANDLE hSourceHandle,
   632       __in   HANDLE hTargetProcessHandle,
   633       __out  LPHANDLE lpTargetHandle,
   634       __in   DWORD dwDesiredAccess,
   635       __in   BOOL bInheritHandle,
   636       __in   DWORD dwOptions
   637     );
   638     */
   639     var DuplicateHandle = kernel32dll.declare("DuplicateHandle",
   640                                         WinABI,
   641                                         BOOL,
   642                                         HANDLE,
   643                                         HANDLE,
   644                                         HANDLE,
   645                                         HANDLE.ptr,
   646                                         DWORD,
   647                                         BOOL,
   648                                         DWORD
   649     );
   652     /*
   653     BOOL WINAPI GetExitCodeProcess(
   654       __in   HANDLE hProcess,
   655       __out  LPDWORD lpExitCode
   656     );
   657     */
   658     var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess",
   659                                         WinABI,
   660                                         BOOL,
   661                                         HANDLE,
   662                                         LPDWORD
   663     );
   665     /*
   666     DWORD WINAPI WaitForSingleObject(
   667       __in  HANDLE hHandle,
   668       __in  DWORD dwMilliseconds
   669     );
   670     */
   671     var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject",
   672                                         WinABI,
   673                                         DWORD,
   674                                         HANDLE,
   675                                         DWORD
   676     );
   678     /*
   679     BOOL WINAPI TerminateProcess(
   680       __in  HANDLE hProcess,
   681       __in  UINT uExitCode
   682     );
   683     */
   684     var TerminateProcess = kernel32dll.declare("TerminateProcess",
   685                                         WinABI,
   686                                         BOOL,
   687                                         HANDLE,
   688                                         UINT
   689     );
   691     //functions
   692     function popen(command, workdir, args, environment, child) {
   693         //escape arguments
   694         args.unshift(command);
   695         for (var i = 0; i < args.length; i++) {
   696           if (typeof args[i] != "string") { args[i] = args[i].toString(); }
   697           /* quote arguments with spaces */
   698           if (args[i].match(/\s/)) {
   699             args[i] = "\"" + args[i] + "\"";
   700           }
   701           /* If backslash is followed by a quote, double it */
   702           args[i] = args[i].replace(/\\\"/g, "\\\\\"");
   703         }
   704         command = args.join(' ');
   706         environment = environment || [];
   707         if(environment.length) {
   708             //An environment block consists of
   709             //a null-terminated block of null-terminated strings.
   710             //Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar
   711             environment = ctypes.jschar.array()(environment.join('\0') + '\0');
   712         } else {
   713             environment = null;
   714         }
   716         var hOutputReadTmp = new HANDLE(),
   717             hOutputRead = new HANDLE(),
   718             hOutputWrite = new HANDLE();
   720         var hErrorRead = new HANDLE(),
   721             hErrorReadTmp = new HANDLE(),
   722             hErrorWrite = new HANDLE();
   724         var hInputRead = new HANDLE(),
   725             hInputWriteTmp = new HANDLE(),
   726             hInputWrite = new HANDLE();
   728         // Set up the security attributes struct.
   729         var sa = new SECURITY_ATTRIBUTES();
   730         sa.nLength = SECURITY_ATTRIBUTES.size;
   731         sa.lpSecurityDescriptor = null;
   732         sa.bInheritHandle = true;
   734         // Create output pipe.
   736         if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0))
   737             LogError('CreatePipe hOutputReadTmp failed');
   739         if(options.mergeStderr) {
   740           // Create a duplicate of the output write handle for the std error
   741           // write handle. This is necessary in case the child application
   742           // closes one of its std output handles.
   743           if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
   744                                GetCurrentProcess(), hErrorWrite.address(), 0,
   745                                true, DUPLICATE_SAME_ACCESS))
   746              LogError("DuplicateHandle hOutputWrite failed");
   747         } else {
   748             // Create error pipe.
   749             if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
   750                 LogError('CreatePipe hErrorReadTmp failed');
   751         }
   753         // Create input pipe.
   754         if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
   755             LogError("CreatePipe hInputRead failed");
   757         // Create new output/error read handle and the input write handles. Set
   758         // the Properties to FALSE. Otherwise, the child inherits the
   759         // properties and, as a result, non-closeable handles to the pipes
   760         // are created.
   761         if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
   762                              GetCurrentProcess(),
   763                              hOutputRead.address(), // Address of new handle.
   764                              0, false, // Make it uninheritable.
   765                              DUPLICATE_SAME_ACCESS))
   766              LogError("DupliateHandle hOutputReadTmp failed");
   768         if(!options.mergeStderr) {
   769             if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
   770                              GetCurrentProcess(),
   771                              hErrorRead.address(), // Address of new handle.
   772                              0, false, // Make it uninheritable.
   773                              DUPLICATE_SAME_ACCESS))
   774              LogError("DupliateHandle hErrorReadTmp failed");
   775         }
   776         if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
   777                              GetCurrentProcess(),
   778                              hInputWrite.address(), // Address of new handle.
   779                              0, false, // Make it uninheritable.
   780                              DUPLICATE_SAME_ACCESS))
   781           LogError("DupliateHandle hInputWriteTmp failed");
   783         // Close inheritable copies of the handles.
   784         if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
   785         if(!options.mergeStderr)
   786             if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
   787         if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
   789         var pi = new PROCESS_INFORMATION();
   790         var si = new STARTUPINFO();
   792         si.cb = STARTUPINFO.size;
   793         si.dwFlags = STARTF_USESTDHANDLES;
   794         si.hStdInput  = hInputRead;
   795         si.hStdOutput = hOutputWrite;
   796         si.hStdError  = hErrorWrite;
   798         // Launch the process
   799         if(!CreateProcessW(null,            // executable name
   800                            command,         // command buffer
   801                            null,            // process security attribute
   802                            null,            // thread security attribute
   803                            true,            // inherits system handles
   804                            CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
   805                            environment,     // envrionment block
   806                            workdir,          // set as current directory
   807                            si.address(),    // (in) startup information
   808                            pi.address()     // (out) process information
   809         ))
   810             throw("Fatal - Could not launch subprocess '"+command+"'");
   812         // Close any unnecessary handles.
   813         if (!CloseHandle(pi.hThread))
   814             LogError("CloseHandle pi.hThread failed");
   816         // Close pipe handles (do not continue to modify the parent).
   817         // You need to make sure that no handles to the write end of the
   818         // output pipe are maintained in this process or else the pipe will
   819         // not close when the child process exits and the ReadFile will hang.
   820         if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
   821         if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
   822         if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
   824         //return values
   825         child.stdin = hInputWrite;
   826         child.stdout = hOutputRead;
   827         child.stderr = options.mergeStderr ? undefined : hErrorRead;
   828         child.process = pi.hProcess;
   829         return pi.hProcess;
   830     }
   832     /*
   833      * createStdinWriter ()
   834      *
   835      * Create a ChromeWorker object for writing data to the subprocess' stdin
   836      * pipe. The ChromeWorker object lives on a separate thread; this avoids
   837      * internal deadlocks.
   838      */
   839     function createStdinWriter() {
   840         debugLog("Creating new stdin worker\n");
   841         stdinWorker = new ChromeWorker(WORKER_URL_WIN);
   842         stdinWorker.onmessage = function(event) {
   843             switch(event.data) {
   844             case "WriteOK":
   845                 pendingWriteCount--;
   846                 debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
   847                 break;
   848             case "ClosedOK":
   849                 stdinOpenState = CLOSED;
   850                 debugLog("Stdin pipe closed\n");
   851                 break;
   852             default:
   853                 debugLog("got msg from stdinWorker: "+event.data+"\n");
   854             }
   855         }
   856         stdinWorker.onerror = function(error) {
   857             pendingWriteCount--;
   858             LogError("got error from stdinWorker: "+error.message+"\n");
   859         }
   861         stdinWorker.postMessage({msg: "init", libc: options.libc});
   862     }
   864     /*
   865      * writeStdin()
   866      * @data: String containing the data to write
   867      *
   868      * Write data to the subprocess' stdin (equals to sending a request to the
   869      * ChromeWorker object to write the data).
   870      */
   871     function writeStdin(data) {
   872         ++pendingWriteCount;
   873         debugLog("sending "+data.length+" bytes to stdinWorker\n");
   874         var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
   876         stdinWorker.postMessage({
   877                 msg: 'write',
   878                 pipe: pipePtr,
   879                 data: data
   880             });
   881     }
   883     /*
   884      * closeStdinHandle()
   885      *
   886      * Close the stdin pipe, either directly or by requesting the ChromeWorker to
   887      * close the pipe. The ChromeWorker will only close the pipe after the last write
   888      * request process is done.
   889      */
   891     function closeStdinHandle() {
   892         debugLog("trying to close stdin\n");
   893         if (stdinOpenState != OPEN) return;
   894         stdinOpenState = CLOSEABLE;
   896         if (stdinWorker) {
   897             debugLog("sending close stdin to worker\n");
   898             var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
   899             stdinWorker.postMessage({
   900                 msg: 'close',
   901                 pipe: pipePtr
   902             });
   903         }
   904         else {
   905             stdinOpenState = CLOSED;
   906             debugLog("Closing Stdin\n");
   907             CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed");
   908         }
   909     }
   912     /*
   913      * createReader(pipe, name)
   914      *
   915      * @pipe: handle to the pipe
   916      * @name: String containing the pipe name (stdout or stderr)
   917      *
   918      * Create a ChromeWorker object for reading data asynchronously from
   919      * the pipe (i.e. on a separate thread), and passing the result back to
   920      * the caller.
   921      */
   922     function createReader(pipe, name, callbackFunc) {
   923         var worker = new ChromeWorker(WORKER_URL_WIN);
   924         worker.onmessage = function(event) {
   925             switch(event.data.msg) {
   926             case "data":
   927                 debugLog("got "+event.data.count+" bytes from "+name+"\n");
   928                 var data = '';
   929                 if (options.charset === null) {
   930                     data = getBytes(event.data.data);
   931                 }
   932                 else {
   933                     try {
   934                         data = convertBytes(event.data.data, options.charset);
   935                     }
   936                     catch(ex) {
   937                         console.warn("error decoding output: " + ex);
   938                         data = getBytes(event.data.data);
   939                     }
   940                 }
   942                 callbackFunc(data);
   943                 break;
   944             case "done":
   945                 debugLog("Pipe "+name+" closed\n");
   946                 --readers;
   947                 if (readers == 0) cleanup();
   948                 break;
   949             default:
   950                 debugLog("Got msg from "+name+": "+event.data.data+"\n");
   951             }
   952         }
   954         worker.onerror = function(errorMsg) {
   955             LogError("Got error from "+name+": "+errorMsg.message);
   956         }
   958         var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
   960         worker.postMessage({
   961                 msg: 'read',
   962                 pipe: pipePtr,
   963                 libc: options.libc,
   964                 charset: options.charset === null ? "null" : options.charset,
   965                 name: name
   966             });
   968         return worker;
   969     }
   971     /*
   972      * readPipes()
   973      *
   974      * Open the pipes for reading from stdout and stderr
   975      */
   976     function readPipes() {
   977         stdoutWorker = createReader(child.stdout, "stdout", function (data) {
   978             if(options.stdout) {
   979                 setTimeout(function() {
   980                     options.stdout(data);
   981                 }, 0);
   982             } else {
   983                 output += data;
   984             }
   985         });
   988         if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
   989             if(options.stderr) {
   990                 setTimeout(function() {
   991                     options.stderr(data);
   992                 }, 0);
   993             } else {
   994                 error += data;
   995             }
   996         });
   997     }
   999     /*
  1000      * cleanup()
  1002      * close stdin if needed, get the exit code from the subprocess and invoke
  1003      * the caller's done() function.
  1005      * Note: because stdout() and stderr() are called using setTimeout, we need to
  1006      * do the same here in order to guarantee the message sequence.
  1007      */
  1008     function cleanup() {
  1009         debugLog("Cleanup called\n");
  1010         if(active) {
  1011             active = false;
  1013             closeStdinHandle(); // should only be required in case of errors
  1015             var exit = new DWORD();
  1016             GetExitCodeProcess(child.process, exit.address());
  1017             exitCode = exit.value;
  1019             if (stdinWorker)
  1020                 stdinWorker.postMessage({msg: 'stop'})
  1022             setTimeout(function _done() {
  1023                 if (options.done) {
  1024                     try {
  1025                         options.done({
  1026                             exitCode: exitCode,
  1027                             stdout: output,
  1028                             stderr: error,
  1029                         });
  1031                     catch (ex) {
  1032                         // prevent from blocking if options.done() throws an error
  1033                         done = true;
  1034                         throw ex;
  1037                 done = true;
  1038             }, 0);
  1039             kernel32dll.close();
  1043     var cmdStr = getCommandStr(options.command);
  1044     var workDir = getWorkDir(options.workdir);
  1046     //main
  1047     hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
  1049     readPipes();
  1051     if (options.stdin) {
  1052        createStdinWriter();
  1054         if(typeof(options.stdin) == 'function') {
  1055             try {
  1056                 options.stdin({
  1057                     write: function(data) {
  1058                         writeStdin(data);
  1059                     },
  1060                     close: function() {
  1061                         closeStdinHandle();
  1063                 });
  1065             catch (ex) {
  1066                 // prevent from failing if options.stdin() throws an exception
  1067                 closeStdinHandle();
  1068                 throw ex;
  1070         } else {
  1071             writeStdin(options.stdin);
  1072             closeStdinHandle();
  1075     else
  1076         closeStdinHandle();
  1078     return {
  1079         kill: function(hardKill) {
  1080             // hardKill is currently ignored on Windows
  1081             var r = !!TerminateProcess(child.process, 255);
  1082             cleanup(-1);
  1083             return r;
  1084         },
  1085         wait: function() {
  1086             // wait for async operations to complete
  1087             var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
  1088             while (!done) thread.processNextEvent(true);
  1090             return exitCode;
  1096 function subprocess_unix(options) {
  1097     // stdin pipe states
  1098     const OPEN = 2;
  1099     const CLOSEABLE = 1;
  1100     const CLOSED = 0;
  1102     var libc = ctypes.open(options.libc),
  1103         active = true,
  1104         done = false,
  1105         exitCode = -1,
  1106         workerExitCode = 0,
  1107         child = {},
  1108         pid = -1,
  1109         stdinWorker = null,
  1110         stdoutWorker = null,
  1111         stderrWorker = null,
  1112         pendingWriteCount = 0,
  1113         readers = options.mergeStderr ? 1 : 2,
  1114         stdinOpenState = OPEN,
  1115         error = '',
  1116         output = '';
  1118     //api declarations
  1120     //pid_t fork(void);
  1121     var fork = libc.declare("fork",
  1122                          ctypes.default_abi,
  1123                          pid_t
  1124     );
  1126     //NULL terminated array of strings, argv[0] will be command >> + 2
  1127     var argv = ctypes.char.ptr.array(options.arguments.length + 2);
  1128     var envp = ctypes.char.ptr.array(options.environment.length + 1);
  1130     // posix_spawn_file_actions_t is a complex struct that may be different on
  1131     // each platform. We do not care about its attributes, we don't need to
  1132     // get access to them, but we do need to allocate the right amount
  1133     // of memory for it.
  1134     // At 2013/10/28, its size was 80 on linux, but better be safe (and larger),
  1135     // than crash when posix_spawn_file_actions_init fill `action` with zeros.
  1136     // Use `gcc sizeof_fileaction.c && ./a.out` to check that size.
  1137     var posix_spawn_file_actions_t = ctypes.uint8_t.array(100);
  1139     //int posix_spawn(pid_t *restrict pid, const char *restrict path,
  1140     //   const posix_spawn_file_actions_t *file_actions,
  1141     //   const posix_spawnattr_t *restrict attrp,
  1142     //   char *const argv[restrict], char *const envp[restrict]);
  1143     var posix_spawn = libc.declare("posix_spawn",
  1144                          ctypes.default_abi,
  1145                          ctypes.int,
  1146                          pid_t.ptr,
  1147                          ctypes.char.ptr,
  1148                          posix_spawn_file_actions_t.ptr,
  1149                          ctypes.voidptr_t,
  1150                          argv,
  1151                          envp
  1152     );
  1154     //int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
  1155     var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init",
  1156                          ctypes.default_abi,
  1157                          ctypes.int,
  1158                          posix_spawn_file_actions_t.ptr
  1159     );
  1161     //int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
  1162     var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy",
  1163                          ctypes.default_abi,
  1164                          ctypes.int,
  1165                          posix_spawn_file_actions_t.ptr
  1166     );
  1168     // int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *
  1169     //                                      file_actions, int fildes, int newfildes);
  1170     var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2",
  1171                          ctypes.default_abi,
  1172                          ctypes.int,
  1173                          posix_spawn_file_actions_t.ptr,
  1174                          ctypes.int,
  1175                          ctypes.int
  1176     );
  1178     // int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *
  1179     //                                       file_actions, int fildes);
  1180     var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose",
  1181                          ctypes.default_abi,
  1182                          ctypes.int,
  1183                          posix_spawn_file_actions_t.ptr,
  1184                          ctypes.int
  1185     );
  1187     //int pipe(int pipefd[2]);
  1188     var pipefd = ctypes.int.array(2);
  1189     var pipe = libc.declare("pipe",
  1190                          ctypes.default_abi,
  1191                          ctypes.int,
  1192                          pipefd
  1193     );
  1195     //int close(int fd);
  1196     var close = libc.declare("close",
  1197                           ctypes.default_abi,
  1198                           ctypes.int,
  1199                           ctypes.int
  1200     );
  1202     //pid_t waitpid(pid_t pid, int *status, int options);
  1203     var waitpid = libc.declare("waitpid",
  1204                           ctypes.default_abi,
  1205                           pid_t,
  1206                           pid_t,
  1207                           ctypes.int.ptr,
  1208                           ctypes.int
  1209     );
  1211     //int kill(pid_t pid, int sig);
  1212     var kill = libc.declare("kill",
  1213                           ctypes.default_abi,
  1214                           ctypes.int,
  1215                           pid_t,
  1216                           ctypes.int
  1217     );
  1219     //int read(int fd, void *buf, size_t count);
  1220     var bufferSize = 1024;
  1221     var buffer = ctypes.char.array(bufferSize);
  1222     var read = libc.declare("read",
  1223                           ctypes.default_abi,
  1224                           ctypes.int,
  1225                           ctypes.int,
  1226                           buffer,
  1227                           ctypes.int
  1228     );
  1230     //ssize_t write(int fd, const void *buf, size_t count);
  1231     var write = libc.declare("write",
  1232                           ctypes.default_abi,
  1233                           ctypes.int,
  1234                           ctypes.int,
  1235                           ctypes.char.ptr,
  1236                           ctypes.int
  1237     );
  1239     //int chdir(const char *path);
  1240     var chdir = libc.declare("chdir",
  1241                           ctypes.default_abi,
  1242                           ctypes.int,
  1243                           ctypes.char.ptr
  1244     );
  1246     //int fcntl(int fd, int cmd, ... /* arg */ );
  1247     var fcntl = libc.declare("fcntl",
  1248                           ctypes.default_abi,
  1249                           ctypes.int,
  1250                           ctypes.int,
  1251                           ctypes.int,
  1252                           ctypes.int
  1253     );
  1255     function popen(command, workdir, args, environment, child) {
  1256         var _in,
  1257             _out,
  1258             _err,
  1259             pid,
  1260             rc;
  1261         _in = new pipefd();
  1262         _out = new pipefd();
  1263         if(!options.mergeStderr)
  1264             _err = new pipefd();
  1266         var _args = argv();
  1267         args.unshift(command);
  1268         for(var i=0;i<args.length;i++) {
  1269             _args[i] = ctypes.char.array()(args[i]);
  1271         var _envp = envp();
  1272         for(var i=0;i<environment.length;i++) {
  1273             _envp[i] = ctypes.char.array()(environment[i]);
  1274             // LogError(_envp);
  1277         rc = pipe(_in);
  1278         if (rc < 0) {
  1279             return -1;
  1281         rc = pipe(_out);
  1282         fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK));
  1283         if (rc < 0) {
  1284             close(_in[0]);
  1285             close(_in[1]);
  1286             return -1
  1288         if(!options.mergeStderr) {
  1289             rc = pipe(_err);
  1290             fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK));
  1291             if (rc < 0) {
  1292                 close(_in[0]);
  1293                 close(_in[1]);
  1294                 close(_out[0]);
  1295                 close(_out[1]);
  1296                 return -1
  1300         let STDIN_FILENO = 0;
  1301         let STDOUT_FILENO = 1;
  1302         let STDERR_FILENO = 2;
  1304         let action = posix_spawn_file_actions_t();
  1305         posix_spawn_file_actions_init(action.address());
  1307         posix_spawn_file_actions_adddup2(action.address(), _in[0], STDIN_FILENO);
  1308         posix_spawn_file_actions_addclose(action.address(), _in[1]);
  1309         posix_spawn_file_actions_addclose(action.address(), _in[0]);
  1311         posix_spawn_file_actions_adddup2(action.address(), _out[1], STDOUT_FILENO);
  1312         posix_spawn_file_actions_addclose(action.address(), _out[1]);
  1313         posix_spawn_file_actions_addclose(action.address(), _out[0]);
  1315         if (!options.mergeStderr) {
  1316           posix_spawn_file_actions_adddup2(action.address(), _err[1], STDERR_FILENO);
  1317           posix_spawn_file_actions_addclose(action.address(), _err[1]);
  1318           posix_spawn_file_actions_addclose(action.address(), _err[0]);
  1321         // posix_spawn doesn't support setting a custom workdir for the child,
  1322         // so change the cwd in the parent process before launching the child process.
  1323         if (workdir) {
  1324           if (chdir(workdir) < 0) {
  1325             throw new Error("Unable to change workdir before launching child process");
  1329         closeOtherFds(action, _in[1], _out[0], options.mergeStderr ? undefined : _err[0]);
  1331         let id = pid_t(0);
  1332         let rv = posix_spawn(id.address(), command, action.address(), null, _args, _envp);
  1333         posix_spawn_file_actions_destroy(action.address());
  1334         if (rv != 0) {
  1335           // we should not really end up here
  1336           if(!options.mergeStderr) {
  1337             close(_err[0]);
  1338             close(_err[1]);
  1340           close(_out[0]);
  1341           close(_out[1]);
  1342           close(_in[0]);
  1343           close(_in[1]);
  1344           throw new Error("Fatal - failed to create subprocess '"+command+"'");
  1346         pid = id.value;
  1348         close(_in[0]);
  1349         close(_out[1]);
  1350         if (!options.mergeStderr)
  1351           close(_err[1]);
  1352         child.stdin  = _in[1];
  1353         child.stdout = _out[0];
  1354         child.stderr = options.mergeStderr ? undefined : _err[0];
  1355         child.pid = pid;
  1357         return pid;
  1361     // close any file descriptors that are not required for the process
  1362     function closeOtherFds(action, fdIn, fdOut, fdErr) {
  1363         // Unfortunately on mac, any fd registered in posix_spawn_file_actions_addclose
  1364         // that can't be closed correctly will make posix_spawn fail...
  1365         // Even if we ensure registering only still opened fds.
  1366         if (gXulRuntime.OS == "Darwin")
  1367             return;
  1369         var maxFD = 256; // arbitrary max
  1372         var rlim_t = getPlatformValue(RLIM_T);
  1374         const RLIMITS = new ctypes.StructType("RLIMITS", [
  1375             {"rlim_cur": rlim_t},
  1376             {"rlim_max": rlim_t}
  1377         ]);
  1379         try {
  1380             var getrlimit = libc.declare("getrlimit",
  1381                                   ctypes.default_abi,
  1382                                   ctypes.int,
  1383                                   ctypes.int,
  1384                                   RLIMITS.ptr
  1385             );
  1387             var rl = new RLIMITS();
  1388             if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) == 0) {
  1389                 maxFD = rl.rlim_cur;
  1391             debugLog("getlimit: maxFD="+maxFD+"\n");
  1394         catch(ex) {
  1395             debugLog("getrlimit: no such function on this OS\n");
  1396             debugLog(ex.toString());
  1399         // close any file descriptors
  1400         // fd's 0-2 are already closed
  1401         for (var i = 3; i < maxFD; i++) {
  1402             if (i != fdIn && i != fdOut && i != fdErr && fcntl(i, F_GETFD, -1) >= 0) {
  1403                 posix_spawn_file_actions_addclose(action.address(), i);
  1408     /*
  1409      * createStdinWriter ()
  1411      * Create a ChromeWorker object for writing data to the subprocess' stdin
  1412      * pipe. The ChromeWorker object lives on a separate thread; this avoids
  1413      * internal deadlocks.
  1414      */
  1415     function createStdinWriter() {
  1416         debugLog("Creating new stdin worker\n");
  1417         stdinWorker = new ChromeWorker(WORKER_URL_UNIX);
  1418         stdinWorker.onmessage = function(event) {
  1419             switch (event.data.msg) {
  1420             case "info":
  1421                 switch(event.data.data) {
  1422                 case "WriteOK":
  1423                     pendingWriteCount--;
  1424                     debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
  1425                     break;
  1426                 case "ClosedOK":
  1427                     stdinOpenState = CLOSED;
  1428                     debugLog("Stdin pipe closed\n");
  1429                     break;
  1430                 default:
  1431                     debugLog("got msg from stdinWorker: "+event.data.data+"\n");
  1433                 break;
  1434             case "debug":
  1435                 debugLog("stdinWorker: "+event.data.data+"\n");
  1436                 break;
  1437             case "error":
  1438                 LogError("got error from stdinWorker: "+event.data.data+"\n");
  1439                 pendingWriteCount = 0;
  1440                 stdinOpenState = CLOSED;
  1443         stdinWorker.onerror = function(error) {
  1444             pendingWriteCount = 0;
  1445             closeStdinHandle();
  1446             LogError("got error from stdinWorker: "+error.message+"\n");
  1448         stdinWorker.postMessage({msg: "init", libc: options.libc});
  1451     /*
  1452      * writeStdin()
  1453      * @data: String containing the data to write
  1455      * Write data to the subprocess' stdin (equals to sending a request to the
  1456      * ChromeWorker object to write the data).
  1457      */
  1458     function writeStdin(data) {
  1459         if (stdinOpenState == CLOSED) return; // do not write to closed pipes
  1461         ++pendingWriteCount;
  1462         debugLog("sending "+data.length+" bytes to stdinWorker\n");
  1463         var pipe = parseInt(child.stdin);
  1465         stdinWorker.postMessage({
  1466             msg: 'write',
  1467             pipe: pipe,
  1468             data: data
  1469         });
  1473     /*
  1474      * closeStdinHandle()
  1476      * Close the stdin pipe, either directly or by requesting the ChromeWorker to
  1477      * close the pipe. The ChromeWorker will only close the pipe after the last write
  1478      * request process is done.
  1479      */
  1481     function closeStdinHandle() {
  1482         debugLog("trying to close stdin\n");
  1483         if (stdinOpenState != OPEN) return;
  1484         stdinOpenState = CLOSEABLE;
  1486         if (stdinWorker) {
  1487             debugLog("sending close stdin to worker\n");
  1488             var pipePtr = parseInt(child.stdin);
  1490             stdinWorker.postMessage({
  1491                 msg: 'close',
  1492                 pipe: pipePtr
  1493             });
  1495         else {
  1496             stdinOpenState = CLOSED;
  1497             debugLog("Closing Stdin\n");
  1498             close(child.stdin) && LogError("CloseHandle stdin failed");
  1503     /*
  1504      * createReader(pipe, name)
  1506      * @pipe: handle to the pipe
  1507      * @name: String containing the pipe name (stdout or stderr)
  1508      * @callbackFunc: function to be called with the read data
  1510      * Create a ChromeWorker object for reading data asynchronously from
  1511      * the pipe (i.e. on a separate thread), and passing the result back to
  1512      * the caller.
  1514      */
  1515     function createReader(pipe, name, callbackFunc) {
  1516         var worker = new ChromeWorker(WORKER_URL_UNIX);
  1517         worker.onmessage = function(event) {
  1518             switch(event.data.msg) {
  1519             case "data":
  1520                 debugLog("got "+event.data.count+" bytes from "+name+"\n");
  1521                 var data = '';
  1522                 if (options.charset === null) {
  1523                     data = getBytes(event.data.data);
  1525                 else {
  1526                     try {
  1527                         data = convertBytes(event.data.data, options.charset);
  1529                     catch(ex) {
  1530                         console.warn("error decoding output: " + ex);
  1531                         data = getBytes(event.data.data);
  1535                 callbackFunc(data);
  1536                 break;
  1537             case "done":
  1538                 debugLog("Pipe "+name+" closed\n");
  1539                 if (event.data.data > 0) workerExitCode = event.data.data;
  1540                 --readers;
  1541                 if (readers == 0) cleanup();
  1542                 break;
  1543             default:
  1544                 debugLog("Got msg from "+name+": "+event.data.data+"\n");
  1547         worker.onerror = function(error) {
  1548             LogError("Got error from "+name+": "+error.message);
  1551         worker.postMessage({
  1552                 msg: 'read',
  1553                 pipe: pipe,
  1554                 pid: pid,
  1555                 libc: options.libc,
  1556                 charset: options.charset === null ? "null" : options.charset,
  1557                 name: name
  1558             });
  1560         return worker;
  1563     /*
  1564      * readPipes()
  1566      * Open the pipes for reading from stdout and stderr
  1567      */
  1568     function readPipes() {
  1570         stdoutWorker = createReader(child.stdout, "stdout", function (data) {
  1571             if(options.stdout) {
  1572                 setTimeout(function() {
  1573                     options.stdout(data);
  1574                 }, 0);
  1575             } else {
  1576                 output += data;
  1578         });
  1580         if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
  1581             if(options.stderr) {
  1582                 setTimeout(function() {
  1583                     options.stderr(data);
  1584                  }, 0);
  1585             } else {
  1586                 error += data;
  1588         });
  1591     function cleanup() {
  1592         debugLog("Cleanup called\n");
  1593         if(active) {
  1594             active = false;
  1596             closeStdinHandle(); // should only be required in case of errors
  1598             var result, status = ctypes.int();
  1599             result = waitpid(child.pid, status.address(), 0);
  1600             if (result > 0)
  1601                 exitCode = status.value
  1602             else
  1603                 if (workerExitCode >= 0)
  1604                     exitCode = workerExitCode
  1605                 else
  1606                     exitCode = status.value;
  1608             if (stdinWorker)
  1609                 stdinWorker.postMessage({msg: 'stop'})
  1611             setTimeout(function _done() {
  1612                 if (options.done) {
  1613                     try {
  1614                         options.done({
  1615                             exitCode: exitCode,
  1616                             stdout: output,
  1617                             stderr: error,
  1618                         });
  1620                     catch(ex) {
  1621                         // prevent from blocking if options.done() throws an error
  1622                         done = true;
  1623                         throw ex;
  1627                 done = true;
  1628             }, 0);
  1630             libc.close();
  1634     //main
  1636     var cmdStr = getCommandStr(options.command);
  1637     var workDir = getWorkDir(options.workdir);
  1639     child = {};
  1640     pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
  1642     debugLog("subprocess started; got PID "+pid+"\n");
  1644     readPipes();
  1646     if (options.stdin) {
  1647         createStdinWriter();
  1648         if(typeof(options.stdin) == 'function') {
  1649             try {
  1650                 options.stdin({
  1651                     write: function(data) {
  1652                         writeStdin(data);
  1653                     },
  1654                     close: function() {
  1655                         closeStdinHandle();
  1657                 });
  1659             catch(ex) {
  1660                 // prevent from failing if options.stdin() throws an exception
  1661                 closeStdinHandle();
  1662                 throw ex;
  1664         } else {
  1665             writeStdin(options.stdin);
  1666             closeStdinHandle();
  1670     return {
  1671         wait: function() {
  1672             // wait for async operations to complete
  1673             var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
  1674             while (! done) thread.processNextEvent(true)
  1675             return exitCode;
  1676         },
  1677         kill: function(hardKill) {
  1678             var rv = kill(pid, (hardKill ? 9: 15));
  1679             cleanup(-1);
  1680             return rv;
  1681         },
  1682         pid: pid
  1687 module.exports = subprocess;

mercurial