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