michael@0: // -*- coding: utf-8 -*- michael@0: // vim: et:ts=4:sw=4:sts=4:ft=javascript michael@0: /* ***** BEGIN LICENSE BLOCK ***** michael@0: * Version: MPL 1.1/GPL 2.0/LGPL 2.1 michael@0: * michael@0: * The contents of this file are subject to the Mozilla Public michael@0: * License Version 1.1 (the "MPL"); you may not use this file michael@0: * except in compliance with the MPL. You may obtain a copy of michael@0: * the MPL at http://www.mozilla.org/MPL/ michael@0: * michael@0: * Software distributed under the MPL is distributed on an "AS michael@0: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or michael@0: * implied. See the MPL for the specific language governing michael@0: * rights and limitations under the MPL. michael@0: * michael@0: * The Original Code is subprocess.jsm. michael@0: * michael@0: * The Initial Developer of this code is Jan Gerber. michael@0: * Portions created by Jan Gerber michael@0: * are Copyright (C) 2011 Jan Gerber. michael@0: * All Rights Reserved. michael@0: * michael@0: * Contributor(s): michael@0: * Patrick Brunschwig michael@0: * michael@0: * Alternatively, the contents of this file may be used under the terms of michael@0: * either the GNU General Public License Version 2 or later (the "GPL"), or michael@0: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), michael@0: * in which case the provisions of the GPL or the LGPL are applicable instead michael@0: * of those above. If you wish to allow use of your version of this file only michael@0: * under the terms of either the GPL or the LGPL, and not to allow others to michael@0: * use your version of this file under the terms of the MPL, indicate your michael@0: * decision by deleting the provisions above and replace them with the notice michael@0: * and other provisions required by the GPL or the LGPL. If you do not delete michael@0: * the provisions above, a recipient may use your version of this file under michael@0: * the terms of any one of the MPL, the GPL or the LGPL. michael@0: * ***** END LICENSE BLOCK ***** */ michael@0: michael@0: /* michael@0: * This object allows to start a process, and read/write data to/from it michael@0: * using stdin/stdout/stderr streams. michael@0: * Usage example: michael@0: * michael@0: * var p = subprocess.call({ michael@0: * command: '/bin/foo', michael@0: * arguments: ['-v', 'foo'], michael@0: * environment: [ "XYZ=abc", "MYVAR=def" ], michael@0: * charset: 'UTF-8', michael@0: * workdir: '/home/foo', michael@0: * //stdin: "some value to write to stdin\nfoobar", michael@0: * stdin: function(stdin) { michael@0: * stdin.write("some value to write to stdin\nfoobar"); michael@0: * stdin.close(); michael@0: * }, michael@0: * stdout: function(data) { michael@0: * dump("got data on stdout:" + data + "\n"); michael@0: * }, michael@0: * stderr: function(data) { michael@0: * dump("got data on stderr:" + data + "\n"); michael@0: * }, michael@0: * done: function(result) { michael@0: * dump("process terminated with " + result.exitCode + "\n"); michael@0: * }, michael@0: * mergeStderr: false michael@0: * }); michael@0: * p.wait(); // wait for the subprocess to terminate michael@0: * // this will block the main thread, michael@0: * // only do if you can wait that long michael@0: * michael@0: * michael@0: * Description of parameters: michael@0: * -------------------------- michael@0: * Apart from , all arguments are optional. michael@0: * michael@0: * command: either a |nsIFile| object pointing to an executable file or a michael@0: * String containing the platform-dependent path to an executable michael@0: * file. michael@0: * michael@0: * arguments: optional string array containing the arguments to the command. michael@0: * michael@0: * environment: optional string array containing environment variables to pass michael@0: * to the command. The array elements must have the form michael@0: * "VAR=data". Please note that if environment is defined, it michael@0: * replaces any existing environment variables for the subprocess. michael@0: * michael@0: * charset: Output is decoded with given charset and a string is returned. michael@0: * If charset is undefined, "UTF-8" is used as default. michael@0: * To get binary data, set this to null and the returned string michael@0: * is not decoded in any way. michael@0: * michael@0: * workdir: optional; String containing the platform-dependent path to a michael@0: * directory to become the current working directory of the subprocess. michael@0: * michael@0: * stdin: optional input data for the process to be passed on standard michael@0: * input. stdin can either be a string or a function. michael@0: * A |string| gets written to stdin and stdin gets closed; michael@0: * A |function| gets passed an object with write and close function. michael@0: * Please note that the write() function will return almost immediately; michael@0: * data is always written asynchronously on a separate thread. michael@0: * michael@0: * stdout: an optional function that can receive output data from the michael@0: * process. The stdout-function is called asynchronously; it can be michael@0: * called mutliple times during the execution of a process. michael@0: * At a minimum at each occurance of \n or \r. michael@0: * Please note that null-characters might need to be escaped michael@0: * with something like 'data.replace(/\0/g, "\\0");'. michael@0: * michael@0: * stderr: an optional function that can receive stderr data from the michael@0: * process. The stderr-function is called asynchronously; it can be michael@0: * called mutliple times during the execution of a process. Please michael@0: * note that null-characters might need to be escaped with michael@0: * something like 'data.replace(/\0/g, "\\0");'. michael@0: * (on windows it only gets called once right now) michael@0: * michael@0: * done: optional function that is called when the process has terminated. michael@0: * The exit code from the process available via result.exitCode. If michael@0: * stdout is not defined, then the output from stdout is available michael@0: * via result.stdout. stderr data is in result.stderr michael@0: * michael@0: * mergeStderr: optional boolean value. If true, stderr is merged with stdout; michael@0: * no data will be provided to stderr. michael@0: * michael@0: * michael@0: * Description of object returned by subprocess.call(...) michael@0: * ------------------------------------------------------ michael@0: * The object returned by subprocess.call offers a few methods that can be michael@0: * executed: michael@0: * michael@0: * wait(): waits for the subprocess to terminate. It is not required to use michael@0: * wait; done will be called in any case when the subprocess terminated. michael@0: * michael@0: * kill(hardKill): kill the subprocess. Any open pipes will be closed and michael@0: * done will be called. michael@0: * hardKill [ignored on Windows]: michael@0: * - false: signal the process terminate (SIGTERM) michael@0: * - true: kill the process (SIGKILL) michael@0: * michael@0: * michael@0: * Other methods in subprocess michael@0: * --------------------------- michael@0: * michael@0: * registerDebugHandler(functionRef): register a handler that is called to get michael@0: * debugging information michael@0: * registerLogHandler(functionRef): register a handler that is called to get error michael@0: * messages michael@0: * michael@0: * example: michael@0: * subprocess.registerLogHandler( function(s) { dump(s); } ); michael@0: */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const { Cc, Ci, Cu, ChromeWorker } = require("chrome"); michael@0: michael@0: Cu.import("resource://gre/modules/ctypes.jsm"); michael@0: michael@0: const NS_LOCAL_FILE = "@mozilla.org/file/local;1"; michael@0: michael@0: const Runtime = require("sdk/system/runtime"); michael@0: const Environment = require("sdk/system/environment").env; michael@0: const DEFAULT_ENVIRONMENT = []; michael@0: if (Runtime.OS == "Linux" && "DISPLAY" in Environment) { michael@0: DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY); michael@0: } michael@0: michael@0: /* michael@0: Fake require statements to ensure worker scripts are packaged: michael@0: require("./subprocess_worker_win.js"); michael@0: require("./subprocess_worker_unix.js"); michael@0: */ michael@0: const URL_PREFIX = module.uri.replace(/subprocess\.js/, ""); michael@0: const WORKER_URL_WIN = URL_PREFIX + "subprocess_worker_win.js"; michael@0: const WORKER_URL_UNIX = URL_PREFIX + "subprocess_worker_unix.js"; michael@0: michael@0: //Windows API definitions michael@0: if (ctypes.size_t.size == 8) { michael@0: var WinABI = ctypes.default_abi; michael@0: } else { michael@0: var WinABI = ctypes.winapi_abi; michael@0: } michael@0: const WORD = ctypes.uint16_t; michael@0: const DWORD = ctypes.uint32_t; michael@0: const LPDWORD = DWORD.ptr; michael@0: michael@0: const UINT = ctypes.unsigned_int; michael@0: const BOOL = ctypes.bool; michael@0: const HANDLE = ctypes.size_t; michael@0: const HWND = HANDLE; michael@0: const HMODULE = HANDLE; michael@0: const WPARAM = ctypes.size_t; michael@0: const LPARAM = ctypes.size_t; michael@0: const LRESULT = ctypes.size_t; michael@0: const ULONG_PTR = ctypes.uintptr_t; michael@0: const PVOID = ctypes.voidptr_t; michael@0: const LPVOID = PVOID; michael@0: const LPCTSTR = ctypes.jschar.ptr; michael@0: const LPCWSTR = ctypes.jschar.ptr; michael@0: const LPTSTR = ctypes.jschar.ptr; michael@0: const LPSTR = ctypes.char.ptr; michael@0: const LPCSTR = ctypes.char.ptr; michael@0: const LPBYTE = ctypes.char.ptr; michael@0: michael@0: const CREATE_NEW_CONSOLE = 0x00000010; michael@0: const CREATE_NO_WINDOW = 0x08000000; michael@0: const CREATE_UNICODE_ENVIRONMENT = 0x00000400; michael@0: const STARTF_USESHOWWINDOW = 0x00000001; michael@0: const STARTF_USESTDHANDLES = 0x00000100; michael@0: const SW_HIDE = 0; michael@0: const DUPLICATE_SAME_ACCESS = 0x00000002; michael@0: const STILL_ACTIVE = 259; michael@0: const INFINITE = DWORD(0xFFFFFFFF); michael@0: const WAIT_TIMEOUT = 0x00000102; michael@0: michael@0: /* michael@0: typedef struct _SECURITY_ATTRIBUTES { michael@0: DWORD nLength; michael@0: LPVOID lpSecurityDescriptor; michael@0: BOOL bInheritHandle; michael@0: } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; michael@0: */ michael@0: const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [ michael@0: {"nLength": DWORD}, michael@0: {"lpSecurityDescriptor": LPVOID}, michael@0: {"bInheritHandle": BOOL}, michael@0: ]); michael@0: michael@0: /* michael@0: typedef struct _STARTUPINFO { michael@0: DWORD cb; michael@0: LPTSTR lpReserved; michael@0: LPTSTR lpDesktop; michael@0: LPTSTR lpTitle; michael@0: DWORD dwX; michael@0: DWORD dwY; michael@0: DWORD dwXSize; michael@0: DWORD dwYSize; michael@0: DWORD dwXCountChars; michael@0: DWORD dwYCountChars; michael@0: DWORD dwFillAttribute; michael@0: DWORD dwFlags; michael@0: WORD wShowWindow; michael@0: WORD cbReserved2; michael@0: LPBYTE lpReserved2; michael@0: HANDLE hStdInput; michael@0: HANDLE hStdOutput; michael@0: HANDLE hStdError; michael@0: } STARTUPINFO, *LPSTARTUPINFO; michael@0: */ michael@0: const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [ michael@0: {"cb": DWORD}, michael@0: {"lpReserved": LPTSTR}, michael@0: {"lpDesktop": LPTSTR}, michael@0: {"lpTitle": LPTSTR}, michael@0: {"dwX": DWORD}, michael@0: {"dwY": DWORD}, michael@0: {"dwXSize": DWORD}, michael@0: {"dwYSize": DWORD}, michael@0: {"dwXCountChars": DWORD}, michael@0: {"dwYCountChars": DWORD}, michael@0: {"dwFillAttribute": DWORD}, michael@0: {"dwFlags": DWORD}, michael@0: {"wShowWindow": WORD}, michael@0: {"cbReserved2": WORD}, michael@0: {"lpReserved2": LPBYTE}, michael@0: {"hStdInput": HANDLE}, michael@0: {"hStdOutput": HANDLE}, michael@0: {"hStdError": HANDLE}, michael@0: ]); michael@0: michael@0: /* michael@0: typedef struct _PROCESS_INFORMATION { michael@0: HANDLE hProcess; michael@0: HANDLE hThread; michael@0: DWORD dwProcessId; michael@0: DWORD dwThreadId; michael@0: } PROCESS_INFORMATION, *LPPROCESS_INFORMATION; michael@0: */ michael@0: const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [ michael@0: {"hProcess": HANDLE}, michael@0: {"hThread": HANDLE}, michael@0: {"dwProcessId": DWORD}, michael@0: {"dwThreadId": DWORD}, michael@0: ]); michael@0: michael@0: /* michael@0: typedef struct _OVERLAPPED { michael@0: ULONG_PTR Internal; michael@0: ULONG_PTR InternalHigh; michael@0: union { michael@0: struct { michael@0: DWORD Offset; michael@0: DWORD OffsetHigh; michael@0: }; michael@0: PVOID Pointer; michael@0: }; michael@0: HANDLE hEvent; michael@0: } OVERLAPPED, *LPOVERLAPPED; michael@0: */ michael@0: const OVERLAPPED = new ctypes.StructType("OVERLAPPED"); michael@0: michael@0: //UNIX definitions michael@0: const pid_t = ctypes.int32_t; michael@0: const WNOHANG = 1; michael@0: const F_GETFD = 1; michael@0: const F_SETFL = 4; michael@0: michael@0: const LIBNAME = 0; michael@0: const O_NONBLOCK = 1; michael@0: const RLIM_T = 2; michael@0: const RLIMIT_NOFILE = 3; michael@0: michael@0: function getPlatformValue(valueType) { michael@0: michael@0: if (! gXulRuntime) michael@0: gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); michael@0: michael@0: const platformDefaults = { michael@0: // Windows API: michael@0: 'winnt': [ 'kernel32.dll' ], michael@0: michael@0: // Unix API: michael@0: // library name O_NONBLOCK RLIM_T RLIMIT_NOFILE michael@0: 'darwin': [ 'libc.dylib', 0x04 , ctypes.uint64_t , 8 ], michael@0: 'linux': [ 'libc.so.6', 2024 , ctypes.unsigned_long, 7 ], michael@0: 'freebsd': [ 'libc.so.7', 0x04 , ctypes.int64_t , 8 ], michael@0: 'openbsd': [ 'libc.so.61.0', 0x04 , ctypes.int64_t , 8 ], michael@0: 'sunos': [ 'libc.so', 0x80 , ctypes.unsigned_long, 5 ] michael@0: } michael@0: michael@0: return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType]; michael@0: } michael@0: michael@0: michael@0: var gDebugFunc = null, michael@0: gLogFunc = null, michael@0: gXulRuntime = null; michael@0: michael@0: function LogError(s) { michael@0: if (gLogFunc) michael@0: gLogFunc(s); michael@0: else michael@0: dump(s); michael@0: } michael@0: michael@0: function debugLog(s) { michael@0: if (gDebugFunc) michael@0: gDebugFunc(s); michael@0: } michael@0: michael@0: function setTimeout(callback, timeout) { michael@0: var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }; michael@0: michael@0: function getBytes(data) { michael@0: var string = ''; michael@0: data.forEach(function(x) { string += String.fromCharCode(x < 0 ? x + 256 : x) }); michael@0: return string; michael@0: } michael@0: michael@0: function readString(data, length, charset) { michael@0: var string = '', bytes = []; michael@0: for(var i = 0;i < length; i++) { michael@0: if(data[i] == 0 && charset !== null) // stop on NULL character for non-binary data michael@0: break michael@0: bytes.push(data[i]); michael@0: } michael@0: if (!bytes || bytes.length == 0) michael@0: return string; michael@0: if(charset === null) { michael@0: return bytes; michael@0: } michael@0: return convertBytes(bytes, charset); michael@0: } michael@0: michael@0: function convertBytes(bytes, charset) { michael@0: var string = ''; michael@0: charset = charset || 'UTF-8'; michael@0: var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .getService(Ci.nsIScriptableUnicodeConverter); michael@0: try { michael@0: unicodeConv.charset = charset; michael@0: string = unicodeConv.convertFromByteArray(bytes, bytes.length); michael@0: } catch (ex) { michael@0: LogError("String conversion failed: "+ex.toString()+"\n") michael@0: string = ''; michael@0: } michael@0: string += unicodeConv.Finish(); michael@0: return string; michael@0: } michael@0: michael@0: michael@0: // temporary solution for removal of nsILocalFile michael@0: function getLocalFileApi() { michael@0: if ("nsILocalFile" in Ci) { michael@0: return Ci.nsILocalFile; michael@0: } michael@0: else michael@0: return Ci.nsIFile; michael@0: } michael@0: michael@0: function getCommandStr(command) { michael@0: let commandStr = null; michael@0: if (typeof(command) == "string") { michael@0: let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi()); michael@0: file.initWithPath(command); michael@0: if (! (file.isExecutable() && file.isFile())) michael@0: throw("File '"+command+"' is not an executable file"); michael@0: commandStr = command; michael@0: } michael@0: else { michael@0: if (! (command.isExecutable() && command.isFile())) michael@0: throw("File '"+command.path+"' is not an executable file"); michael@0: commandStr = command.path; michael@0: } michael@0: michael@0: return commandStr; michael@0: } michael@0: michael@0: function getWorkDir(workdir) { michael@0: let workdirStr = null; michael@0: if (typeof(workdir) == "string") { michael@0: let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi()); michael@0: file.initWithPath(workdir); michael@0: if (! (file.isDirectory())) michael@0: throw("Directory '"+workdir+"' does not exist"); michael@0: workdirStr = workdir; michael@0: } michael@0: else if (workdir) { michael@0: if (! workdir.isDirectory()) michael@0: throw("Directory '"+workdir.path+"' does not exist"); michael@0: workdirStr = workdir.path; michael@0: } michael@0: return workdirStr; michael@0: } michael@0: michael@0: michael@0: var subprocess = { michael@0: call: function(options) { michael@0: options.mergeStderr = options.mergeStderr || false; michael@0: options.workdir = options.workdir || null; michael@0: options.environment = options.environment || DEFAULT_ENVIRONMENT; michael@0: if (options.arguments) { michael@0: var args = options.arguments; michael@0: options.arguments = []; michael@0: args.forEach(function(argument) { michael@0: options.arguments.push(argument); michael@0: }); michael@0: } else { michael@0: options.arguments = []; michael@0: } michael@0: michael@0: options.libc = getPlatformValue(LIBNAME); michael@0: michael@0: if (gXulRuntime.OS.substring(0, 3) == "WIN") { michael@0: return subprocess_win32(options); michael@0: } else { michael@0: return subprocess_unix(options); michael@0: } michael@0: michael@0: }, michael@0: registerDebugHandler: function(func) { michael@0: gDebugFunc = func; michael@0: }, michael@0: registerLogHandler: function(func) { michael@0: gLogFunc = func; michael@0: } michael@0: }; michael@0: michael@0: michael@0: michael@0: function subprocess_win32(options) { michael@0: var kernel32dll = ctypes.open(options.libc), michael@0: hChildProcess, michael@0: active = true, michael@0: done = false, michael@0: exitCode = -1, michael@0: child = {}, michael@0: stdinWorker = null, michael@0: stdoutWorker = null, michael@0: stderrWorker = null, michael@0: pendingWriteCount = 0, michael@0: readers = options.mergeStderr ? 1 : 2, michael@0: stdinOpenState = 2, michael@0: error = '', michael@0: output = ''; michael@0: michael@0: // stdin pipe states michael@0: const OPEN = 2; michael@0: const CLOSEABLE = 1; michael@0: const CLOSED = 0; michael@0: michael@0: //api declarations michael@0: /* michael@0: BOOL WINAPI CloseHandle( michael@0: __in HANDLE hObject michael@0: ); michael@0: */ michael@0: var CloseHandle = kernel32dll.declare("CloseHandle", michael@0: WinABI, michael@0: BOOL, michael@0: HANDLE michael@0: ); michael@0: michael@0: /* michael@0: BOOL WINAPI CreateProcess( michael@0: __in_opt LPCTSTR lpApplicationName, michael@0: __inout_opt LPTSTR lpCommandLine, michael@0: __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, michael@0: __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, michael@0: __in BOOL bInheritHandles, michael@0: __in DWORD dwCreationFlags, michael@0: __in_opt LPVOID lpEnvironment, michael@0: __in_opt LPCTSTR lpCurrentDirectory, michael@0: __in LPSTARTUPINFO lpStartupInfo, michael@0: __out LPPROCESS_INFORMATION lpProcessInformation michael@0: ); michael@0: */ michael@0: var CreateProcessW = kernel32dll.declare("CreateProcessW", michael@0: WinABI, michael@0: BOOL, michael@0: LPCTSTR, michael@0: LPTSTR, michael@0: SECURITY_ATTRIBUTES.ptr, michael@0: SECURITY_ATTRIBUTES.ptr, michael@0: BOOL, michael@0: DWORD, michael@0: LPVOID, michael@0: LPCTSTR, michael@0: STARTUPINFO.ptr, michael@0: PROCESS_INFORMATION.ptr michael@0: ); michael@0: michael@0: // /* michael@0: // BOOL WINAPI ReadFile( michael@0: // __in HANDLE hFile, michael@0: // __out LPVOID ReadFileBuffer, michael@0: // __in DWORD nNumberOfBytesToRead, michael@0: // __out_opt LPDWORD lpNumberOfBytesRead, michael@0: // __inout_opt LPOVERLAPPED lpOverlapped michael@0: // ); michael@0: // */ michael@0: // var ReadFileBufferSize = 1024, michael@0: // ReadFileBuffer = ctypes.char.array(ReadFileBufferSize), michael@0: // ReadFile = kernel32dll.declare("ReadFile", michael@0: // WinABI, michael@0: // BOOL, michael@0: // HANDLE, michael@0: // ReadFileBuffer, michael@0: // DWORD, michael@0: // LPDWORD, michael@0: // OVERLAPPED.ptr michael@0: // ); michael@0: // michael@0: // /* michael@0: // BOOL WINAPI PeekNamedPipe( michael@0: // __in HANDLE hNamedPipe, michael@0: // __out_opt LPVOID lpBuffer, michael@0: // __in DWORD nBufferSize, michael@0: // __out_opt LPDWORD lpBytesRead, michael@0: // __out_opt LPDWORD lpTotalBytesAvail, michael@0: // __out_opt LPDWORD lpBytesLeftThisMessage michael@0: // ); michael@0: // */ michael@0: // var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe", michael@0: // WinABI, michael@0: // BOOL, michael@0: // HANDLE, michael@0: // ReadFileBuffer, michael@0: // DWORD, michael@0: // LPDWORD, michael@0: // LPDWORD, michael@0: // LPDWORD michael@0: // ); michael@0: // michael@0: // /* michael@0: // BOOL WINAPI WriteFile( michael@0: // __in HANDLE hFile, michael@0: // __in LPCVOID lpBuffer, michael@0: // __in DWORD nNumberOfBytesToWrite, michael@0: // __out_opt LPDWORD lpNumberOfBytesWritten, michael@0: // __inout_opt LPOVERLAPPED lpOverlapped michael@0: // ); michael@0: // */ michael@0: // var WriteFile = kernel32dll.declare("WriteFile", michael@0: // WinABI, michael@0: // BOOL, michael@0: // HANDLE, michael@0: // ctypes.char.ptr, michael@0: // DWORD, michael@0: // LPDWORD, michael@0: // OVERLAPPED.ptr michael@0: // ); michael@0: michael@0: /* michael@0: BOOL WINAPI CreatePipe( michael@0: __out PHANDLE hReadPipe, michael@0: __out PHANDLE hWritePipe, michael@0: __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes, michael@0: __in DWORD nSize michael@0: ); michael@0: */ michael@0: var CreatePipe = kernel32dll.declare("CreatePipe", michael@0: WinABI, michael@0: BOOL, michael@0: HANDLE.ptr, michael@0: HANDLE.ptr, michael@0: SECURITY_ATTRIBUTES.ptr, michael@0: DWORD michael@0: ); michael@0: michael@0: /* michael@0: HANDLE WINAPI GetCurrentProcess(void); michael@0: */ michael@0: var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess", michael@0: WinABI, michael@0: HANDLE michael@0: ); michael@0: michael@0: /* michael@0: DWORD WINAPI GetLastError(void); michael@0: */ michael@0: var GetLastError = kernel32dll.declare("GetLastError", michael@0: WinABI, michael@0: DWORD michael@0: ); michael@0: michael@0: /* michael@0: BOOL WINAPI DuplicateHandle( michael@0: __in HANDLE hSourceProcessHandle, michael@0: __in HANDLE hSourceHandle, michael@0: __in HANDLE hTargetProcessHandle, michael@0: __out LPHANDLE lpTargetHandle, michael@0: __in DWORD dwDesiredAccess, michael@0: __in BOOL bInheritHandle, michael@0: __in DWORD dwOptions michael@0: ); michael@0: */ michael@0: var DuplicateHandle = kernel32dll.declare("DuplicateHandle", michael@0: WinABI, michael@0: BOOL, michael@0: HANDLE, michael@0: HANDLE, michael@0: HANDLE, michael@0: HANDLE.ptr, michael@0: DWORD, michael@0: BOOL, michael@0: DWORD michael@0: ); michael@0: michael@0: michael@0: /* michael@0: BOOL WINAPI GetExitCodeProcess( michael@0: __in HANDLE hProcess, michael@0: __out LPDWORD lpExitCode michael@0: ); michael@0: */ michael@0: var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess", michael@0: WinABI, michael@0: BOOL, michael@0: HANDLE, michael@0: LPDWORD michael@0: ); michael@0: michael@0: /* michael@0: DWORD WINAPI WaitForSingleObject( michael@0: __in HANDLE hHandle, michael@0: __in DWORD dwMilliseconds michael@0: ); michael@0: */ michael@0: var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject", michael@0: WinABI, michael@0: DWORD, michael@0: HANDLE, michael@0: DWORD michael@0: ); michael@0: michael@0: /* michael@0: BOOL WINAPI TerminateProcess( michael@0: __in HANDLE hProcess, michael@0: __in UINT uExitCode michael@0: ); michael@0: */ michael@0: var TerminateProcess = kernel32dll.declare("TerminateProcess", michael@0: WinABI, michael@0: BOOL, michael@0: HANDLE, michael@0: UINT michael@0: ); michael@0: michael@0: //functions michael@0: function popen(command, workdir, args, environment, child) { michael@0: //escape arguments michael@0: args.unshift(command); michael@0: for (var i = 0; i < args.length; i++) { michael@0: if (typeof args[i] != "string") { args[i] = args[i].toString(); } michael@0: /* quote arguments with spaces */ michael@0: if (args[i].match(/\s/)) { michael@0: args[i] = "\"" + args[i] + "\""; michael@0: } michael@0: /* If backslash is followed by a quote, double it */ michael@0: args[i] = args[i].replace(/\\\"/g, "\\\\\""); michael@0: } michael@0: command = args.join(' '); michael@0: michael@0: environment = environment || []; michael@0: if(environment.length) { michael@0: //An environment block consists of michael@0: //a null-terminated block of null-terminated strings. michael@0: //Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar michael@0: environment = ctypes.jschar.array()(environment.join('\0') + '\0'); michael@0: } else { michael@0: environment = null; michael@0: } michael@0: michael@0: var hOutputReadTmp = new HANDLE(), michael@0: hOutputRead = new HANDLE(), michael@0: hOutputWrite = new HANDLE(); michael@0: michael@0: var hErrorRead = new HANDLE(), michael@0: hErrorReadTmp = new HANDLE(), michael@0: hErrorWrite = new HANDLE(); michael@0: michael@0: var hInputRead = new HANDLE(), michael@0: hInputWriteTmp = new HANDLE(), michael@0: hInputWrite = new HANDLE(); michael@0: michael@0: // Set up the security attributes struct. michael@0: var sa = new SECURITY_ATTRIBUTES(); michael@0: sa.nLength = SECURITY_ATTRIBUTES.size; michael@0: sa.lpSecurityDescriptor = null; michael@0: sa.bInheritHandle = true; michael@0: michael@0: // Create output pipe. michael@0: michael@0: if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0)) michael@0: LogError('CreatePipe hOutputReadTmp failed'); michael@0: michael@0: if(options.mergeStderr) { michael@0: // Create a duplicate of the output write handle for the std error michael@0: // write handle. This is necessary in case the child application michael@0: // closes one of its std output handles. michael@0: if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite, michael@0: GetCurrentProcess(), hErrorWrite.address(), 0, michael@0: true, DUPLICATE_SAME_ACCESS)) michael@0: LogError("DuplicateHandle hOutputWrite failed"); michael@0: } else { michael@0: // Create error pipe. michael@0: if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0)) michael@0: LogError('CreatePipe hErrorReadTmp failed'); michael@0: } michael@0: michael@0: // Create input pipe. michael@0: if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0)) michael@0: LogError("CreatePipe hInputRead failed"); michael@0: michael@0: // Create new output/error read handle and the input write handles. Set michael@0: // the Properties to FALSE. Otherwise, the child inherits the michael@0: // properties and, as a result, non-closeable handles to the pipes michael@0: // are created. michael@0: if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp, michael@0: GetCurrentProcess(), michael@0: hOutputRead.address(), // Address of new handle. michael@0: 0, false, // Make it uninheritable. michael@0: DUPLICATE_SAME_ACCESS)) michael@0: LogError("DupliateHandle hOutputReadTmp failed"); michael@0: michael@0: if(!options.mergeStderr) { michael@0: if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp, michael@0: GetCurrentProcess(), michael@0: hErrorRead.address(), // Address of new handle. michael@0: 0, false, // Make it uninheritable. michael@0: DUPLICATE_SAME_ACCESS)) michael@0: LogError("DupliateHandle hErrorReadTmp failed"); michael@0: } michael@0: if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp, michael@0: GetCurrentProcess(), michael@0: hInputWrite.address(), // Address of new handle. michael@0: 0, false, // Make it uninheritable. michael@0: DUPLICATE_SAME_ACCESS)) michael@0: LogError("DupliateHandle hInputWriteTmp failed"); michael@0: michael@0: // Close inheritable copies of the handles. michael@0: if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed"); michael@0: if(!options.mergeStderr) michael@0: if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed"); michael@0: if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed"); michael@0: michael@0: var pi = new PROCESS_INFORMATION(); michael@0: var si = new STARTUPINFO(); michael@0: michael@0: si.cb = STARTUPINFO.size; michael@0: si.dwFlags = STARTF_USESTDHANDLES; michael@0: si.hStdInput = hInputRead; michael@0: si.hStdOutput = hOutputWrite; michael@0: si.hStdError = hErrorWrite; michael@0: michael@0: // Launch the process michael@0: if(!CreateProcessW(null, // executable name michael@0: command, // command buffer michael@0: null, // process security attribute michael@0: null, // thread security attribute michael@0: true, // inherits system handles michael@0: CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags michael@0: environment, // envrionment block michael@0: workdir, // set as current directory michael@0: si.address(), // (in) startup information michael@0: pi.address() // (out) process information michael@0: )) michael@0: throw("Fatal - Could not launch subprocess '"+command+"'"); michael@0: michael@0: // Close any unnecessary handles. michael@0: if (!CloseHandle(pi.hThread)) michael@0: LogError("CloseHandle pi.hThread failed"); michael@0: michael@0: // Close pipe handles (do not continue to modify the parent). michael@0: // You need to make sure that no handles to the write end of the michael@0: // output pipe are maintained in this process or else the pipe will michael@0: // not close when the child process exits and the ReadFile will hang. michael@0: if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed"); michael@0: if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed"); michael@0: if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed"); michael@0: michael@0: //return values michael@0: child.stdin = hInputWrite; michael@0: child.stdout = hOutputRead; michael@0: child.stderr = options.mergeStderr ? undefined : hErrorRead; michael@0: child.process = pi.hProcess; michael@0: return pi.hProcess; michael@0: } michael@0: michael@0: /* michael@0: * createStdinWriter () michael@0: * michael@0: * Create a ChromeWorker object for writing data to the subprocess' stdin michael@0: * pipe. The ChromeWorker object lives on a separate thread; this avoids michael@0: * internal deadlocks. michael@0: */ michael@0: function createStdinWriter() { michael@0: debugLog("Creating new stdin worker\n"); michael@0: stdinWorker = new ChromeWorker(WORKER_URL_WIN); michael@0: stdinWorker.onmessage = function(event) { michael@0: switch(event.data) { michael@0: case "WriteOK": michael@0: pendingWriteCount--; michael@0: debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n"); michael@0: break; michael@0: case "ClosedOK": michael@0: stdinOpenState = CLOSED; michael@0: debugLog("Stdin pipe closed\n"); michael@0: break; michael@0: default: michael@0: debugLog("got msg from stdinWorker: "+event.data+"\n"); michael@0: } michael@0: } michael@0: stdinWorker.onerror = function(error) { michael@0: pendingWriteCount--; michael@0: LogError("got error from stdinWorker: "+error.message+"\n"); michael@0: } michael@0: michael@0: stdinWorker.postMessage({msg: "init", libc: options.libc}); michael@0: } michael@0: michael@0: /* michael@0: * writeStdin() michael@0: * @data: String containing the data to write michael@0: * michael@0: * Write data to the subprocess' stdin (equals to sending a request to the michael@0: * ChromeWorker object to write the data). michael@0: */ michael@0: function writeStdin(data) { michael@0: ++pendingWriteCount; michael@0: debugLog("sending "+data.length+" bytes to stdinWorker\n"); michael@0: var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value); michael@0: michael@0: stdinWorker.postMessage({ michael@0: msg: 'write', michael@0: pipe: pipePtr, michael@0: data: data michael@0: }); michael@0: } michael@0: michael@0: /* michael@0: * closeStdinHandle() michael@0: * michael@0: * Close the stdin pipe, either directly or by requesting the ChromeWorker to michael@0: * close the pipe. The ChromeWorker will only close the pipe after the last write michael@0: * request process is done. michael@0: */ michael@0: michael@0: function closeStdinHandle() { michael@0: debugLog("trying to close stdin\n"); michael@0: if (stdinOpenState != OPEN) return; michael@0: stdinOpenState = CLOSEABLE; michael@0: michael@0: if (stdinWorker) { michael@0: debugLog("sending close stdin to worker\n"); michael@0: var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value); michael@0: stdinWorker.postMessage({ michael@0: msg: 'close', michael@0: pipe: pipePtr michael@0: }); michael@0: } michael@0: else { michael@0: stdinOpenState = CLOSED; michael@0: debugLog("Closing Stdin\n"); michael@0: CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed"); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* michael@0: * createReader(pipe, name) michael@0: * michael@0: * @pipe: handle to the pipe michael@0: * @name: String containing the pipe name (stdout or stderr) michael@0: * michael@0: * Create a ChromeWorker object for reading data asynchronously from michael@0: * the pipe (i.e. on a separate thread), and passing the result back to michael@0: * the caller. michael@0: */ michael@0: function createReader(pipe, name, callbackFunc) { michael@0: var worker = new ChromeWorker(WORKER_URL_WIN); michael@0: worker.onmessage = function(event) { michael@0: switch(event.data.msg) { michael@0: case "data": michael@0: debugLog("got "+event.data.count+" bytes from "+name+"\n"); michael@0: var data = ''; michael@0: if (options.charset === null) { michael@0: data = getBytes(event.data.data); michael@0: } michael@0: else { michael@0: try { michael@0: data = convertBytes(event.data.data, options.charset); michael@0: } michael@0: catch(ex) { michael@0: console.warn("error decoding output: " + ex); michael@0: data = getBytes(event.data.data); michael@0: } michael@0: } michael@0: michael@0: callbackFunc(data); michael@0: break; michael@0: case "done": michael@0: debugLog("Pipe "+name+" closed\n"); michael@0: --readers; michael@0: if (readers == 0) cleanup(); michael@0: break; michael@0: default: michael@0: debugLog("Got msg from "+name+": "+event.data.data+"\n"); michael@0: } michael@0: } michael@0: michael@0: worker.onerror = function(errorMsg) { michael@0: LogError("Got error from "+name+": "+errorMsg.message); michael@0: } michael@0: michael@0: var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value); michael@0: michael@0: worker.postMessage({ michael@0: msg: 'read', michael@0: pipe: pipePtr, michael@0: libc: options.libc, michael@0: charset: options.charset === null ? "null" : options.charset, michael@0: name: name michael@0: }); michael@0: michael@0: return worker; michael@0: } michael@0: michael@0: /* michael@0: * readPipes() michael@0: * michael@0: * Open the pipes for reading from stdout and stderr michael@0: */ michael@0: function readPipes() { michael@0: stdoutWorker = createReader(child.stdout, "stdout", function (data) { michael@0: if(options.stdout) { michael@0: setTimeout(function() { michael@0: options.stdout(data); michael@0: }, 0); michael@0: } else { michael@0: output += data; michael@0: } michael@0: }); michael@0: michael@0: michael@0: if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) { michael@0: if(options.stderr) { michael@0: setTimeout(function() { michael@0: options.stderr(data); michael@0: }, 0); michael@0: } else { michael@0: error += data; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /* michael@0: * cleanup() michael@0: * michael@0: * close stdin if needed, get the exit code from the subprocess and invoke michael@0: * the caller's done() function. michael@0: * michael@0: * Note: because stdout() and stderr() are called using setTimeout, we need to michael@0: * do the same here in order to guarantee the message sequence. michael@0: */ michael@0: function cleanup() { michael@0: debugLog("Cleanup called\n"); michael@0: if(active) { michael@0: active = false; michael@0: michael@0: closeStdinHandle(); // should only be required in case of errors michael@0: michael@0: var exit = new DWORD(); michael@0: GetExitCodeProcess(child.process, exit.address()); michael@0: exitCode = exit.value; michael@0: michael@0: if (stdinWorker) michael@0: stdinWorker.postMessage({msg: 'stop'}) michael@0: michael@0: setTimeout(function _done() { michael@0: if (options.done) { michael@0: try { michael@0: options.done({ michael@0: exitCode: exitCode, michael@0: stdout: output, michael@0: stderr: error, michael@0: }); michael@0: } michael@0: catch (ex) { michael@0: // prevent from blocking if options.done() throws an error michael@0: done = true; michael@0: throw ex; michael@0: } michael@0: } michael@0: done = true; michael@0: }, 0); michael@0: kernel32dll.close(); michael@0: } michael@0: } michael@0: michael@0: var cmdStr = getCommandStr(options.command); michael@0: var workDir = getWorkDir(options.workdir); michael@0: michael@0: //main michael@0: hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child); michael@0: michael@0: readPipes(); michael@0: michael@0: if (options.stdin) { michael@0: createStdinWriter(); michael@0: michael@0: if(typeof(options.stdin) == 'function') { michael@0: try { michael@0: options.stdin({ michael@0: write: function(data) { michael@0: writeStdin(data); michael@0: }, michael@0: close: function() { michael@0: closeStdinHandle(); michael@0: } michael@0: }); michael@0: } michael@0: catch (ex) { michael@0: // prevent from failing if options.stdin() throws an exception michael@0: closeStdinHandle(); michael@0: throw ex; michael@0: } michael@0: } else { michael@0: writeStdin(options.stdin); michael@0: closeStdinHandle(); michael@0: } michael@0: } michael@0: else michael@0: closeStdinHandle(); michael@0: michael@0: return { michael@0: kill: function(hardKill) { michael@0: // hardKill is currently ignored on Windows michael@0: var r = !!TerminateProcess(child.process, 255); michael@0: cleanup(-1); michael@0: return r; michael@0: }, michael@0: wait: function() { michael@0: // wait for async operations to complete michael@0: var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread; michael@0: while (!done) thread.processNextEvent(true); michael@0: michael@0: return exitCode; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: function subprocess_unix(options) { michael@0: // stdin pipe states michael@0: const OPEN = 2; michael@0: const CLOSEABLE = 1; michael@0: const CLOSED = 0; michael@0: michael@0: var libc = ctypes.open(options.libc), michael@0: active = true, michael@0: done = false, michael@0: exitCode = -1, michael@0: workerExitCode = 0, michael@0: child = {}, michael@0: pid = -1, michael@0: stdinWorker = null, michael@0: stdoutWorker = null, michael@0: stderrWorker = null, michael@0: pendingWriteCount = 0, michael@0: readers = options.mergeStderr ? 1 : 2, michael@0: stdinOpenState = OPEN, michael@0: error = '', michael@0: output = ''; michael@0: michael@0: //api declarations michael@0: michael@0: //pid_t fork(void); michael@0: var fork = libc.declare("fork", michael@0: ctypes.default_abi, michael@0: pid_t michael@0: ); michael@0: michael@0: //NULL terminated array of strings, argv[0] will be command >> + 2 michael@0: var argv = ctypes.char.ptr.array(options.arguments.length + 2); michael@0: var envp = ctypes.char.ptr.array(options.environment.length + 1); michael@0: michael@0: // posix_spawn_file_actions_t is a complex struct that may be different on michael@0: // each platform. We do not care about its attributes, we don't need to michael@0: // get access to them, but we do need to allocate the right amount michael@0: // of memory for it. michael@0: // At 2013/10/28, its size was 80 on linux, but better be safe (and larger), michael@0: // than crash when posix_spawn_file_actions_init fill `action` with zeros. michael@0: // Use `gcc sizeof_fileaction.c && ./a.out` to check that size. michael@0: var posix_spawn_file_actions_t = ctypes.uint8_t.array(100); michael@0: michael@0: //int posix_spawn(pid_t *restrict pid, const char *restrict path, michael@0: // const posix_spawn_file_actions_t *file_actions, michael@0: // const posix_spawnattr_t *restrict attrp, michael@0: // char *const argv[restrict], char *const envp[restrict]); michael@0: var posix_spawn = libc.declare("posix_spawn", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: pid_t.ptr, michael@0: ctypes.char.ptr, michael@0: posix_spawn_file_actions_t.ptr, michael@0: ctypes.voidptr_t, michael@0: argv, michael@0: envp michael@0: ); michael@0: michael@0: //int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions); michael@0: var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: posix_spawn_file_actions_t.ptr michael@0: ); michael@0: michael@0: //int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions); michael@0: var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: posix_spawn_file_actions_t.ptr michael@0: ); michael@0: michael@0: // int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t * michael@0: // file_actions, int fildes, int newfildes); michael@0: var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: posix_spawn_file_actions_t.ptr, michael@0: ctypes.int, michael@0: ctypes.int michael@0: ); michael@0: michael@0: // int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t * michael@0: // file_actions, int fildes); michael@0: var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: posix_spawn_file_actions_t.ptr, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //int pipe(int pipefd[2]); michael@0: var pipefd = ctypes.int.array(2); michael@0: var pipe = libc.declare("pipe", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: pipefd michael@0: ); michael@0: michael@0: //int close(int fd); michael@0: var close = libc.declare("close", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //pid_t waitpid(pid_t pid, int *status, int options); michael@0: var waitpid = libc.declare("waitpid", michael@0: ctypes.default_abi, michael@0: pid_t, michael@0: pid_t, michael@0: ctypes.int.ptr, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //int kill(pid_t pid, int sig); michael@0: var kill = libc.declare("kill", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: pid_t, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //int read(int fd, void *buf, size_t count); michael@0: var bufferSize = 1024; michael@0: var buffer = ctypes.char.array(bufferSize); michael@0: var read = libc.declare("read", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: buffer, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //ssize_t write(int fd, const void *buf, size_t count); michael@0: var write = libc.declare("write", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: ctypes.char.ptr, michael@0: ctypes.int michael@0: ); michael@0: michael@0: //int chdir(const char *path); michael@0: var chdir = libc.declare("chdir", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.char.ptr michael@0: ); michael@0: michael@0: //int fcntl(int fd, int cmd, ... /* arg */ ); michael@0: var fcntl = libc.declare("fcntl", michael@0: ctypes.default_abi, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: ctypes.int, michael@0: ctypes.int michael@0: ); michael@0: michael@0: function popen(command, workdir, args, environment, child) { michael@0: var _in, michael@0: _out, michael@0: _err, michael@0: pid, michael@0: rc; michael@0: _in = new pipefd(); michael@0: _out = new pipefd(); michael@0: if(!options.mergeStderr) michael@0: _err = new pipefd(); michael@0: michael@0: var _args = argv(); michael@0: args.unshift(command); michael@0: for(var i=0;i= 0) { michael@0: posix_spawn_file_actions_addclose(action.address(), i); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* michael@0: * createStdinWriter () michael@0: * michael@0: * Create a ChromeWorker object for writing data to the subprocess' stdin michael@0: * pipe. The ChromeWorker object lives on a separate thread; this avoids michael@0: * internal deadlocks. michael@0: */ michael@0: function createStdinWriter() { michael@0: debugLog("Creating new stdin worker\n"); michael@0: stdinWorker = new ChromeWorker(WORKER_URL_UNIX); michael@0: stdinWorker.onmessage = function(event) { michael@0: switch (event.data.msg) { michael@0: case "info": michael@0: switch(event.data.data) { michael@0: case "WriteOK": michael@0: pendingWriteCount--; michael@0: debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n"); michael@0: break; michael@0: case "ClosedOK": michael@0: stdinOpenState = CLOSED; michael@0: debugLog("Stdin pipe closed\n"); michael@0: break; michael@0: default: michael@0: debugLog("got msg from stdinWorker: "+event.data.data+"\n"); michael@0: } michael@0: break; michael@0: case "debug": michael@0: debugLog("stdinWorker: "+event.data.data+"\n"); michael@0: break; michael@0: case "error": michael@0: LogError("got error from stdinWorker: "+event.data.data+"\n"); michael@0: pendingWriteCount = 0; michael@0: stdinOpenState = CLOSED; michael@0: } michael@0: } michael@0: stdinWorker.onerror = function(error) { michael@0: pendingWriteCount = 0; michael@0: closeStdinHandle(); michael@0: LogError("got error from stdinWorker: "+error.message+"\n"); michael@0: } michael@0: stdinWorker.postMessage({msg: "init", libc: options.libc}); michael@0: } michael@0: michael@0: /* michael@0: * writeStdin() michael@0: * @data: String containing the data to write michael@0: * michael@0: * Write data to the subprocess' stdin (equals to sending a request to the michael@0: * ChromeWorker object to write the data). michael@0: */ michael@0: function writeStdin(data) { michael@0: if (stdinOpenState == CLOSED) return; // do not write to closed pipes michael@0: michael@0: ++pendingWriteCount; michael@0: debugLog("sending "+data.length+" bytes to stdinWorker\n"); michael@0: var pipe = parseInt(child.stdin); michael@0: michael@0: stdinWorker.postMessage({ michael@0: msg: 'write', michael@0: pipe: pipe, michael@0: data: data michael@0: }); michael@0: } michael@0: michael@0: michael@0: /* michael@0: * closeStdinHandle() michael@0: * michael@0: * Close the stdin pipe, either directly or by requesting the ChromeWorker to michael@0: * close the pipe. The ChromeWorker will only close the pipe after the last write michael@0: * request process is done. michael@0: */ michael@0: michael@0: function closeStdinHandle() { michael@0: debugLog("trying to close stdin\n"); michael@0: if (stdinOpenState != OPEN) return; michael@0: stdinOpenState = CLOSEABLE; michael@0: michael@0: if (stdinWorker) { michael@0: debugLog("sending close stdin to worker\n"); michael@0: var pipePtr = parseInt(child.stdin); michael@0: michael@0: stdinWorker.postMessage({ michael@0: msg: 'close', michael@0: pipe: pipePtr michael@0: }); michael@0: } michael@0: else { michael@0: stdinOpenState = CLOSED; michael@0: debugLog("Closing Stdin\n"); michael@0: close(child.stdin) && LogError("CloseHandle stdin failed"); michael@0: } michael@0: } michael@0: michael@0: michael@0: /* michael@0: * createReader(pipe, name) michael@0: * michael@0: * @pipe: handle to the pipe michael@0: * @name: String containing the pipe name (stdout or stderr) michael@0: * @callbackFunc: function to be called with the read data michael@0: * michael@0: * Create a ChromeWorker object for reading data asynchronously from michael@0: * the pipe (i.e. on a separate thread), and passing the result back to michael@0: * the caller. michael@0: * michael@0: */ michael@0: function createReader(pipe, name, callbackFunc) { michael@0: var worker = new ChromeWorker(WORKER_URL_UNIX); michael@0: worker.onmessage = function(event) { michael@0: switch(event.data.msg) { michael@0: case "data": michael@0: debugLog("got "+event.data.count+" bytes from "+name+"\n"); michael@0: var data = ''; michael@0: if (options.charset === null) { michael@0: data = getBytes(event.data.data); michael@0: } michael@0: else { michael@0: try { michael@0: data = convertBytes(event.data.data, options.charset); michael@0: } michael@0: catch(ex) { michael@0: console.warn("error decoding output: " + ex); michael@0: data = getBytes(event.data.data); michael@0: } michael@0: } michael@0: michael@0: callbackFunc(data); michael@0: break; michael@0: case "done": michael@0: debugLog("Pipe "+name+" closed\n"); michael@0: if (event.data.data > 0) workerExitCode = event.data.data; michael@0: --readers; michael@0: if (readers == 0) cleanup(); michael@0: break; michael@0: default: michael@0: debugLog("Got msg from "+name+": "+event.data.data+"\n"); michael@0: } michael@0: } michael@0: worker.onerror = function(error) { michael@0: LogError("Got error from "+name+": "+error.message); michael@0: } michael@0: michael@0: worker.postMessage({ michael@0: msg: 'read', michael@0: pipe: pipe, michael@0: pid: pid, michael@0: libc: options.libc, michael@0: charset: options.charset === null ? "null" : options.charset, michael@0: name: name michael@0: }); michael@0: michael@0: return worker; michael@0: } michael@0: michael@0: /* michael@0: * readPipes() michael@0: * michael@0: * Open the pipes for reading from stdout and stderr michael@0: */ michael@0: function readPipes() { michael@0: michael@0: stdoutWorker = createReader(child.stdout, "stdout", function (data) { michael@0: if(options.stdout) { michael@0: setTimeout(function() { michael@0: options.stdout(data); michael@0: }, 0); michael@0: } else { michael@0: output += data; michael@0: } michael@0: }); michael@0: michael@0: if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) { michael@0: if(options.stderr) { michael@0: setTimeout(function() { michael@0: options.stderr(data); michael@0: }, 0); michael@0: } else { michael@0: error += data; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function cleanup() { michael@0: debugLog("Cleanup called\n"); michael@0: if(active) { michael@0: active = false; michael@0: michael@0: closeStdinHandle(); // should only be required in case of errors michael@0: michael@0: var result, status = ctypes.int(); michael@0: result = waitpid(child.pid, status.address(), 0); michael@0: if (result > 0) michael@0: exitCode = status.value michael@0: else michael@0: if (workerExitCode >= 0) michael@0: exitCode = workerExitCode michael@0: else michael@0: exitCode = status.value; michael@0: michael@0: if (stdinWorker) michael@0: stdinWorker.postMessage({msg: 'stop'}) michael@0: michael@0: setTimeout(function _done() { michael@0: if (options.done) { michael@0: try { michael@0: options.done({ michael@0: exitCode: exitCode, michael@0: stdout: output, michael@0: stderr: error, michael@0: }); michael@0: } michael@0: catch(ex) { michael@0: // prevent from blocking if options.done() throws an error michael@0: done = true; michael@0: throw ex; michael@0: } michael@0: michael@0: } michael@0: done = true; michael@0: }, 0); michael@0: michael@0: libc.close(); michael@0: } michael@0: } michael@0: michael@0: //main michael@0: michael@0: var cmdStr = getCommandStr(options.command); michael@0: var workDir = getWorkDir(options.workdir); michael@0: michael@0: child = {}; michael@0: pid = popen(cmdStr, workDir, options.arguments, options.environment, child); michael@0: michael@0: debugLog("subprocess started; got PID "+pid+"\n"); michael@0: michael@0: readPipes(); michael@0: michael@0: if (options.stdin) { michael@0: createStdinWriter(); michael@0: if(typeof(options.stdin) == 'function') { michael@0: try { michael@0: options.stdin({ michael@0: write: function(data) { michael@0: writeStdin(data); michael@0: }, michael@0: close: function() { michael@0: closeStdinHandle(); michael@0: } michael@0: }); michael@0: } michael@0: catch(ex) { michael@0: // prevent from failing if options.stdin() throws an exception michael@0: closeStdinHandle(); michael@0: throw ex; michael@0: } michael@0: } else { michael@0: writeStdin(options.stdin); michael@0: closeStdinHandle(); michael@0: } michael@0: } michael@0: michael@0: return { michael@0: wait: function() { michael@0: // wait for async operations to complete michael@0: var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread; michael@0: while (! done) thread.processNextEvent(true) michael@0: return exitCode; michael@0: }, michael@0: kill: function(hardKill) { michael@0: var rv = kill(pid, (hardKill ? 9: 15)); michael@0: cleanup(-1); michael@0: return rv; michael@0: }, michael@0: pid: pid michael@0: } michael@0: } michael@0: michael@0: michael@0: module.exports = subprocess;