Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F25703497
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
79 KB
Subscribers
None
View Options
diff --git a/ipc/modules/subprocess.jsm b/ipc/modules/subprocess.jsm
index 4ab0b9e0..eaeb9b44 100644
--- a/ipc/modules/subprocess.jsm
+++ b/ipc/modules/subprocess.jsm
@@ -1,1902 +1,1902 @@
// -*- coding: utf-8 -*-
// vim: et:ts=4:sw=4:sts=4:ft=javascript
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "MPL"); you may not use this file
* except in compliance with the MPL. You may obtain a copy of
* the MPL at http://www.mozilla.org/MPL/
*
* Software distributed under the MPL is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the MPL for the specific language governing
* rights and limitations under the MPL.
*
* The Original Code is subprocess.jsm.
*
* The Initial Developer of this code is Jan Gerber.
* Portions created by Jan Gerber <j@mailb.org>
* are Copyright (C) 2011 Jan Gerber.
* All Rights Reserved.
*
* Contributor(s):
* Patrick Brunschwig <patrick@enigmail.net>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
* ***** END LICENSE BLOCK ***** */
/*
* Import into a JS component using
* 'Components.utils.import("resource://firefogg/subprocess.jsm");'
*
* This object allows to start a process, and read/write data to/from it
* using stdin/stdout/stderr streams.
* Usage example:
*
* var p = subprocess.call({
* command: '/bin/foo',
* arguments: ['-v', 'foo'],
* environment: [ "XYZ=abc", "MYVAR=def" ],
* charset: 'UTF-8',
* workdir: '/home/foo',
* //stdin: "some value to write to stdin\nfoobar",
* stdin: function(stdin) {
* stdin.write("some value to write to stdin\nfoobar");
* stdin.close();
* },
* stdout: function(data) {
* dump("got data on stdout:" + data + "\n");
* },
* stderr: function(data) {
* dump("got data on stderr:" + data + "\n");
* },
* done: function(result) {
* dump("process terminated with " + result.exitCode + "\n");
* },
* mergeStderr: false
* });
* p.wait(); // wait for the subprocess to terminate
* // this will block the main thread,
* // only do if you can wait that long
*
*
* Description of parameters:
* --------------------------
* Apart from <command>, all arguments are optional.
*
* command: either a |nsIFile| object pointing to an executable file or a
* String containing the platform-dependent path to an executable
* file.
*
* arguments: optional string array containing the arguments to the command.
*
* environment: optional string array containing environment variables to pass
* to the command. The array elements must have the form
* "VAR=data". Please note that if environment is defined, it
* replaces any existing environment variables for the subprocess.
*
* charset: Output is decoded with given charset and a string is returned.
* If charset is undefined, "UTF-8" is used as default.
* To get binary data, set this explicitly to null and the
* returned string is not decoded in any way.
*
* workdir: optional; String containing the platform-dependent path to a
* directory to become the current working directory of the subprocess.
*
* stdin: optional input data for the process to be passed on standard
* input. stdin can either be a string or a function.
* A |string| gets written to stdin and stdin gets closed;
* A |function| gets passed an object with write and close function.
* Please note that the write() function will return almost immediately;
* data is always written asynchronously on a separate thread.
*
* stdout: an optional function that can receive output data from the
* process. The stdout-function is called asynchronously; it can be
* called mutliple times during the execution of a process.
* At a minimum at each occurance of \n or \r.
* Please note that null-characters might need to be escaped
* with something like 'data.replace(/\0/g, "\\0");'.
*
* stderr: an optional function that can receive stderr data from the
* process. The stderr-function is called asynchronously; it can be
* called mutliple times during the execution of a process. Please
* note that null-characters might need to be escaped with
* something like 'data.replace(/\0/g, "\\0");'.
* (on windows it only gets called once right now)
*
* pipes: **NOT AVAILABLE ON WINDOWS**
* optional argmuent containing an |array| of the objects with the
* following structure:
* - readFd: function(data) or string - working identically to stdout()
* - writeFd: function(pipe) - working identically to stdin()
* The array is treated as an ordered list.
* For every element in the array, a new file descriptor is opened
* to read from or and write to. The file descriptors are numbered
* sequentially starting by 3, i.e. the child process can read or
* write from/to file descriptors 3, 4, etc.
* NOTE: pipes are directed; you can only specify either readFd OR
* writeFd for any element of the array.
*
* done: optional function that is called when the process has terminated.
* The exit code from the process available via result.exitCode. If
* stdout is not defined, then the output from stdout is available
* via result.stdout. stderr data is in result.stderr
*
* mergeStderr: optional boolean value. If true, stderr is merged with stdout;
* no data will be provided to stderr. Default is false.
*
* bufferedOutput: optional boolean value. If true, stderr and stdout are buffered
* and will only deliver data when a certain amount of output is
* available. Enabling the option will give you some performance
* benefits if you read a lot of data. Don't enable this if your
* application works in a conversation-like mode. Default is false.
*
*
* Description of object returned by subprocess.call(...)
* ------------------------------------------------------
* The object returned by subprocess.call offers a few methods that can be
* executed:
*
* wait(): waits for the subprocess to terminate. It is not required to use
* wait; done will be called in any case when the subprocess terminated.
*
* kill(hardKill): kill the subprocess. Any open pipes will be closed and
* done will be called.
* hardKill [ignored on Windows]:
* - false: signal the process terminate (SIGTERM)
* - true: kill the process (SIGKILL)
*
*
* Other methods in subprocess
* ---------------------------
*
* registerDebugHandler(functionRef): register a handler that is called to get
* debugging information
* registerLogHandler(functionRef): register a handler that is called to get error
* messages
*
* example:
* subprocess.registerLogHandler( function(s) { dump(s); } );
*/
Components.utils.import("resource://gre/modules/ctypes.jsm");
let EXPORTED_SYMBOLS = [ "subprocess" ];
const Cc = Components.classes;
const Ci = Components.interfaces;
const NS_LOCAL_FILE = "@mozilla.org/file/local;1";
//Windows API definitions
if (ctypes.size_t.size == 8) {
var WinABI = ctypes.default_abi;
} else {
var WinABI = ctypes.winapi_abi;
}
const WORD = ctypes.uint16_t;
const DWORD = ctypes.uint32_t;
const LPDWORD = DWORD.ptr;
const UINT = ctypes.unsigned_int;
const BOOL = ctypes.bool;
const HANDLE = ctypes.size_t;
const HWND = HANDLE;
const HMODULE = HANDLE;
const WPARAM = ctypes.size_t;
const LPARAM = ctypes.size_t;
const LRESULT = ctypes.size_t;
const ULONG_PTR = ctypes.uintptr_t;
const PVOID = ctypes.voidptr_t;
const LPVOID = PVOID;
const LPCTSTR = ctypes.jschar.ptr;
const LPCWSTR = ctypes.jschar.ptr;
const LPTSTR = ctypes.jschar.ptr;
const LPSTR = ctypes.char.ptr;
const LPCSTR = ctypes.char.ptr;
const LPBYTE = ctypes.char.ptr;
const CREATE_NEW_CONSOLE = 0x00000010;
const CREATE_NO_WINDOW = 0x08000000;
const CREATE_UNICODE_ENVIRONMENT = 0x00000400;
const STARTF_USESHOWWINDOW = 0x00000001;
const STARTF_USESTDHANDLES = 0x00000100;
const SW_HIDE = 0;
const DUPLICATE_SAME_ACCESS = 0x00000002;
const STILL_ACTIVE = 259;
const INFINITE = DWORD(0xFFFFFFFF);
const WAIT_TIMEOUT = 0x00000102;
// stdin pipe states
const PIPE_STATE_NOT_INIT = 3;
const PIPE_STATE_OPEN = 2;
const PIPE_STATE_CLOSEABLE = 1;
const PIPE_STATE_CLOSED = 0;
/*
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
*/
const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
{"nLength": DWORD},
{"lpSecurityDescriptor": LPVOID},
{"bInheritHandle": BOOL}
]);
/*
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
*/
const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
{"cb": DWORD},
{"lpReserved": LPTSTR},
{"lpDesktop": LPTSTR},
{"lpTitle": LPTSTR},
{"dwX": DWORD},
{"dwY": DWORD},
{"dwXSize": DWORD},
{"dwYSize": DWORD},
{"dwXCountChars": DWORD},
{"dwYCountChars": DWORD},
{"dwFillAttribute": DWORD},
{"dwFlags": DWORD},
{"wShowWindow": WORD},
{"cbReserved2": WORD},
{"lpReserved2": LPBYTE},
{"hStdInput": HANDLE},
{"hStdOutput": HANDLE},
{"hStdError": HANDLE}
]);
/*
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
*/
const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
{"hProcess": HANDLE},
{"hThread": HANDLE},
{"dwProcessId": DWORD},
{"dwThreadId": DWORD}
]);
/*
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
union {
struct {
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
*/
const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
//UNIX definitions
const pid_t = ctypes.int32_t;
const WNOHANG = 1;
const F_GETFL = 3;
const F_SETFL = 4;
const LIBNAME = 0;
const O_NONBLOCK = 1;
const RLIM_T = 2;
const RLIMIT_NOFILE = 3;
function getPlatformValue(valueType) {
if (! gXulRuntime)
gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
const platformDefaults = {
// Windows API:
'winnt': [ 'kernel32.dll' ],
// Unix API:
// library name O_NONBLOCK RLIM_T RLIMIT_NOFILE
'darwin': [ 'libc.dylib', 0x04 , ctypes.uint64_t , 8 ],
'linux': [ 'libc.so.6', 2024 , ctypes.unsigned_long, 7 ],
'freebsd': [ 'libc.so.7', 0x04 , ctypes.int64_t , 8 ],
'dragonfly': [ 'libc.so.8', 0x04 , ctypes.int64_t , 8 ],
'gnu/kfreebsd': [ 'libc.so.0.1', 0x04 , ctypes.int64_t , 8 ],
'netbsd': [ 'libc.so', 0x04 , ctypes.int64_t , 8 ],
'openbsd': [ 'libc.so.61.0', 0x04 , ctypes.int64_t , 8 ],
'sunos': [ 'libc.so', 0x80 , ctypes.unsigned_long, 5 ]
};
return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
}
var gDebugFunc = null,
gLogFunc = null,
gXulRuntime = null;
function LogError(s) {
if (gLogFunc)
gLogFunc(s);
else
dump(s);
}
function debugLog(s) {
if (gDebugFunc)
gDebugFunc(s);
}
function setTimeout(callback, timeout) {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
}
function convertBytes(data, charset) {
var string = charset == "null" || charset == "UTF-8" ? data
: Cc["@mozilla.org/intl/utf8converterservice;1"]
.getService(Ci.nsIUTF8ConverterService)
.convertStringToUTF8(data, charset, false, true);
return string;
}
function getCommandStr(command) {
let commandStr = null;
if (typeof(command) == "string") {
let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
file.initWithPath(command);
if (! (file.isExecutable() && file.isFile()))
throw("File '"+command+"' is not an executable file");
commandStr = command;
}
else {
if (! (command.isExecutable() && command.isFile()))
throw("File '"+command.path+"' is not an executable file");
commandStr = command.path;
}
return commandStr;
}
function getWorkDir(workdir) {
let workdirStr = null;
if (typeof(workdir) == "string") {
let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
file.initWithPath(workdir);
if (! (file.isDirectory()))
throw("Directory '"+workdir+"' does not exist");
workdirStr = workdir;
}
else if (workdir) {
if (! workdir.isDirectory())
throw("Directory '"+workdir.path+"' does not exist");
workdirStr = workdir.path;
}
return workdirStr;
}
var subprocess = {
call: function(options) {
options.mergeStderr = options.mergeStderr || false;
options.bufferedOutput = options.bufferedOutput || false;
options.workdir = options.workdir || null;
options.environment = options.environment || [];
- options.charset = options.charset === null ? "null" : options.charset || "UTF-8";
+ options.charset = !options.charset ? "null" : options.charset || "UTF-8";
if (options.arguments) {
var args = options.arguments;
options.arguments = [];
args.forEach(function(argument) {
options.arguments.push(argument);
});
} else {
options.arguments = [];
}
if (options.pipes) {
for (let i in options.pipes) {
if (options.pipes[i].writeFd && options.pipes[i].readFd) {
throw("Fatal - pipe "+i+": readFd and writeFd specified");
}
if (!options.pipes[i].writeFd && !options.pipes[i].readFd) {
throw("Fatal - pipe "+i+": neither readFd nor writeFd specified");
}
}
}
options.libc = getPlatformValue(LIBNAME);
if (gXulRuntime.OS.substring(0, 3) == "WIN") {
return subprocess_win32(options);
} else {
return subprocess_unix(options);
}
},
ProcessBuilder: function () {
this.process = {};
this.setCommand = function(command){
this.process.command = command;
};
this.setArguments = function(args){
this.process.arguments = args;
};
this.setEnvironment = function(envList){
this.process.environment = envList;
};
this.setStdin = function(stdin){
this.process.stdin = stdin;
};
this.setStdout = function(stdout){
this.process.stdout = stdout;
};
this.setDone = function(done){
this.process.done = done;
};
this.build = function(){
this.process.charset= null;
this.process.mergeStderr= false;
this.process.resultData= "";
this.process.errorData= "";
this.process.exitCode= -1;
return this.process;
};
return this;
},
registerDebugHandler: function(func) {
gDebugFunc = func;
},
registerLogHandler: function(func) {
gLogFunc = func;
},
getPlatformValue: getPlatformValue
};
function subprocess_win32(options) {
var kernel32dll = ctypes.open(options.libc),
hChildProcess,
active = true,
done = false,
exitCode = -1,
child = {},
stdinWorker = null,
stdoutWorker = null,
stderrWorker = null,
pendingWriteCount = 0,
readers = options.mergeStderr ? 1 : 2,
stdinOpenState = PIPE_STATE_NOT_INIT,
error = '',
output = '';
//api declarations
/*
BOOL WINAPI CloseHandle(
__in HANDLE hObject
);
*/
var CloseHandle = kernel32dll.declare("CloseHandle",
WinABI,
BOOL,
HANDLE
);
/*
BOOL WINAPI CreateProcess(
__in_opt LPCTSTR lpApplicationName,
__inout_opt LPTSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
*/
var CreateProcessW = kernel32dll.declare("CreateProcessW",
WinABI,
BOOL,
LPCTSTR,
LPTSTR,
SECURITY_ATTRIBUTES.ptr,
SECURITY_ATTRIBUTES.ptr,
BOOL,
DWORD,
LPVOID,
LPCTSTR,
STARTUPINFO.ptr,
PROCESS_INFORMATION.ptr
);
// /*
// BOOL WINAPI ReadFile(
// __in HANDLE hFile,
// __out LPVOID ReadFileBuffer,
// __in DWORD nNumberOfBytesToRead,
// __out_opt LPDWORD lpNumberOfBytesRead,
// __inout_opt LPOVERLAPPED lpOverlapped
// );
// */
// var ReadFileBufferSize = 1024,
// ReadFileBuffer = ctypes.char.array(ReadFileBufferSize),
// ReadFile = kernel32dll.declare("ReadFile",
// WinABI,
// BOOL,
// HANDLE,
// ReadFileBuffer,
// DWORD,
// LPDWORD,
// OVERLAPPED.ptr
// );
//
// /*
// BOOL WINAPI PeekNamedPipe(
// __in HANDLE hNamedPipe,
// __out_opt LPVOID lpBuffer,
// __in DWORD nBufferSize,
// __out_opt LPDWORD lpBytesRead,
// __out_opt LPDWORD lpTotalBytesAvail,
// __out_opt LPDWORD lpBytesLeftThisMessage
// );
// */
// var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe",
// WinABI,
// BOOL,
// HANDLE,
// ReadFileBuffer,
// DWORD,
// LPDWORD,
// LPDWORD,
// LPDWORD
// );
//
// /*
// BOOL WINAPI WriteFile(
// __in HANDLE hFile,
// __in LPCVOID lpBuffer,
// __in DWORD nNumberOfBytesToWrite,
// __out_opt LPDWORD lpNumberOfBytesWritten,
// __inout_opt LPOVERLAPPED lpOverlapped
// );
// */
// var WriteFile = kernel32dll.declare("WriteFile",
// WinABI,
// BOOL,
// HANDLE,
// ctypes.char.ptr,
// DWORD,
// LPDWORD,
// OVERLAPPED.ptr
// );
/*
BOOL WINAPI CreatePipe(
__out PHANDLE hReadPipe,
__out PHANDLE hWritePipe,
__in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
__in DWORD nSize
);
*/
var CreatePipe = kernel32dll.declare("CreatePipe",
WinABI,
BOOL,
HANDLE.ptr,
HANDLE.ptr,
SECURITY_ATTRIBUTES.ptr,
DWORD
);
/*
HANDLE WINAPI GetCurrentProcess(void);
*/
var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess",
WinABI,
HANDLE
);
/*
DWORD WINAPI GetLastError(void);
*/
var GetLastError = kernel32dll.declare("GetLastError",
WinABI,
DWORD
);
/*
BOOL WINAPI DuplicateHandle(
__in HANDLE hSourceProcessHandle,
__in HANDLE hSourceHandle,
__in HANDLE hTargetProcessHandle,
__out LPHANDLE lpTargetHandle,
__in DWORD dwDesiredAccess,
__in BOOL bInheritHandle,
__in DWORD dwOptions
);
*/
var DuplicateHandle = kernel32dll.declare("DuplicateHandle",
WinABI,
BOOL,
HANDLE,
HANDLE,
HANDLE,
HANDLE.ptr,
DWORD,
BOOL,
DWORD
);
/*
BOOL WINAPI GetExitCodeProcess(
__in HANDLE hProcess,
__out LPDWORD lpExitCode
);
*/
var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess",
WinABI,
BOOL,
HANDLE,
LPDWORD
);
/*
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);
*/
var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject",
WinABI,
DWORD,
HANDLE,
DWORD
);
/*
BOOL WINAPI TerminateProcess(
__in HANDLE hProcess,
__in UINT uExitCode
);
*/
var TerminateProcess = kernel32dll.declare("TerminateProcess",
WinABI,
BOOL,
HANDLE,
UINT
);
//functions
function popen(command, workdir, args, environment, child) {
//escape arguments
args.unshift(command);
for (var i = 0; i < args.length; i++) {
if (typeof args[i] != "string") { args[i] = args[i].toString(); }
/* quote arguments with spaces */
if (args[i].match(/\s/)) {
args[i] = "\"" + args[i] + "\"";
}
/* If backslash is followed by a quote, double it */
args[i] = args[i].replace(/\\\"/g, "\\\\\"");
}
command = args.join(' ');
environment = environment || [];
if(environment.length) {
//An environment block consists of
//a null-terminated block of null-terminated strings.
//Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar
environment = ctypes.jschar.array()(environment.join('\0') + '\0');
} else {
environment = null;
}
var hOutputReadTmp = new HANDLE(),
hOutputRead = new HANDLE(),
hOutputWrite = new HANDLE();
var hErrorRead = new HANDLE(),
hErrorReadTmp = new HANDLE(),
hErrorWrite = new HANDLE();
var hInputRead = new HANDLE(),
hInputWriteTmp = new HANDLE(),
hInputWrite = new HANDLE();
// Set up the security attributes struct.
var sa = new SECURITY_ATTRIBUTES();
sa.nLength = SECURITY_ATTRIBUTES.size;
sa.lpSecurityDescriptor = null;
sa.bInheritHandle = true;
// Create output pipe.
if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0))
LogError("CreatePipe hOutputReadTmp failed");
if(options.mergeStderr) {
// Create a duplicate of the output write handle for the std error
// write handle. This is necessary in case the child application
// closes one of its std output handles.
if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
GetCurrentProcess(), hErrorWrite.address(), 0,
true, DUPLICATE_SAME_ACCESS))
LogError("DuplicateHandle hOutputWrite failed");
} else {
// Create error pipe.
if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
LogError("CreatePipe hErrorReadTmp failed");
}
// Create input pipe.
if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
LogError("CreatePipe hInputRead failed");
// Create new output/error read handle and the input write handles. Set
// the Properties to FALSE. Otherwise, the child inherits the
// properties and, as a result, non-closeable handles to the pipes
// are created.
if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
GetCurrentProcess(),
hOutputRead.address(), // Address of new handle.
0, false, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
LogError("DupliateHandle hOutputReadTmp failed");
if(!options.mergeStderr) {
if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
GetCurrentProcess(),
hErrorRead.address(), // Address of new handle.
0, false, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
LogError("DupliateHandle hErrorReadTmp failed");
}
if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
GetCurrentProcess(),
hInputWrite.address(), // Address of new handle.
0, false, // Make it uninheritable.
DUPLICATE_SAME_ACCESS))
LogError("DupliateHandle hInputWriteTmp failed");
// Close inheritable copies of the handles.
if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
if(!options.mergeStderr)
if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
var pi = new PROCESS_INFORMATION();
var si = new STARTUPINFO();
si.cb = STARTUPINFO.size;
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = hInputRead;
si.hStdOutput = hOutputWrite;
si.hStdError = hErrorWrite;
// Launch the process
if(!CreateProcessW(null, // executable name
command, // command buffer
null, // process security attribute
null, // thread security attribute
true, // inherits system handles
CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
environment, // envrionment block
workdir, // set as current directory
si.address(), // (in) startup information
pi.address() // (out) process information
))
throw("Fatal - Could not launch subprocess '"+command+"'");
// Close any unnecessary handles.
if (!CloseHandle(pi.hThread))
LogError("CloseHandle pi.hThread failed");
// Close pipe handles (do not continue to modify the parent).
// You need to make sure that no handles to the write end of the
// output pipe are maintained in this process or else the pipe will
// not close when the child process exits and the ReadFile will hang.
if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
//return values
child.stdin = hInputWrite;
child.stdout = hOutputRead;
child.stderr = options.mergeStderr ? undefined : hErrorRead;
child.process = pi.hProcess;
return pi.hProcess;
}
/*
* createStdinWriter ()
*
* Create a ChromeWorker object for writing data to the subprocess' stdin
* pipe. The ChromeWorker object lives on a separate thread; this avoids
* internal deadlocks.
*/
function createStdinWriter() {
debugLog("Creating new stdin worker\n");
stdinWorker = new ChromeWorker("subprocess_worker_win.js");
stdinWorker.onmessage = function(event) {
switch(event.data) {
case "WriteOK":
pendingWriteCount--;
debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
break;
case "InitOK":
stdinOpenState = PIPE_STATE_OPEN;
debugLog("Stdin pipe opened\n");
break;
case "ClosedOK":
stdinOpenState = PIPE_STATE_CLOSED;
debugLog("Stdin pipe closed\n");
break;
default:
debugLog("got msg from stdinWorker: "+event.data+"\n");
}
};
stdinWorker.onerror = function(error) {
pendingWriteCount--;
exitCode = -2;
LogError("got error from stdinWorker: "+error.message+"\n");
};
stdinWorker.postMessage({msg: "init", libc: options.libc});
}
/*
* writeStdin()
* @data: String containing the data to write
*
* Write data to the subprocess' stdin (equals to sending a request to the
* ChromeWorker object to write the data).
*/
function writeStdin(data) {
if (stdinOpenState == PIPE_STATE_CLOSED) {
LogError("trying to write data to closed stdin");
return;
}
++pendingWriteCount;
debugLog("sending "+data.length+" bytes to stdinWorker\n");
var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
stdinWorker.postMessage({
msg: 'write',
pipe: pipePtr,
data: data
});
}
/*
* closeStdinHandle()
*
* Close the stdin pipe, either directly or by requesting the ChromeWorker to
* close the pipe. The ChromeWorker will only close the pipe after the last write
* request process is done.
*/
function closeStdinHandle() {
debugLog("trying to close stdin\n");
if (stdinOpenState != PIPE_STATE_OPEN) return;
stdinOpenState = PIPE_STATE_CLOSEABLE;
if (stdinWorker) {
debugLog("sending close stdin to worker\n");
var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
stdinWorker.postMessage({
msg: 'close',
pipe: pipePtr
});
}
else {
stdinOpenState = PIPE_STATE_CLOSED;
debugLog("Closing Stdin\n");
if(!CloseHandle(child.stdin)) LogError("CloseHandle hInputWrite failed");
}
}
/*
* createReader(pipe, name)
*
* @pipe: handle to the pipe
* @name: String containing the pipe name (stdout or stderr)
*
* Create a ChromeWorker object for reading data asynchronously from
* the pipe (i.e. on a separate thread), and passing the result back to
* the caller.
*/
function createReader(pipe, name, callbackFunc) {
var worker = new ChromeWorker("subprocess_worker_win.js");
worker.onmessage = function(event) {
switch(event.data.msg) {
case "data":
debugLog("got "+event.data.count+" bytes from "+name+"\n");
var data = convertBytes(event.data.data, options.charset);
callbackFunc(data);
break;
case "done":
debugLog("Pipe "+name+" closed\n");
--readers;
if (readers === 0) cleanup();
break;
case "error":
exitCode = -2;
LogError("Got msg from "+name+": "+event.data.data+"\n");
break;
default:
debugLog("Got msg from "+name+": "+event.data.data+"\n");
}
};
worker.onerror = function(errorMsg) {
LogError("Got error from "+name+": "+errorMsg.message);
exitCode = -2;
};
var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
worker.postMessage({
msg: 'read',
pipe: pipePtr,
libc: options.libc,
- charset: options.charset === null ? "null" : options.charset,
+ charset: !options.charset ? "null" : options.charset,
bufferedOutput: options.bufferedOutput,
name: name
});
return worker;
}
/*
* readPipes()
*
* Open the pipes for reading from stdout and stderr
*/
function readPipes() {
stdoutWorker = createReader(child.stdout, "stdout", function (data) {
if(options.stdout) {
setTimeout(function() {
options.stdout(data);
}, 0);
} else {
output += data;
}
});
if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
if(options.stderr) {
setTimeout(function() {
options.stderr(data);
}, 0);
} else {
error += data;
}
});
}
/*
* cleanup()
*
* close stdin if needed, get the exit code from the subprocess and invoke
* the caller's done() function.
*
* Note: because stdout() and stderr() are called using setTimeout, we need to
* do the same here in order to guarantee the message sequence.
*/
function cleanup() {
debugLog("Cleanup called\n");
if(active) {
active = false;
closeStdinHandle(); // should only be required in case of errors
var exit = new DWORD();
GetExitCodeProcess(child.process, exit.address());
if (exitCode > -2)
exitCode = exit.value;
exitCode = exitCode % 0xFF;
if (stdinWorker)
stdinWorker.postMessage({msg: 'stop'});
setTimeout(function _done() {
if (options.done) {
try {
options.done({
exitCode: exitCode,
stdout: output,
stderr: error
});
}
catch (ex) {
// prevent from blocking if options.done() throws an error
done = true;
throw ex;
}
}
done = true;
}, 0);
kernel32dll.close();
}
}
function startWriting() {
debugLog("startWriting called\n");
if (stdinOpenState == PIPE_STATE_NOT_INIT) {
setTimeout(function _f() {
startWriting();
}, 1);
return;
}
if(typeof(options.stdin) == 'function') {
try {
options.stdin({
write: function(data) {
writeStdin(data);
},
close: function() {
closeStdinHandle();
}
});
}
catch (ex) {
// prevent from failing if options.stdin() throws an exception
closeStdinHandle();
throw ex;
}
} else {
writeStdin(options.stdin);
closeStdinHandle();
}
}
//main
if (options.pipes) throw "Error - additional pipes are not supported on this OS";
var cmdStr = getCommandStr(options.command);
var workDir = getWorkDir(options.workdir);
hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
readPipes();
if (options.stdin) {
createStdinWriter();
startWriting();
}
else
closeStdinHandle();
return {
kill: function(hardKill) {
if (!active) return true;
// hardKill is currently ignored on Windows
var r = !!TerminateProcess(child.process, 255);
cleanup(-1);
return r;
},
wait: function() {
// wait for async operations to complete
var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
while (!done) thread.processNextEvent(true);
return exitCode;
}
};
}
function subprocess_unix(options) {
var libc = ctypes.open(options.libc),
active = true,
done = false,
exitCode = -1,
workerExitCode = 0,
child = {},
pid = -1,
writeWorker = [],
stdoutWorker = null,
stderrWorker = null,
readFdWorker = [],
pendingWriteCount = 0,
readers = options.mergeStderr ? 1 : 2,
stdinOpenState = [ PIPE_STATE_NOT_INIT ],
error = '',
output = '';
//api declarations
//pid_t fork(void);
var fork = libc.declare("fork",
ctypes.default_abi,
pid_t
);
//NULL terminated array of strings, argv[0] will be command >> + 2
var argv = ctypes.char.ptr.array(options.arguments.length + 2);
var envp = ctypes.char.ptr.array(options.environment.length + 1);
// posix_spawn_file_actions_t is a complex struct that may be different on
// each platform. We do not care about its attributes, we don't need to
// get access to them, but we do need to allocate the right amount
// of memory for it.
// At 2013/10/28, its size was 80 on linux, but better be safe (and larger),
// than crash when posix_spawn_file_actions_init fill `action` with zeros.
// Use `gcc sizeof_fileaction.c && ./a.out` to check that size.
var posix_spawn_file_actions_t = ctypes.uint8_t.array(100);
//int posix_spawn(pid_t *restrict pid, const char *restrict path,
// const posix_spawn_file_actions_t *file_actions,
// const posix_spawnattr_t *restrict attrp,
// char *const argv[restrict], char *const envp[restrict]);
var posix_spawn = libc.declare("posix_spawn",
ctypes.default_abi,
ctypes.int,
pid_t.ptr,
ctypes.char.ptr,
posix_spawn_file_actions_t.ptr,
ctypes.voidptr_t,
argv,
envp
);
//int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init",
ctypes.default_abi,
ctypes.int,
posix_spawn_file_actions_t.ptr
);
//int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy",
ctypes.default_abi,
ctypes.int,
posix_spawn_file_actions_t.ptr
);
// int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *
// file_actions, int fildes, int newfildes);
var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2",
ctypes.default_abi,
ctypes.int,
posix_spawn_file_actions_t.ptr,
ctypes.int,
ctypes.int
);
// int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *
// file_actions, int fildes);
var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose",
ctypes.default_abi,
ctypes.int,
posix_spawn_file_actions_t.ptr,
ctypes.int
);
//int pipe(int pipefd[2]);
var pipefd = ctypes.int.array(2);
var pipe = libc.declare("pipe",
ctypes.default_abi,
ctypes.int,
pipefd
);
//int dup2(int oldfd, int newfd);
var dup2 = libc.declare("dup2",
ctypes.default_abi,
ctypes.int,
ctypes.int,
ctypes.int
);
//int close(int fd);
var close = libc.declare("close",
ctypes.default_abi,
ctypes.int,
ctypes.int
);
var execve = libc.declare("execve",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr,
argv,
envp
);
//void exit(int status);
var exit = libc.declare("exit",
ctypes.default_abi,
ctypes.void_t,
ctypes.int
);
//pid_t waitpid(pid_t pid, int *status, int options);
var waitpid = libc.declare("waitpid",
ctypes.default_abi,
pid_t,
pid_t,
ctypes.int.ptr,
ctypes.int
);
//int kill(pid_t pid, int sig);
var kill = libc.declare("kill",
ctypes.default_abi,
ctypes.int,
pid_t,
ctypes.int
);
//int read(int fd, void *buf, size_t count);
var bufferSize = 1024;
var buffer = ctypes.char.array(bufferSize);
var read = libc.declare("read",
ctypes.default_abi,
ctypes.int,
ctypes.int,
buffer,
ctypes.int
);
var WriteBuffer = ctypes.uint8_t.array(256);
//ssize_t write(int fd, const void *buf, size_t count);
var write = libc.declare("write",
ctypes.default_abi,
ctypes.int,
ctypes.int,
WriteBuffer,
ctypes.int
);
//int chdir(const char *path);
var chdir = libc.declare("chdir",
ctypes.default_abi,
ctypes.int,
ctypes.char.ptr
);
//int sleep(int);
var sleep = libc.declare("sleep",
ctypes.default_abi,
ctypes.int,
ctypes.int);
//int fcntl(int fd, int cmd, ... /* arg */ );
var fcntl = libc.declare("fcntl",
ctypes.default_abi,
ctypes.int,
ctypes.int,
ctypes.int,
ctypes.int
);
var libcWrapper = null,
launchProcess = null;
var additionalFds = 0;
if ( options.pipes ) {
additionalFds = options.pipes.length;
}
var fdArr = ctypes.int.array(additionalFds + 2);
function popen(command, workdir, args, environment, child) {
var _in,
_out,
_err,
pid,
rc,
i;
_in = new pipefd();
_out = new pipefd();
if(!options.mergeStderr)
_err = new pipefd();
var _args = argv();
args.unshift(command);
for(i=0;i<args.length;i++) {
_args[i] = ctypes.char.array()(args[i]);
}
var _envp = envp();
for(i=0;i<environment.length;i++) {
_envp[i] = ctypes.char.array()(environment[i]);
}
rc = pipe(_in);
if (rc < 0) {
return -1;
}
rc = pipe(_out);
fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK));
if (rc < 0) {
close(_in[0]);
close(_in[1]);
return -1;
}
if(!options.mergeStderr) {
rc = pipe(_err);
fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK));
if (rc < 0) {
close(_in[0]);
close(_in[1]);
close(_out[0]);
close(_out[1]);
return -1;
}
}
child.otherFdChild = fdArr(); // FD's to use in the subprocess (close in parent)
child.otherFdParent = fdArr(); // FD's to use in the parent process (close in child)
if (additionalFds > 0) {
debugLog("adding Fds: " + additionalFds + "\n");
for (i=0; i < additionalFds; i++) {
var fd = new pipefd();
rc = pipe(fd);
if (rc < 0) {
close(_in[0]);
close(_in[1]);
close(_out[0]);
close(_out[1]);
return -1;
}
- if (options.pipes[i].readFd !== undefined) {
+ if (options.pipes[i].readFd) {
debugLog("adding input fd: " +fd[1] + "\n");
child.otherFdChild[i] = fd[1];
child.otherFdParent[i] = fd[0];
}
- else if (options.pipes[i].writeFd !== undefined) {
+ else if (options.pipes[i].writeFd) {
debugLog("adding output fd: " +fd[0] + "\n");
child.otherFdChild[i] = fd[0];
child.otherFdParent[i] = fd[1];
}
}
}
child.otherFdChild[additionalFds] = 0;
let STDIN_FILENO = 0;
let STDOUT_FILENO = 1;
let STDERR_FILENO = 2;
let action = posix_spawn_file_actions_t();
posix_spawn_file_actions_init(action.address());
posix_spawn_file_actions_adddup2(action.address(), _in[0], STDIN_FILENO);
posix_spawn_file_actions_addclose(action.address(), _in[1]);
posix_spawn_file_actions_addclose(action.address(), _in[0]);
posix_spawn_file_actions_adddup2(action.address(), _out[1], STDOUT_FILENO);
posix_spawn_file_actions_addclose(action.address(), _out[1]);
posix_spawn_file_actions_addclose(action.address(), _out[0]);
if (!options.mergeStderr) {
posix_spawn_file_actions_adddup2(action.address(), _err[1], STDERR_FILENO);
posix_spawn_file_actions_addclose(action.address(), _err[1]);
posix_spawn_file_actions_addclose(action.address(), _err[0]);
}
// posix_spawn doesn't support setting a custom workdir for the child,
// so change the cwd in the parent process before launching the child process.
if (workdir) {
if (chdir(workdir) < 0) {
throw new Error("Unable to change workdir before launching child process");
}
}
closeOtherFds(action, _in[1], _out[0], options.mergeStderr ? undefined : _err[0]);
let id = pid_t(0);
let rv = posix_spawn(id.address(), command, action.address(), null, _args, _envp);
posix_spawn_file_actions_destroy(action.address());
if (rv !== 0) {
// we should not really end up here
if(!options.mergeStderr) {
close(_err[0]);
close(_err[1]);
}
close(_out[0]);
close(_out[1]);
close(_in[0]);
close(_in[1]);
throw new Error("Fatal - failed to create subprocess '"+command+"'");
}
pid = id.value;
close(_in[0]);
close(_out[1]);
if (!options.mergeStderr)
close(_err[1]);
child.stdin = _in[1];
child.stdout = _out[0];
child.stderr = options.mergeStderr ? undefined : _err[0];
child.pid = pid;
return pid;
}
// close any file descriptors that are not required for the process
function closeOtherFds(action, fdIn, fdOut, fdErr, otherFd, additionalFds) {
// Unfortunately on mac, any fd registered in posix_spawn_file_actions_addclose
// that can't be closed correctly will make posix_spawn fail...
// Even if we ensure registering only still opened fds.
if (gXulRuntime.OS == "Darwin")
return;
var maxFD = 256; // arbitrary max
var rlim_t = getPlatformValue(RLIM_T);
const RLIMITS = new ctypes.StructType("RLIMITS", [
{"rlim_cur": rlim_t},
{"rlim_max": rlim_t}
]);
try {
var getrlimit = libc.declare("getrlimit",
ctypes.default_abi,
ctypes.int,
ctypes.int,
RLIMITS.ptr
);
var rl = new RLIMITS();
if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) === 0) {
if (rl.rlim_cur < Math.pow(2,20)) // ignore too high numbers
maxFD = rl.rlim_cur;
}
debugLog("getlimit: maxFD="+maxFD+"\n");
}
catch(ex) {
debugLog("getrlimit: no such function on this OS\n");
debugLog(ex.toString());
}
// close any file descriptors
// fd's 0-2 + additional FDs are already closed
for (var i = 3 + additionalFds; i < maxFD; i++) {
let doClose = true;
if (i != fdIn && i != fdOut && i != fdErr) {
for (var j = 0; j < additionalFds; j++) {
if (i == otherFd[j]) doClose = false;
}
if (doClose) {
posix_spawn_file_actions_addclose(action.address(), i);
}
}
}
}
/*
* createWriter ()
*
* Create a ChromeWorker object for writing data to the subprocess' stdin
* pipe. The ChromeWorker object lives on a separate thread; this avoids
* internal deadlocks.
*/
function createWriter(fileDesc, workerNum) {
debugLog("Creating new writing worker " + workerNum +" for pipe "+ fileDesc + "\n");
let wrk = new ChromeWorker("subprocess_worker_unix.js");
wrk.onmessage = function(event) {
switch (event.data.msg) {
case "info":
switch(event.data.data) {
case "WriteOK":
pendingWriteCount--;
debugLog("got OK from writing Worker "+ workerNum +" - remaining count: "+pendingWriteCount+"\n");
break;
case "InitOK":
stdinOpenState[workerNum] = PIPE_STATE_OPEN;
debugLog("write pipe "+ workerNum +" opened\n");
break;
case "ClosedOK":
stdinOpenState[workerNum] = PIPE_STATE_CLOSED;
debugLog("write pipe "+ workerNum +" closed\n");
break;
default:
debugLog("got msg from write Worker: "+event.data.data+"\n");
}
break;
case "debug":
debugLog("write Worker "+ workerNum +": "+event.data.data+"\n");
break;
case "error":
LogError("got error from write Worker "+ workerNum +": "+event.data.data+"\n");
pendingWriteCount = 0;
stdinOpenState[workerNum] = PIPE_STATE_CLOSED;
exitCode = -2;
}
};
wrk.onerror = function(error) {
pendingWriteCount = 0;
exitCode = -2;
closeWriteHandle(wrk);
LogError("got error from write Worker "+ workerNum +": "+error.message+"\n");
};
var pipePtr = parseInt(fileDesc);
wrk.postMessage({
msg: "init",
libc: options.libc,
pipe: pipePtr
});
return wrk;
}
/*
* writeToPipe()
* @writeWorker: worker object that processes the data
* @data: String containing the data to write
*
* Write data to the subprocess' stdin (equals to sending a request to the
* ChromeWorker object to write the data).
*/
function writeToPipe(workerNum, data) {
if (stdinOpenState[workerNum] == PIPE_STATE_CLOSED) {
LogError("trying to write data to closed stdin");
return;
}
++pendingWriteCount;
debugLog("sending "+data.length+" bytes to writing Worker "+workerNum+"\n");
writeWorker[workerNum].postMessage({
msg: 'write',
data: data
});
}
/*
* closeStdinHandle()
*
* Close the stdin pipe, either directly or by requesting the ChromeWorker to
* close the pipe. The ChromeWorker will only close the pipe after the last write
* request process is done.
*/
function closeWriteHandle(workerNum) {
debugLog("trying to close input pipe for worker "+workerNum+"\n");
if (stdinOpenState[workerNum] != PIPE_STATE_OPEN) return;
stdinOpenState[workerNum] = PIPE_STATE_CLOSEABLE;
if (writeWorker[workerNum]) {
debugLog("sending close stdin to worker "+workerNum+"\n");
writeWorker[workerNum].postMessage({
msg: 'close'
});
}
else {
stdinOpenState[workerNum] = PIPE_STATE_CLOSED;
debugLog("Closing Stdin for "+workerNum+"\n");
if (!workerNum)
if(close(child.stdin)) LogError("CloseHandle stdin failed");
else {
let wrk = 0;
for (let i = 0; i < options.pipes.length; i++) {
- if (options.pipes[i].writeFd !== undefined) {
+ if (options.pipes[i].writeFd) {
++wrk;
if (wrk == workerNum) {
if(close(child.writeFdParent[i])) LogError("CloseHandle stdin failed");
}
}
}
}
}
}
/*
* createReader(pipe, name, callbackFunc)
*
* @pipe: handle to the pipe
* @name: String containing the pipe name (stdout or stderr)
* @callbackFunc: function to be called with the read data
*
* Create a ChromeWorker object for reading data asynchronously from
* the pipe (i.e. on a separate thread), and passing the result back to
* the caller.
*
*/
function createReader(pipe, name, callbackFunc) {
debugLog("Opening pipe: "+pipe+"\n");
var worker = new ChromeWorker("subprocess_worker_unix.js");
worker.onmessage = function(event) {
switch(event.data.msg) {
case "data":
debugLog("got "+event.data.count+" bytes from "+name+"\n");
var data = convertBytes(event.data.data, options.charset);
callbackFunc(data);
break;
case "done":
debugLog("Pipe "+name+" closed\n");
if (event.data.data !== 0) workerExitCode = event.data.data;
--readers;
if (readers === 0) cleanup();
break;
case "error":
LogError("Got error from "+name+": "+event.data.data);
exitCode = -2;
break;
default:
debugLog("Got msg from "+name+": "+event.data.data+"\n");
}
};
worker.onerror = function(error) {
LogError("Got error from "+name+": "+error.message);
exitCode = -2;
};
worker.postMessage({
msg: 'read',
pipe: pipe,
pid: pid,
libc: options.libc,
- charset: options.charset === null ? "null" : options.charset,
+ charset: !options.charset ? "null" : options.charset,
bufferedOutput: options.bufferedOutput,
name: name
});
return worker;
}
/*
* readPipes()
*
* Open the pipes for reading from stdout and stderr
*/
function readPipes() {
stdoutWorker = createReader(child.stdout, "stdout", function (data) {
if(options.stdout) {
setTimeout(function() {
options.stdout(data);
}, 0);
} else {
output += data;
}
});
if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
if(options.stderr) {
setTimeout(function() {
options.stderr(data);
}, 0);
} else {
error += data;
}
});
function flusher(pipe) {
return function(data) {
setTimeout(function() {
pipe.readFd(data);
}, 0);
};
}
if (options.pipes) {
for (let i = 0; i < options.pipes.length; i++) {
if (typeof(options.pipes[i].readFd) == "function") {
let pipe = options.pipes[i];
let wrk = createReader(child.otherFdParent[i], "fd_"+ (i+3) , flusher(pipe));
readFdWorker.push(wrk);
}
}
}
}
function cleanup() {
debugLog("Cleanup called\n");
var i;
if (active) {
active = false;
for (i=0; i < writeWorker.length; i++) {
if (writeWorker[i])
closeWriteHandle(i); // should only be required in case of errors
}
var result, status = ctypes.int();
result = waitpid(child.pid, status.address(), 0);
if (exitCode > -2) {
if (result > 0)
exitCode = status.value;
else
if (workerExitCode >= 0)
exitCode = workerExitCode;
else
exitCode = status.value;
}
exitCode = exitCode % 0xFF;
for (i=0; i < writeWorker.length; i++) {
if (writeWorker[i])
writeWorker[i].postMessage({msg: 'stop'});
}
setTimeout(function _done() {
if (options.done) {
try {
options.done({
exitCode: exitCode,
stdout: output,
stderr: error
});
}
catch(ex) {
// prevent from blocking if options.done() throws an error
done = true;
throw ex;
}
}
done = true;
}, 0);
libc.close();
}
}
/**
* Start wrinting on a pipe. The corresponding worker needs to exist.
* @workerNum: Number of the worker (0 = stdin)
* @pipeWriteFunc: Function or String that writes data to the pipe
*/
function startWriting(workerNum, pipeWriteFunc) {
debugLog("startWriting called for " + workerNum + "\n");
if (stdinOpenState[workerNum] == PIPE_STATE_NOT_INIT) {
setTimeout(function _f() {
startWriting(workerNum, pipeWriteFunc);
}, 2 );
return;
}
if (typeof(pipeWriteFunc) == 'function') {
try {
pipeWriteFunc({
write: function(data) {
writeToPipe(workerNum, data);
},
close: function() {
closeWriteHandle(workerNum);
}
});
}
catch(ex) {
// prevent from failing if options.stdin() throws an exception
closeWriteHandle(workerNum);
throw ex;
}
} else {
debugLog("writing <" + pipeWriteFunc +"> to " + workerNum + "\n");
writeToPipe(workerNum, pipeWriteFunc);
closeWriteHandle(workerNum);
}
}
//main
var cmdStr = getCommandStr(options.command);
var workDir = getWorkDir(options.workdir);
child = {};
pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
debugLog("subprocess started; got PID "+pid+"\n");
readPipes();
var workerNum = 0;
if (options.stdin) {
writeWorker[0] = createWriter(child.stdin, 0);
startWriting(0, options.stdin);
++workerNum;
}
else
closeWriteHandle(0);
if (options.pipes) {
for (let i = 0; i < options.pipes.length; i++) {
- if (options.pipes[i].writeFd !== undefined) {
+ if (options.pipes[i].writeFd) {
stdinOpenState.push(PIPE_STATE_NOT_INIT);
writeWorker.push(createWriter(child.otherFdParent[i], workerNum));
startWriting(workerNum, options.pipes[i].writeFd);
++workerNum;
}
}
}
return {
wait: function() {
// wait for async operations to complete
var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
while (! done) thread.processNextEvent(true);
return exitCode;
},
kill: function(hardKill) {
if (!active) return true;
var rv = kill(pid, (hardKill ? 9: 15));
cleanup(-1);
return rv;
}
};
}
diff --git a/ipc/modules/subprocess_worker_unix.js b/ipc/modules/subprocess_worker_unix.js
index 51f9635a..fc37ef1c 100644
--- a/ipc/modules/subprocess_worker_unix.js
+++ b/ipc/modules/subprocess_worker_unix.js
@@ -1,393 +1,393 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "MPL"); you may not use this file
* except in compliance with the MPL. You may obtain a copy of
* the MPL at http://www.mozilla.org/MPL/
*
* Software distributed under the MPL is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the MPL for the specific language governing
* rights and limitations under the MPL.
*
* The Original Code is subprocess.jsm.
*
* The Initial Developer of this code is Patrick Brunschwig.
* Portions created by Patrick Brunschwig <patrick@enigmail.net>
* are Copyright (C) 2011 Patrick Brunschwig.
* All Rights Reserved.
*
* Contributor(s):
* Jan Gerber <j@mailb.org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
* ***** END LICENSE BLOCK ***** */
/*
* ChromeWorker Object subprocess.jsm on Unix-like systems (Linux, Mac OS X, ...)
* to process stdin/stdout/stderr on separate threads.
*
*/
// Being a ChromeWorker object, implicitly uses the following:
// Components.utils.import("resource://gre/modules/ctypes.jsm");
const BufferSize = 1024;
const MaxBufferLen = 102400;
var libc = null;
var libcFunc = {};
var WritePipeHandle = null;
/*
struct pollfd {
int fd; // file descriptor
short events; // events to look for
short revents; // events returned
};
*/
var pollfd = new ctypes.StructType("pollfd",
[ {'fd': ctypes.int},
{'events': ctypes.short},
{'revents': ctypes.short}
]);
var WriteBuffer = ctypes.uint8_t.array(BufferSize);
var ReadBuffer = ctypes.uint8_t.array(BufferSize);
const POLLIN = 0x0001;
const POLLOUT = 0x0004;
const POLLERR = 0x0008; // some poll error occurred
const POLLHUP = 0x0010; // file descriptor was "hung up"
const POLLNVAL = 0x0020; // requested events "invalid"
const WNOHANG = 0x01;
const ECHILD = 10;
const EPIPE = 32; /* Broken pipe */
const EAGAIN = 35; /* Resource temporarily unavailable */
const EINPROGRESS = 36; /* Operation now in progress */
const EALREADY = 37; /* Operation already in progress */
const pid_t = ctypes.int32_t;
const INDEFINITE = -1;
const NOWAIT = 0;
const WAITTIME = 200; // wait time for poll() in ms
function initLibc(libName) {
postMessage({msg: "debug", data: "initialising library with "+ libName});
libc = ctypes.open(libName);
libcFunc.pollFds = pollfd.array(1);
// int poll(struct pollfd fds[], nfds_t nfds, int timeout);
libcFunc.poll = libc.declare("poll",
ctypes.default_abi,
ctypes.int,
libcFunc.pollFds,
ctypes.unsigned_int,
ctypes.int);
//ssize_t write(int fd, const void *buf, size_t count);
// NOTE: buf is declared as array of unsigned int8 instead of char to avoid
// implicit charset conversion
libcFunc.write = libc.declare("write",
ctypes.default_abi,
ctypes.int,
ctypes.int,
WriteBuffer,
ctypes.int);
//int read(int fd, void *buf, size_t count);
libcFunc.read = libc.declare("read",
ctypes.default_abi,
ctypes.int,
ctypes.int,
ReadBuffer,
ctypes.int);
//int pipe(int pipefd[2]);
libcFunc.pipefd = ctypes.int.array(2);
//int close(int fd);
libcFunc.close = libc.declare("close",
ctypes.default_abi,
ctypes.int,
ctypes.int);
//pid_t waitpid(pid_t pid, int *status, int options);
libcFunc.waitpid = libc.declare("waitpid",
ctypes.default_abi,
pid_t,
pid_t,
ctypes.int.ptr,
ctypes.int);
}
function createNpeError() {
let e = new Error("NULL Poiner", "npeError", 1);
try {
// throw an error and catch it to get a stack trace
throw e;
}
catch (ex) {
postMessage({msg: "error", data: "Got NULL pointer error\n" + ex.stack});
}
return;
}
function closePipe(pipe) {
- if (pipe === null) {
+ if (!pipe) {
createNpeError();
return;
}
libcFunc.close(pipe);
}
function writePipe(pipe, data) {
postMessage({msg: "debug", data: "trying to write to "+pipe});
let numChunks = Math.floor(data.length / BufferSize);
let pData = new WriteBuffer();
for (var chunk = 0; chunk <= numChunks; chunk ++) {
let numBytes = chunk < numChunks ? BufferSize : data.length - chunk * BufferSize;
for (var i=0; i < numBytes; i++) {
pData[i] = data.charCodeAt(chunk * BufferSize + i) % 256;
}
let bytesWritten = libcFunc.write(pipe, pData, numBytes);
if (bytesWritten != numBytes) {
postMessage({ msg: "error", data: "error: write failed, errno=" + ctypes.errno });
closePipe(pipe);
libc.close();
close();
}
}
postMessage({msg: "info", data: "wrote "+data.length+" bytes of data"});
}
function readString(data, length, charset) {
var r = '';
for(var i = 0; i < length; i++) {
if(data[i] === 0 && charset != "null") // stop on NULL character for non-binary data
break;
r += String.fromCharCode(data[i]);
}
// For non-UTF-8 strings, the next read always starts at the beginning.
data[0] = 0;
return r;
}
function readUtf8(data, length) {
// This function provides better performance for UTF-8 strings by using
// the readStringReplaceMalformed() method available on CData string
// objects. Before we can call it, though, we have to check the end of
// the string to see if we only read part of a multi-byte character.
var endChar = [];
if (data[length - 1] >= 0x80) {
// Collect all bytes from the last character if it's a non-ASCII.
for (let i = length - 1; i >= 0; i--) {
endChar.unshift(data[i]);
if (data[i] >= 0xc0) break;
}
// Find out how long the character should be from the first byte.
var leadingOne = 0x20;
var numBytes = 2;
while (endChar[0] & leadingOne) {
numBytes++;
leadingOne >>= 1;
}
// If we read the full character, we don't need to do anything special.
if (endChar.length == numBytes) endChar = [];
}
// Mark the end of the string, excluding any trailing partial character.
data[length - endChar.length] = 0;
var r = data.readStringReplaceMalformed();
// Place the partial character at the beginning for the next read.
let i = 0;
endChar.forEach(function (v) {
data[i++] = v;
});
// Place a null character to mark where the next read should start.
data[i] = 0;
return r;
}
function readPipe(pipe, charset, pid, bufferedOutput) {
var p = new libcFunc.pollFds();
p[0].fd = pipe;
p[0].events = POLLIN | POLLERR | POLLHUP;
p[0].revents = 0;
var pollTimeout = WAITTIME;
var exitCode = -1;
var readCount = 0;
var result, status = ctypes.int();
result = 0;
var dataStr = "";
var dataObj = {};
var line = new ReadBuffer();
const i=0;
while (true) {
if (result === 0) {
result = libcFunc.waitpid(pid, status.address(), WNOHANG);
if (result > 0) {
pollTimeout = NOWAIT;
exitCode = parseInt(status.value);
postMessage({msg: "debug", data: "waitpid signaled subprocess stop, exitcode="+status.value });
}
else if (result < 0) {
postMessage({msg: "debug", data: "waitpid returned with errno="+ctypes.errno });
if (ctypes.errno == ECHILD) {
pollTimeout = NOWAIT;
}
}
}
p[i].revents = 0;
var r = libcFunc.poll(p, 1, pollTimeout);
if (pollTimeout == NOWAIT) {
readCount = 0;
}
if (r > 0) {
if (p[i].revents & POLLIN) {
// postMessage({msg: "debug", data: "reading next chunk"});
readCount = readPolledFd(p[i].fd, line, charset, dataObj);
if (! bufferedOutput)
postMessage({msg: "data", data: dataObj.value, count: dataObj.value.length});
else {
dataStr += dataObj.value;
if (dataStr.length > MaxBufferLen) {
postMessage({msg: "data", data: dataStr, count: dataStr.length});
dataStr = "";
}
}
if (readCount === 0) break;
}
if (p[i].revents & POLLHUP) {
postMessage({msg: "debug", data: "poll returned HUP"});
break;
}
else if (p[i].revents & POLLERR) {
postMessage({msg: "error", data: "poll returned error"});
break;
}
else if (p[i].revents != POLLIN) {
postMessage({msg: "error", data: "poll returned "+p[i]});
break;
}
}
else
if (pollTimeout == NOWAIT || r < 0) break;
}
// continue reading until the buffer is empty
while (readCount > 0) {
readCount = readPolledFd(pipe, line, charset, dataObj);
if (! bufferedOutput)
postMessage({msg: "data", data: dataObj.value, count: dataObj.value.length});
else
dataStr += dataObj.value;
libcFunc.poll(p, 1, NOWAIT);
}
if (bufferedOutput)
postMessage({msg: "data", data: dataStr, count: dataStr.length});
libcFunc.close(pipe);
postMessage({msg: "done", data: exitCode });
libc.close();
close();
}
function readPolledFd(pipe, line, charset, dataObj) {
// Start reading at first null byte (line might begin with an
// incomplete UTF-8 character from the previous read).
var offset = 0;
while (line[offset] !== 0) offset++;
var r = libcFunc.read(pipe, line.addressOfElement(offset), BufferSize-offset-1);
if (r > 0) {
var readStringFunc = charset == "UTF-8" ? readUtf8 : readString;
var c = readStringFunc(line, r + offset, charset);
dataObj.value = c;
}
else
dataObj.value = "";
return r;
}
onmessage = function (event) {
switch (event.data.msg) {
case "init":
- if (event.data.pipe === null) {
+ if (!event.data.pipe) {
createNpeError();
return;
}
WritePipeHandle = event.data.pipe;
initLibc(event.data.libc);
postMessage({msg: "info", data: "InitOK"});
break;
case "read":
- if (event.data.pipe === null) {
+ if (!event.data.pipe) {
createNpeError();
return;
}
initLibc(event.data.libc);
readPipe(event.data.pipe, event.data.charset, event.data.pid, event.data.bufferedOutput);
break;
case "write":
// data contents:
// msg: 'write'
// data: the data (string) to write
// pipe: ptr to pipe
writePipe(WritePipeHandle, event.data.data);
postMessage({msg: "info", data: "WriteOK"});
break;
case "close":
postMessage({msg: "debug", data: "closing input pipe\n"});
closePipe(WritePipeHandle);
postMessage({msg: "info", data: "ClosedOK"});
break;
case "stop":
libc.close(); // do not use libc after this point
close();
break;
default:
throw("error: Unknown command"+event.data.msg+"\n");
}
return;
};
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Jul 8, 12:19 PM (9 h, 40 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0a/e3/97c6ba702705539d411ac8beb67d
Attached To
rENIG Enigmail
Event Timeline
Log In to Comment