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
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/
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.
16
* The Original Code is subprocess.jsm.
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.
24
* Patrick Brunschwig <patrick@enigmail.net>
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 ***** */
40
* Import into a JS component using
41
* 'Components.utils.import("resource://firefogg/subprocess.jsm");'
43
* This object allows to start a process, and read/write data to/from it
44
* using stdin/stdout/stderr streams.
47
* var p = subprocess.call({
48
* command: '/bin/foo',
49
* arguments: ['-v', 'foo'],
50
* environment: [ "XYZ=abc", "MYVAR=def" ],
52
* workdir: '/home/foo',
53
* //stdin: "some value to write to stdin\nfoobar",
54
* stdin: function(stdin) {
55
* stdin.write("some value to write to stdin\nfoobar");
58
* stdout: function(data) {
59
* dump("got data on stdout:" + data + "\n");
61
* stderr: function(data) {
62
* dump("got data on stderr:" + data + "\n");
64
* done: function(result) {
65
* dump("process terminated with " + result.exitCode + "\n");
69
* p.wait(); // wait for the subprocess to terminate
70
* // this will block the main thread,
71
* // only do if you can wait that long
74
* Description of parameters:
75
* --------------------------
76
* Apart from <command>, all arguments are optional.
78
* command: either a |nsIFile| object pointing to an executable file or a
79
* String containing the platform-dependent path to an executable
82
* arguments: optional string array containing the arguments to the command.
84
* environment: optional string array containing environment variables to pass
85
* to the command. The array elements must have the form
86
* "VAR=data". Please note that if environment is defined, it
87
* replaces any existing environment variables for the subprocess.
89
* charset: Output is decoded with given charset and a string is returned.
90
* If charset is undefined, "UTF-8" is used as default.
91
* To get binary data, set this explicitly to null and the
92
* returned string is not decoded in any way.
94
* workdir: optional; String containing the platform-dependent path to a
95
* directory to become the current working directory of the subprocess.
97
* stdin: optional input data for the process to be passed on standard
98
* input. stdin can either be a string or a function.
99
* A |string| gets written to stdin and stdin gets closed;
100
* A |function| gets passed an object with write and close function.
101
* Please note that the write() function will return almost immediately;
102
* data is always written asynchronously on a separate thread.
104
* stdout: an optional function that can receive output data from the
105
* process. The stdout-function is called asynchronously; it can be
106
* called mutliple times during the execution of a process.
107
* At a minimum at each occurance of \n or \r.
108
* Please note that null-characters might need to be escaped
109
* with something like 'data.replace(/\0/g, "\\0");'.
111
* stderr: an optional function that can receive stderr data from the
112
* process. The stderr-function is called asynchronously; it can be
113
* called mutliple times during the execution of a process. Please
114
* note that null-characters might need to be escaped with
115
* something like 'data.replace(/\0/g, "\\0");'.
116
* (on windows it only gets called once right now)
118
* done: optional function that is called when the process has terminated.
119
* The exit code from the process available via result.exitCode. If
120
* stdout is not defined, then the output from stdout is available
121
* via result.stdout. stderr data is in result.stderr
123
* mergeStderr: optional boolean value. If true, stderr is merged with stdout;
124
* no data will be provided to stderr. Default is false.
126
* bufferedOutput: optional boolean value. If true, stderr and stdout are buffered
127
* and will only deliver data when a certain amount of output is
128
* available. Enabling the option will give you some performance
129
* benefits if your read a lot of data. Don't enable this if your
130
* application works in a conversation-like mode. Default is false.
133
* Description of object returned by subprocess.call(...)
134
* ------------------------------------------------------
135
* The object returned by subprocess.call offers a few methods that can be
138
* wait(): waits for the subprocess to terminate. It is not required to use
139
* wait; done will be called in any case when the subprocess terminated.
141
* kill(hardKill): kill the subprocess. Any open pipes will be closed and
142
* done will be called.
143
* hardKill [ignored on Windows]:
144
* - false: signal the process terminate (SIGTERM)
145
* - true: kill the process (SIGKILL)
148
* Other methods in subprocess
149
* ---------------------------
151
* registerDebugHandler(functionRef): register a handler that is called to get
152
* debugging information
153
* registerLogHandler(functionRef): register a handler that is called to get error
157
* subprocess.registerLogHandler( function(s) { dump(s); } );
162
Components.utils.import("resource://gre/modules/ctypes.jsm");
164
let EXPORTED_SYMBOLS = [ "subprocess" ];
166
const Cc = Components.classes;
167
const Ci = Components.interfaces;
169
const NS_LOCAL_FILE = "@mozilla.org/file/local;1";
172
//Windows API definitions
173
if (ctypes.size_t.size == 8) {
174
var WinABI = ctypes.default_abi;
176
var WinABI = ctypes.winapi_abi;
178
const WORD = ctypes.uint16_t;
179
const DWORD = ctypes.uint32_t;
180
const LPDWORD = DWORD.ptr;
182
const UINT = ctypes.unsigned_int;
183
const BOOL = ctypes.bool;
184
const HANDLE = ctypes.size_t;
186
const HMODULE = HANDLE;
187
const WPARAM = ctypes.size_t;
188
const LPARAM = ctypes.size_t;
189
const LRESULT = ctypes.size_t;
190
const ULONG_PTR = ctypes.uintptr_t;
191
const PVOID = ctypes.voidptr_t;
192
const LPVOID = PVOID;
193
const LPCTSTR = ctypes.jschar.ptr;
194
const LPCWSTR = ctypes.jschar.ptr;
195
const LPTSTR = ctypes.jschar.ptr;
196
const LPSTR = ctypes.char.ptr;
197
const LPCSTR = ctypes.char.ptr;
198
const LPBYTE = ctypes.char.ptr;
200
const CREATE_NEW_CONSOLE = 0x00000010;
201
const CREATE_NO_WINDOW = 0x08000000;
202
const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
203
const STARTF_USESHOWWINDOW = 0x00000001;
204
const STARTF_USESTDHANDLES = 0x00000100;
206
const DUPLICATE_SAME_ACCESS = 0x00000002;
207
const STILL_ACTIVE = 259;
208
const INFINITE = DWORD(0xFFFFFFFF);
209
const WAIT_TIMEOUT = 0x00000102;
212
const PIPE_STATE_NOT_INIT = 3;
213
const PIPE_STATE_OPEN = 2;
214
const PIPE_STATE_CLOSEABLE = 1;
215
const PIPE_STATE_CLOSED = 0;
218
typedef struct _SECURITY_ATTRIBUTES {
220
LPVOID lpSecurityDescriptor;
222
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
224
const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
226
{"lpSecurityDescriptor": LPVOID},
227
{"bInheritHandle": BOOL}
231
typedef struct _STARTUPINFO {
242
DWORD dwFillAttribute;
250
} STARTUPINFO, *LPSTARTUPINFO;
252
const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
254
{"lpReserved": LPTSTR},
255
{"lpDesktop": LPTSTR},
261
{"dwXCountChars": DWORD},
262
{"dwYCountChars": DWORD},
263
{"dwFillAttribute": DWORD},
265
{"wShowWindow": WORD},
266
{"cbReserved2": WORD},
267
{"lpReserved2": LPBYTE},
268
{"hStdInput": HANDLE},
269
{"hStdOutput": HANDLE},
270
{"hStdError": HANDLE}
274
typedef struct _PROCESS_INFORMATION {
279
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
281
const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
282
{"hProcess": HANDLE},
284
{"dwProcessId": DWORD},
285
{"dwThreadId": DWORD}
289
typedef struct _OVERLAPPED {
291
ULONG_PTR InternalHigh;
300
} OVERLAPPED, *LPOVERLAPPED;
302
const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
305
const pid_t = ctypes.int32_t;
310
const O_NONBLOCK = 1;
312
const RLIMIT_NOFILE = 3;
314
function getPlatformValue(valueType) {
317
gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
319
const platformDefaults = {
321
'winnt': [ 'kernel32.dll' ],
324
// library name O_NONBLOCK RLIM_T RLIMIT_NOFILE
325
'darwin': [ 'libc.dylib', 0x04 , ctypes.uint64_t , 8 ],
326
'linux': [ 'libc.so.6', 2024 , ctypes.unsigned_long, 7 ],
327
'freebsd': [ 'libc.so.7', 0x04 , ctypes.int64_t , 8 ],
328
'openbsd': [ 'libc.so.61.0', 0x04 , ctypes.int64_t , 8 ],
329
'sunos': [ 'libc.so', 0x80 , ctypes.unsigned_long, 5 ]
332
return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
336
var gDebugFunc = null,
341
function LogError(s) {
348
function debugLog(s) {
353
function setTimeout(callback, timeout) {
354
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
355
timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
358
function convertBytes(data, charset) {
360
charset = charset || 'UTF-8';
361
var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
362
.getService(Ci.nsIScriptableUnicodeConverter);
364
unicodeConv.charset = charset;
365
string = unicodeConv.ConvertToUnicode(data);
367
LogError("String conversion failed: "+ex.toString()+"\n");
370
string += unicodeConv.Finish();
374
function getCommandStr(command) {
375
let commandStr = null;
376
if (typeof(command) == "string") {
377
let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
378
file.initWithPath(command);
379
if (! (file.isExecutable() && file.isFile()))
380
throw("File '"+command+"' is not an executable file");
381
commandStr = command;
384
if (! (command.isExecutable() && command.isFile()))
385
throw("File '"+command.path+"' is not an executable file");
386
commandStr = command.path;
392
function getWorkDir(workdir) {
393
let workdirStr = null;
394
if (typeof(workdir) == "string") {
395
let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
396
file.initWithPath(workdir);
397
if (! (file.isDirectory()))
398
throw("Directory '"+workdir+"' does not exist");
399
workdirStr = workdir;
402
if (! workdir.isDirectory())
403
throw("Directory '"+workdir.path+"' does not exist");
404
workdirStr = workdir.path;
411
call: function(options) {
412
options.mergeStderr = options.mergeStderr || false;
413
options.bufferedOutput = options.bufferedOutput || false;
414
options.workdir = options.workdir || null;
415
options.environment = options.environment || [];
416
if (options.arguments) {
417
var args = options.arguments;
418
options.arguments = [];
419
args.forEach(function(argument) {
420
options.arguments.push(argument);
423
options.arguments = [];
426
options.libc = getPlatformValue(LIBNAME);
428
if (gXulRuntime.OS.substring(0, 3) == "WIN") {
429
return subprocess_win32(options);
431
return subprocess_unix(options);
435
registerDebugHandler: function(func) {
438
registerLogHandler: function(func) {
441
registerLibcWrapper: function(dllName) {
442
gLibcWrapper = dllName;
445
getPlatformValue: getPlatformValue
450
function subprocess_win32(options) {
451
var kernel32dll = ctypes.open(options.libc),
460
pendingWriteCount = 0,
461
readers = options.mergeStderr ? 1 : 2,
462
stdinOpenState = PIPE_STATE_NOT_INIT,
468
BOOL WINAPI CloseHandle(
472
var CloseHandle = kernel32dll.declare("CloseHandle",
479
BOOL WINAPI CreateProcess(
480
__in_opt LPCTSTR lpApplicationName,
481
__inout_opt LPTSTR lpCommandLine,
482
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
483
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
484
__in BOOL bInheritHandles,
485
__in DWORD dwCreationFlags,
486
__in_opt LPVOID lpEnvironment,
487
__in_opt LPCTSTR lpCurrentDirectory,
488
__in LPSTARTUPINFO lpStartupInfo,
489
__out LPPROCESS_INFORMATION lpProcessInformation
492
var CreateProcessW = kernel32dll.declare("CreateProcessW",
497
SECURITY_ATTRIBUTES.ptr,
498
SECURITY_ATTRIBUTES.ptr,
504
PROCESS_INFORMATION.ptr
508
// BOOL WINAPI ReadFile(
509
// __in HANDLE hFile,
510
// __out LPVOID ReadFileBuffer,
511
// __in DWORD nNumberOfBytesToRead,
512
// __out_opt LPDWORD lpNumberOfBytesRead,
513
// __inout_opt LPOVERLAPPED lpOverlapped
516
// var ReadFileBufferSize = 1024,
517
// ReadFileBuffer = ctypes.char.array(ReadFileBufferSize),
518
// ReadFile = kernel32dll.declare("ReadFile",
529
// BOOL WINAPI PeekNamedPipe(
530
// __in HANDLE hNamedPipe,
531
// __out_opt LPVOID lpBuffer,
532
// __in DWORD nBufferSize,
533
// __out_opt LPDWORD lpBytesRead,
534
// __out_opt LPDWORD lpTotalBytesAvail,
535
// __out_opt LPDWORD lpBytesLeftThisMessage
538
// var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe",
550
// BOOL WINAPI WriteFile(
551
// __in HANDLE hFile,
552
// __in LPCVOID lpBuffer,
553
// __in DWORD nNumberOfBytesToWrite,
554
// __out_opt LPDWORD lpNumberOfBytesWritten,
555
// __inout_opt LPOVERLAPPED lpOverlapped
558
// var WriteFile = kernel32dll.declare("WriteFile",
569
BOOL WINAPI CreatePipe(
570
__out PHANDLE hReadPipe,
571
__out PHANDLE hWritePipe,
572
__in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
576
var CreatePipe = kernel32dll.declare("CreatePipe",
581
SECURITY_ATTRIBUTES.ptr,
586
HANDLE WINAPI GetCurrentProcess(void);
588
var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess",
594
DWORD WINAPI GetLastError(void);
596
var GetLastError = kernel32dll.declare("GetLastError",
602
BOOL WINAPI DuplicateHandle(
603
__in HANDLE hSourceProcessHandle,
604
__in HANDLE hSourceHandle,
605
__in HANDLE hTargetProcessHandle,
606
__out LPHANDLE lpTargetHandle,
607
__in DWORD dwDesiredAccess,
608
__in BOOL bInheritHandle,
612
var DuplicateHandle = kernel32dll.declare("DuplicateHandle",
626
BOOL WINAPI GetExitCodeProcess(
627
__in HANDLE hProcess,
628
__out LPDWORD lpExitCode
631
var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess",
639
DWORD WINAPI WaitForSingleObject(
641
__in DWORD dwMilliseconds
644
var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject",
652
BOOL WINAPI TerminateProcess(
653
__in HANDLE hProcess,
657
var TerminateProcess = kernel32dll.declare("TerminateProcess",
665
function popen(command, workdir, args, environment, child) {
667
args.unshift(command);
668
for (var i = 0; i < args.length; i++) {
669
if (typeof args[i] != "string") { args[i] = args[i].toString(); }
670
/* quote arguments with spaces */
671
if (args[i].match(/\s/)) {
672
args[i] = "\"" + args[i] + "\"";
674
/* If backslash is followed by a quote, double it */
675
args[i] = args[i].replace(/\\\"/g, "\\\\\"");
677
command = args.join(' ');
679
environment = environment || [];
680
if(environment.length) {
681
//An environment block consists of
682
//a null-terminated block of null-terminated strings.
683
//Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar
684
environment = ctypes.jschar.array()(environment.join('\0') + '\0');
689
var hOutputReadTmp = new HANDLE(),
690
hOutputRead = new HANDLE(),
691
hOutputWrite = new HANDLE();
693
var hErrorRead = new HANDLE(),
694
hErrorReadTmp = new HANDLE(),
695
hErrorWrite = new HANDLE();
697
var hInputRead = new HANDLE(),
698
hInputWriteTmp = new HANDLE(),
699
hInputWrite = new HANDLE();
701
// Set up the security attributes struct.
702
var sa = new SECURITY_ATTRIBUTES();
703
sa.nLength = SECURITY_ATTRIBUTES.size;
704
sa.lpSecurityDescriptor = null;
705
sa.bInheritHandle = true;
707
// Create output pipe.
709
if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0))
710
LogError('CreatePipe hOutputReadTmp failed');
712
if(options.mergeStderr) {
713
// Create a duplicate of the output write handle for the std error
714
// write handle. This is necessary in case the child application
715
// closes one of its std output handles.
716
if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
717
GetCurrentProcess(), hErrorWrite.address(), 0,
718
true, DUPLICATE_SAME_ACCESS))
719
LogError("DuplicateHandle hOutputWrite failed");
721
// Create error pipe.
722
if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
723
LogError('CreatePipe hErrorReadTmp failed');
726
// Create input pipe.
727
if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
728
LogError("CreatePipe hInputRead failed");
730
// Create new output/error read handle and the input write handles. Set
731
// the Properties to FALSE. Otherwise, the child inherits the
732
// properties and, as a result, non-closeable handles to the pipes
734
if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
736
hOutputRead.address(), // Address of new handle.
737
0, false, // Make it uninheritable.
738
DUPLICATE_SAME_ACCESS))
739
LogError("DupliateHandle hOutputReadTmp failed");
741
if(!options.mergeStderr) {
742
if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
744
hErrorRead.address(), // Address of new handle.
745
0, false, // Make it uninheritable.
746
DUPLICATE_SAME_ACCESS))
747
LogError("DupliateHandle hErrorReadTmp failed");
749
if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
751
hInputWrite.address(), // Address of new handle.
752
0, false, // Make it uninheritable.
753
DUPLICATE_SAME_ACCESS))
754
LogError("DupliateHandle hInputWriteTmp failed");
756
// Close inheritable copies of the handles.
757
if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
758
if(!options.mergeStderr)
759
if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
760
if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
762
var pi = new PROCESS_INFORMATION();
763
var si = new STARTUPINFO();
765
si.cb = STARTUPINFO.size;
766
si.dwFlags = STARTF_USESTDHANDLES;
767
si.hStdInput = hInputRead;
768
si.hStdOutput = hOutputWrite;
769
si.hStdError = hErrorWrite;
771
// Launch the process
772
if(!CreateProcessW(null, // executable name
773
command, // command buffer
774
null, // process security attribute
775
null, // thread security attribute
776
true, // inherits system handles
777
CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
778
environment, // envrionment block
779
workdir, // set as current directory
780
si.address(), // (in) startup information
781
pi.address() // (out) process information
783
throw("Fatal - Could not launch subprocess '"+command+"'");
785
// Close any unnecessary handles.
786
if (!CloseHandle(pi.hThread))
787
LogError("CloseHandle pi.hThread failed");
789
// Close pipe handles (do not continue to modify the parent).
790
// You need to make sure that no handles to the write end of the
791
// output pipe are maintained in this process or else the pipe will
792
// not close when the child process exits and the ReadFile will hang.
793
if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
794
if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
795
if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
798
child.stdin = hInputWrite;
799
child.stdout = hOutputRead;
800
child.stderr = options.mergeStderr ? undefined : hErrorRead;
801
child.process = pi.hProcess;
806
* createStdinWriter ()
808
* Create a ChromeWorker object for writing data to the subprocess' stdin
809
* pipe. The ChromeWorker object lives on a separate thread; this avoids
810
* internal deadlocks.
812
function createStdinWriter() {
813
debugLog("Creating new stdin worker\n");
814
stdinWorker = new ChromeWorker("subprocess_worker_win.js");
815
stdinWorker.onmessage = function(event) {
819
debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
822
stdinOpenState = PIPE_STATE_OPEN;
823
debugLog("Stdin pipe opened\n");
826
stdinOpenState = PIPE_STATE_CLOSED;
827
debugLog("Stdin pipe closed\n");
830
debugLog("got msg from stdinWorker: "+event.data+"\n");
833
stdinWorker.onerror = function(error) {
836
LogError("got error from stdinWorker: "+error.message+"\n");
839
stdinWorker.postMessage({msg: "init", libc: options.libc});
844
* @data: String containing the data to write
846
* Write data to the subprocess' stdin (equals to sending a request to the
847
* ChromeWorker object to write the data).
849
function writeStdin(data) {
850
if (stdinOpenState == PIPE_STATE_CLOSED) {
851
LogError("trying to write data to closed stdin");
856
debugLog("sending "+data.length+" bytes to stdinWorker\n");
858
var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
860
stdinWorker.postMessage({
870
* Close the stdin pipe, either directly or by requesting the ChromeWorker to
871
* close the pipe. The ChromeWorker will only close the pipe after the last write
872
* request process is done.
875
function closeStdinHandle() {
876
debugLog("trying to close stdin\n");
877
if (stdinOpenState != PIPE_STATE_OPEN) return;
878
stdinOpenState = PIPE_STATE_CLOSEABLE;
881
debugLog("sending close stdin to worker\n");
882
var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
883
stdinWorker.postMessage({
889
stdinOpenState = PIPE_STATE_CLOSED;
890
debugLog("Closing Stdin\n");
891
CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed");
897
* createReader(pipe, name)
899
* @pipe: handle to the pipe
900
* @name: String containing the pipe name (stdout or stderr)
902
* Create a ChromeWorker object for reading data asynchronously from
903
* the pipe (i.e. on a separate thread), and passing the result back to
906
function createReader(pipe, name, callbackFunc) {
907
var worker = new ChromeWorker("subprocess_worker_win.js");
908
worker.onmessage = function(event) {
909
switch(event.data.msg) {
911
debugLog("got "+event.data.count+" bytes from "+name+"\n");
913
if (options.charset === null) {
914
data = event.data.data;
917
data = convertBytes(event.data.data, options.charset);
922
debugLog("Pipe "+name+" closed\n");
924
if (readers == 0) cleanup();
928
LogError("Got msg from "+name+": "+event.data.data+"\n");
931
debugLog("Got msg from "+name+": "+event.data.data+"\n");
935
worker.onerror = function(errorMsg) {
936
LogError("Got error from "+name+": "+errorMsg.message);
940
var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
946
charset: options.charset === null ? "null" : options.charset,
947
bufferedOutput: options.bufferedOutput,
957
* Open the pipes for reading from stdout and stderr
959
function readPipes() {
961
stdoutWorker = createReader(child.stdout, "stdout", function (data) {
963
setTimeout(function() {
964
options.stdout(data);
972
if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
974
setTimeout(function() {
975
options.stderr(data);
986
* close stdin if needed, get the exit code from the subprocess and invoke
987
* the caller's done() function.
989
* Note: because stdout() and stderr() are called using setTimeout, we need to
990
* do the same here in order to guarantee the message sequence.
993
debugLog("Cleanup called\n");
997
closeStdinHandle(); // should only be required in case of errors
999
var exit = new DWORD();
1000
GetExitCodeProcess(child.process, exit.address());
1003
exitCode = exit.value;
1006
stdinWorker.postMessage({msg: 'stop'});
1008
setTimeout(function _done() {
1018
// prevent from blocking if options.done() throws an error
1025
kernel32dll.close();
1029
function startWriting() {
1030
debugLog("startWriting called\n");
1032
if (stdinOpenState == PIPE_STATE_NOT_INIT) {
1033
setTimeout(function _f() {
1039
if(typeof(options.stdin) == 'function') {
1042
write: function(data) {
1051
// prevent from failing if options.stdin() throws an exception
1056
writeStdin(options.stdin);
1063
var cmdStr = getCommandStr(options.command);
1064
var workDir = getWorkDir(options.workdir);
1066
hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
1070
if (options.stdin) {
1071
createStdinWriter();
1079
kill: function(hardKill) {
1080
// hardKill is currently ignored on Windows
1081
var r = !!TerminateProcess(child.process, 255);
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);
1096
function subprocess_unix(options) {
1097
var libc = ctypes.open(options.libc),
1105
stdoutWorker = null,
1106
stderrWorker = null,
1107
pendingWriteCount = 0,
1108
readers = options.mergeStderr ? 1 : 2,
1109
stdinOpenState = PIPE_STATE_NOT_INIT,
1116
var fork = libc.declare("fork",
1121
//int pipe(int pipefd[2]);
1122
var pipefd = ctypes.int.array(2);
1123
var pipe = libc.declare("pipe",
1129
//int dup(int oldfd);
1130
var dup= libc.declare("dup",
1136
//int close(int fd);
1137
var close = libc.declare("close",
1143
//NULL terminated array of strings, argv[0] will be command >> + 2
1144
var argv = ctypes.char.ptr.array(options.arguments.length + 2);
1145
var envp = ctypes.char.ptr.array(options.environment.length + 1);
1146
var execve = libc.declare("execve",
1154
//void exit(int status);
1155
var exit = libc.declare("exit",
1161
//pid_t waitpid(pid_t pid, int *status, int options);
1162
var waitpid = libc.declare("waitpid",
1170
//int kill(pid_t pid, int sig);
1171
var kill = libc.declare("kill",
1178
//int read(int fd, void *buf, size_t count);
1179
var bufferSize = 1024;
1180
var buffer = ctypes.char.array(bufferSize);
1181
var read = libc.declare("read",
1189
//ssize_t write(int fd, const void *buf, size_t count);
1190
var write = libc.declare("write",
1198
//int chdir(const char *path);
1199
var chdir = libc.declare("chdir",
1206
var sleep = libc.declare("sleep",
1212
//int fcntl(int fd, int cmd, ... /* arg */ );
1213
var fcntl = libc.declare("fcntl",
1221
var libcWrapper = null,
1222
launchProcess = null;
1225
debugLog("Trying to use LibcWrapper\n");
1228
libcWrapper = ctypes.open(gLibcWrapper);
1230
launchProcess = libcWrapper.declare("launchProcess",
1243
LogError("could not initialize libc wrapper "+gLibcWrapper+"\n");
1245
gLibcWrapper = null;
1249
function popen(command, workdir, args, environment, child) {
1257
_out = new pipefd();
1258
if(!options.mergeStderr)
1259
_err = new pipefd();
1262
args.unshift(command);
1263
for(i=0;i<args.length;i++) {
1264
_args[i] = ctypes.char.array()(args[i]);
1267
for(i=0;i<environment.length;i++) {
1268
_envp[i] = ctypes.char.array()(environment[i]);
1276
fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK));
1282
if(!options.mergeStderr) {
1284
fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK));
1294
if (launchProcess) {
1295
pid = launchProcess(command, _args, _envp, workdir,
1296
_in, _out, options.mergeStderr ? null : _err);
1298
if (pid > 0) { // parent
1301
if(!options.mergeStderr)
1303
child.stdin = _in[1];
1304
child.stdinFd = _in;
1305
child.stdout = _out[0];
1306
child.stderr = options.mergeStderr ? undefined : _err[0];
1312
if (pid > 0) { // parent
1315
if(!options.mergeStderr)
1317
child.stdin = _in[1];
1318
child.stdinFd = _in;
1319
child.stdout = _out[0];
1320
child.stderr = options.mergeStderr ? undefined : _err[0];
1323
} else if (pid == 0) { // child
1325
if (chdir(workdir) < 0) {
1329
closeOtherFds(_in[0], _out[1], options.mergeStderr ? _out[1] : _err[1]);
1332
if(!options.mergeStderr)
1339
dup(options.mergeStderr ? _out[1] : _err[1]);
1340
execve(command, _args, _envp);
1343
// we should not really end up here
1344
if(!options.mergeStderr) {
1352
throw("Fatal - failed to create subprocess '"+command+"'");
1360
// close any file descriptors that are not required for the process
1361
function closeOtherFds(fdIn, fdOut, fdErr) {
1363
var maxFD = 256; // arbitrary max
1366
var rlim_t = getPlatformValue(RLIM_T);
1368
const RLIMITS = new ctypes.StructType("RLIMITS", [
1369
{"rlim_cur": rlim_t},
1370
{"rlim_max": rlim_t}
1374
var getrlimit = libc.declare("getrlimit",
1381
var rl = new RLIMITS();
1382
if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) == 0) {
1383
if (rl.rlim_cur < Math.pow(2,20)) // ignore too high numbers
1384
maxFD = rl.rlim_cur;
1386
debugLog("getlimit: maxFD="+maxFD+"\n");
1390
debugLog("getrlimit: no such function on this OS\n");
1391
debugLog(ex.toString());
1394
// close any file descriptors
1395
// fd's 0-2 are already closed
1396
for (var i = 3; i < maxFD; i++) {
1397
if (i != fdIn && i != fdOut && i != fdErr)
1403
* createStdinWriter ()
1405
* Create a ChromeWorker object for writing data to the subprocess' stdin
1406
* pipe. The ChromeWorker object lives on a separate thread; this avoids
1407
* internal deadlocks.
1409
function createStdinWriter() {
1410
debugLog("Creating new stdin worker\n");
1411
stdinWorker = new ChromeWorker("subprocess_worker_unix.js");
1412
stdinWorker.onmessage = function(event) {
1413
switch (event.data.msg) {
1415
switch(event.data.data) {
1417
pendingWriteCount--;
1418
debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
1421
stdinOpenState = PIPE_STATE_OPEN;
1422
debugLog("Stdin pipe opened\n");
1425
stdinOpenState = PIPE_STATE_CLOSED;
1426
debugLog("Stdin pipe closed\n");
1429
debugLog("got msg from stdinWorker: "+event.data.data+"\n");
1433
debugLog("stdinWorker: "+event.data.data+"\n");
1436
LogError("got error from stdinWorker: "+event.data.data+"\n");
1437
pendingWriteCount = 0;
1438
stdinOpenState = PIPE_STATE_CLOSED;
1442
stdinWorker.onerror = function(error) {
1443
pendingWriteCount = 0;
1446
LogError("got error from stdinWorker: "+error.message+"\n");
1448
stdinWorker.postMessage({msg: "init", libc: options.libc});
1453
* @data: String containing the data to write
1455
* Write data to the subprocess' stdin (equals to sending a request to the
1456
* ChromeWorker object to write the data).
1458
function writeStdin(data) {
1459
if (stdinOpenState == PIPE_STATE_CLOSED) {
1460
LogError("trying to write data to closed stdin");
1464
++pendingWriteCount;
1465
debugLog("sending "+data.length+" bytes to stdinWorker\n");
1466
var pipe = parseInt(child.stdin);
1468
stdinWorker.postMessage({
1477
* closeStdinHandle()
1479
* Close the stdin pipe, either directly or by requesting the ChromeWorker to
1480
* close the pipe. The ChromeWorker will only close the pipe after the last write
1481
* request process is done.
1484
function closeStdinHandle() {
1485
debugLog("trying to close stdin\n");
1486
if (stdinOpenState != PIPE_STATE_OPEN) return;
1487
stdinOpenState = PIPE_STATE_CLOSEABLE;
1490
debugLog("sending close stdin to worker\n");
1491
var pipePtr = parseInt(child.stdin);
1493
stdinWorker.postMessage({
1499
stdinOpenState = PIPE_STATE_CLOSED;
1500
debugLog("Closing Stdin\n");
1501
close(child.stdin) && LogError("CloseHandle stdin failed");
1507
* createReader(pipe, name)
1509
* @pipe: handle to the pipe
1510
* @name: String containing the pipe name (stdout or stderr)
1511
* @callbackFunc: function to be called with the read data
1513
* Create a ChromeWorker object for reading data asynchronously from
1514
* the pipe (i.e. on a separate thread), and passing the result back to
1518
function createReader(pipe, name, callbackFunc) {
1519
var worker = new ChromeWorker("subprocess_worker_unix.js");
1520
worker.onmessage = function(event) {
1521
switch(event.data.msg) {
1523
debugLog("got "+event.data.count+" bytes from "+name+"\n");
1525
if (options.charset === null) {
1526
data = event.data.data;
1529
data = convertBytes(event.data.data, options.charset);
1534
debugLog("Pipe "+name+" closed\n");
1535
if (event.data.data != 0) workerExitCode = event.data.data;
1537
if (readers == 0) cleanup();
1540
LogError("Got error from "+name+": "+event.data.data);
1544
debugLog("Got msg from "+name+": "+event.data.data+"\n");
1547
worker.onerror = function(error) {
1548
LogError("Got error from "+name+": "+error.message);
1552
worker.postMessage({
1557
charset: options.charset === null ? "null" : options.charset,
1558
bufferedOutput: options.bufferedOutput,
1568
* Open the pipes for reading from stdout and stderr
1570
function readPipes() {
1572
stdoutWorker = createReader(child.stdout, "stdout", function (data) {
1573
if(options.stdout) {
1574
setTimeout(function() {
1575
options.stdout(data);
1582
if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
1583
if(options.stderr) {
1584
setTimeout(function() {
1585
options.stderr(data);
1593
function cleanup() {
1594
debugLog("Cleanup called\n");
1598
closeStdinHandle(); // should only be required in case of errors
1600
var result, status = ctypes.int();
1601
result = waitpid(child.pid, status.address(), 0);
1603
if (exitCode > -2) {
1605
exitCode = status.value;
1607
if (workerExitCode >= 0)
1608
exitCode = workerExitCode;
1610
exitCode = status.value;
1614
stdinWorker.postMessage({msg: 'stop'});
1616
setTimeout(function _done() {
1626
// prevent from blocking if options.done() throws an error
1640
function startWriting() {
1641
debugLog("startWriting called\n");
1643
if (stdinOpenState == PIPE_STATE_NOT_INIT) {
1644
setTimeout(function _f() {
1650
if(typeof(options.stdin) == 'function') {
1653
write: function(data) {
1662
// prevent from failing if options.stdin() throws an exception
1667
writeStdin(options.stdin);
1673
var cmdStr = getCommandStr(options.command);
1674
var workDir = getWorkDir(options.workdir);
1677
pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
1679
debugLog("subprocess started; got PID "+pid+"\n");
1683
if (options.stdin) {
1684
createStdinWriter();
1693
// wait for async operations to complete
1694
var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
1695
while (! done) thread.processNextEvent(true);
1698
kill: function(hardKill) {
1699
var rv = kill(pid, (hardKill ? 9: 15));