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