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

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial