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.

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

mercurial