1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/addon-sdk/source/lib/sdk/system/child_process/subprocess.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1687 @@ 1.4 +// -*- coding: utf-8 -*- 1.5 +// vim: et:ts=4:sw=4:sts=4:ft=javascript 1.6 +/* ***** BEGIN LICENSE BLOCK ***** 1.7 + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 1.8 + * 1.9 + * The contents of this file are subject to the Mozilla Public 1.10 + * License Version 1.1 (the "MPL"); you may not use this file 1.11 + * except in compliance with the MPL. You may obtain a copy of 1.12 + * the MPL at http://www.mozilla.org/MPL/ 1.13 + * 1.14 + * Software distributed under the MPL is distributed on an "AS 1.15 + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 1.16 + * implied. See the MPL for the specific language governing 1.17 + * rights and limitations under the MPL. 1.18 + * 1.19 + * The Original Code is subprocess.jsm. 1.20 + * 1.21 + * The Initial Developer of this code is Jan Gerber. 1.22 + * Portions created by Jan Gerber <j@mailb.org> 1.23 + * are Copyright (C) 2011 Jan Gerber. 1.24 + * All Rights Reserved. 1.25 + * 1.26 + * Contributor(s): 1.27 + * Patrick Brunschwig <patrick@enigmail.net> 1.28 + * 1.29 + * Alternatively, the contents of this file may be used under the terms of 1.30 + * either the GNU General Public License Version 2 or later (the "GPL"), or 1.31 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 1.32 + * in which case the provisions of the GPL or the LGPL are applicable instead 1.33 + * of those above. If you wish to allow use of your version of this file only 1.34 + * under the terms of either the GPL or the LGPL, and not to allow others to 1.35 + * use your version of this file under the terms of the MPL, indicate your 1.36 + * decision by deleting the provisions above and replace them with the notice 1.37 + * and other provisions required by the GPL or the LGPL. If you do not delete 1.38 + * the provisions above, a recipient may use your version of this file under 1.39 + * the terms of any one of the MPL, the GPL or the LGPL. 1.40 + * ***** END LICENSE BLOCK ***** */ 1.41 + 1.42 +/* 1.43 + * This object allows to start a process, and read/write data to/from it 1.44 + * using stdin/stdout/stderr streams. 1.45 + * Usage example: 1.46 + * 1.47 + * var p = subprocess.call({ 1.48 + * command: '/bin/foo', 1.49 + * arguments: ['-v', 'foo'], 1.50 + * environment: [ "XYZ=abc", "MYVAR=def" ], 1.51 + * charset: 'UTF-8', 1.52 + * workdir: '/home/foo', 1.53 + * //stdin: "some value to write to stdin\nfoobar", 1.54 + * stdin: function(stdin) { 1.55 + * stdin.write("some value to write to stdin\nfoobar"); 1.56 + * stdin.close(); 1.57 + * }, 1.58 + * stdout: function(data) { 1.59 + * dump("got data on stdout:" + data + "\n"); 1.60 + * }, 1.61 + * stderr: function(data) { 1.62 + * dump("got data on stderr:" + data + "\n"); 1.63 + * }, 1.64 + * done: function(result) { 1.65 + * dump("process terminated with " + result.exitCode + "\n"); 1.66 + * }, 1.67 + * mergeStderr: false 1.68 + * }); 1.69 + * p.wait(); // wait for the subprocess to terminate 1.70 + * // this will block the main thread, 1.71 + * // only do if you can wait that long 1.72 + * 1.73 + * 1.74 + * Description of parameters: 1.75 + * -------------------------- 1.76 + * Apart from <command>, all arguments are optional. 1.77 + * 1.78 + * command: either a |nsIFile| object pointing to an executable file or a 1.79 + * String containing the platform-dependent path to an executable 1.80 + * file. 1.81 + * 1.82 + * arguments: optional string array containing the arguments to the command. 1.83 + * 1.84 + * environment: optional string array containing environment variables to pass 1.85 + * to the command. The array elements must have the form 1.86 + * "VAR=data". Please note that if environment is defined, it 1.87 + * replaces any existing environment variables for the subprocess. 1.88 + * 1.89 + * charset: Output is decoded with given charset and a string is returned. 1.90 + * If charset is undefined, "UTF-8" is used as default. 1.91 + * To get binary data, set this to null and the returned string 1.92 + * is not decoded in any way. 1.93 + * 1.94 + * workdir: optional; String containing the platform-dependent path to a 1.95 + * directory to become the current working directory of the subprocess. 1.96 + * 1.97 + * stdin: optional input data for the process to be passed on standard 1.98 + * input. stdin can either be a string or a function. 1.99 + * A |string| gets written to stdin and stdin gets closed; 1.100 + * A |function| gets passed an object with write and close function. 1.101 + * Please note that the write() function will return almost immediately; 1.102 + * data is always written asynchronously on a separate thread. 1.103 + * 1.104 + * stdout: an optional function that can receive output data from the 1.105 + * process. The stdout-function is called asynchronously; it can be 1.106 + * called mutliple times during the execution of a process. 1.107 + * At a minimum at each occurance of \n or \r. 1.108 + * Please note that null-characters might need to be escaped 1.109 + * with something like 'data.replace(/\0/g, "\\0");'. 1.110 + * 1.111 + * stderr: an optional function that can receive stderr data from the 1.112 + * process. The stderr-function is called asynchronously; it can be 1.113 + * called mutliple times during the execution of a process. Please 1.114 + * note that null-characters might need to be escaped with 1.115 + * something like 'data.replace(/\0/g, "\\0");'. 1.116 + * (on windows it only gets called once right now) 1.117 + * 1.118 + * done: optional function that is called when the process has terminated. 1.119 + * The exit code from the process available via result.exitCode. If 1.120 + * stdout is not defined, then the output from stdout is available 1.121 + * via result.stdout. stderr data is in result.stderr 1.122 + * 1.123 + * mergeStderr: optional boolean value. If true, stderr is merged with stdout; 1.124 + * no data will be provided to stderr. 1.125 + * 1.126 + * 1.127 + * Description of object returned by subprocess.call(...) 1.128 + * ------------------------------------------------------ 1.129 + * The object returned by subprocess.call offers a few methods that can be 1.130 + * executed: 1.131 + * 1.132 + * wait(): waits for the subprocess to terminate. It is not required to use 1.133 + * wait; done will be called in any case when the subprocess terminated. 1.134 + * 1.135 + * kill(hardKill): kill the subprocess. Any open pipes will be closed and 1.136 + * done will be called. 1.137 + * hardKill [ignored on Windows]: 1.138 + * - false: signal the process terminate (SIGTERM) 1.139 + * - true: kill the process (SIGKILL) 1.140 + * 1.141 + * 1.142 + * Other methods in subprocess 1.143 + * --------------------------- 1.144 + * 1.145 + * registerDebugHandler(functionRef): register a handler that is called to get 1.146 + * debugging information 1.147 + * registerLogHandler(functionRef): register a handler that is called to get error 1.148 + * messages 1.149 + * 1.150 + * example: 1.151 + * subprocess.registerLogHandler( function(s) { dump(s); } ); 1.152 + */ 1.153 + 1.154 +'use strict'; 1.155 + 1.156 +const { Cc, Ci, Cu, ChromeWorker } = require("chrome"); 1.157 + 1.158 +Cu.import("resource://gre/modules/ctypes.jsm"); 1.159 + 1.160 +const NS_LOCAL_FILE = "@mozilla.org/file/local;1"; 1.161 + 1.162 +const Runtime = require("sdk/system/runtime"); 1.163 +const Environment = require("sdk/system/environment").env; 1.164 +const DEFAULT_ENVIRONMENT = []; 1.165 +if (Runtime.OS == "Linux" && "DISPLAY" in Environment) { 1.166 + DEFAULT_ENVIRONMENT.push("DISPLAY=" + Environment.DISPLAY); 1.167 +} 1.168 + 1.169 +/* 1.170 +Fake require statements to ensure worker scripts are packaged: 1.171 +require("./subprocess_worker_win.js"); 1.172 +require("./subprocess_worker_unix.js"); 1.173 +*/ 1.174 +const URL_PREFIX = module.uri.replace(/subprocess\.js/, ""); 1.175 +const WORKER_URL_WIN = URL_PREFIX + "subprocess_worker_win.js"; 1.176 +const WORKER_URL_UNIX = URL_PREFIX + "subprocess_worker_unix.js"; 1.177 + 1.178 +//Windows API definitions 1.179 +if (ctypes.size_t.size == 8) { 1.180 + var WinABI = ctypes.default_abi; 1.181 +} else { 1.182 + var WinABI = ctypes.winapi_abi; 1.183 +} 1.184 +const WORD = ctypes.uint16_t; 1.185 +const DWORD = ctypes.uint32_t; 1.186 +const LPDWORD = DWORD.ptr; 1.187 + 1.188 +const UINT = ctypes.unsigned_int; 1.189 +const BOOL = ctypes.bool; 1.190 +const HANDLE = ctypes.size_t; 1.191 +const HWND = HANDLE; 1.192 +const HMODULE = HANDLE; 1.193 +const WPARAM = ctypes.size_t; 1.194 +const LPARAM = ctypes.size_t; 1.195 +const LRESULT = ctypes.size_t; 1.196 +const ULONG_PTR = ctypes.uintptr_t; 1.197 +const PVOID = ctypes.voidptr_t; 1.198 +const LPVOID = PVOID; 1.199 +const LPCTSTR = ctypes.jschar.ptr; 1.200 +const LPCWSTR = ctypes.jschar.ptr; 1.201 +const LPTSTR = ctypes.jschar.ptr; 1.202 +const LPSTR = ctypes.char.ptr; 1.203 +const LPCSTR = ctypes.char.ptr; 1.204 +const LPBYTE = ctypes.char.ptr; 1.205 + 1.206 +const CREATE_NEW_CONSOLE = 0x00000010; 1.207 +const CREATE_NO_WINDOW = 0x08000000; 1.208 +const CREATE_UNICODE_ENVIRONMENT = 0x00000400; 1.209 +const STARTF_USESHOWWINDOW = 0x00000001; 1.210 +const STARTF_USESTDHANDLES = 0x00000100; 1.211 +const SW_HIDE = 0; 1.212 +const DUPLICATE_SAME_ACCESS = 0x00000002; 1.213 +const STILL_ACTIVE = 259; 1.214 +const INFINITE = DWORD(0xFFFFFFFF); 1.215 +const WAIT_TIMEOUT = 0x00000102; 1.216 + 1.217 +/* 1.218 +typedef struct _SECURITY_ATTRIBUTES { 1.219 + DWORD nLength; 1.220 + LPVOID lpSecurityDescriptor; 1.221 + BOOL bInheritHandle; 1.222 +} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; 1.223 +*/ 1.224 +const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [ 1.225 + {"nLength": DWORD}, 1.226 + {"lpSecurityDescriptor": LPVOID}, 1.227 + {"bInheritHandle": BOOL}, 1.228 +]); 1.229 + 1.230 +/* 1.231 +typedef struct _STARTUPINFO { 1.232 + DWORD cb; 1.233 + LPTSTR lpReserved; 1.234 + LPTSTR lpDesktop; 1.235 + LPTSTR lpTitle; 1.236 + DWORD dwX; 1.237 + DWORD dwY; 1.238 + DWORD dwXSize; 1.239 + DWORD dwYSize; 1.240 + DWORD dwXCountChars; 1.241 + DWORD dwYCountChars; 1.242 + DWORD dwFillAttribute; 1.243 + DWORD dwFlags; 1.244 + WORD wShowWindow; 1.245 + WORD cbReserved2; 1.246 + LPBYTE lpReserved2; 1.247 + HANDLE hStdInput; 1.248 + HANDLE hStdOutput; 1.249 + HANDLE hStdError; 1.250 +} STARTUPINFO, *LPSTARTUPINFO; 1.251 +*/ 1.252 +const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [ 1.253 + {"cb": DWORD}, 1.254 + {"lpReserved": LPTSTR}, 1.255 + {"lpDesktop": LPTSTR}, 1.256 + {"lpTitle": LPTSTR}, 1.257 + {"dwX": DWORD}, 1.258 + {"dwY": DWORD}, 1.259 + {"dwXSize": DWORD}, 1.260 + {"dwYSize": DWORD}, 1.261 + {"dwXCountChars": DWORD}, 1.262 + {"dwYCountChars": DWORD}, 1.263 + {"dwFillAttribute": DWORD}, 1.264 + {"dwFlags": DWORD}, 1.265 + {"wShowWindow": WORD}, 1.266 + {"cbReserved2": WORD}, 1.267 + {"lpReserved2": LPBYTE}, 1.268 + {"hStdInput": HANDLE}, 1.269 + {"hStdOutput": HANDLE}, 1.270 + {"hStdError": HANDLE}, 1.271 +]); 1.272 + 1.273 +/* 1.274 +typedef struct _PROCESS_INFORMATION { 1.275 + HANDLE hProcess; 1.276 + HANDLE hThread; 1.277 + DWORD dwProcessId; 1.278 + DWORD dwThreadId; 1.279 +} PROCESS_INFORMATION, *LPPROCESS_INFORMATION; 1.280 +*/ 1.281 +const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [ 1.282 + {"hProcess": HANDLE}, 1.283 + {"hThread": HANDLE}, 1.284 + {"dwProcessId": DWORD}, 1.285 + {"dwThreadId": DWORD}, 1.286 +]); 1.287 + 1.288 +/* 1.289 +typedef struct _OVERLAPPED { 1.290 + ULONG_PTR Internal; 1.291 + ULONG_PTR InternalHigh; 1.292 + union { 1.293 + struct { 1.294 + DWORD Offset; 1.295 + DWORD OffsetHigh; 1.296 + }; 1.297 + PVOID Pointer; 1.298 + }; 1.299 + HANDLE hEvent; 1.300 +} OVERLAPPED, *LPOVERLAPPED; 1.301 +*/ 1.302 +const OVERLAPPED = new ctypes.StructType("OVERLAPPED"); 1.303 + 1.304 +//UNIX definitions 1.305 +const pid_t = ctypes.int32_t; 1.306 +const WNOHANG = 1; 1.307 +const F_GETFD = 1; 1.308 +const F_SETFL = 4; 1.309 + 1.310 +const LIBNAME = 0; 1.311 +const O_NONBLOCK = 1; 1.312 +const RLIM_T = 2; 1.313 +const RLIMIT_NOFILE = 3; 1.314 + 1.315 +function getPlatformValue(valueType) { 1.316 + 1.317 + if (! gXulRuntime) 1.318 + gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); 1.319 + 1.320 + const platformDefaults = { 1.321 + // Windows API: 1.322 + 'winnt': [ 'kernel32.dll' ], 1.323 + 1.324 + // Unix API: 1.325 + // library name O_NONBLOCK RLIM_T RLIMIT_NOFILE 1.326 + 'darwin': [ 'libc.dylib', 0x04 , ctypes.uint64_t , 8 ], 1.327 + 'linux': [ 'libc.so.6', 2024 , ctypes.unsigned_long, 7 ], 1.328 + 'freebsd': [ 'libc.so.7', 0x04 , ctypes.int64_t , 8 ], 1.329 + 'openbsd': [ 'libc.so.61.0', 0x04 , ctypes.int64_t , 8 ], 1.330 + 'sunos': [ 'libc.so', 0x80 , ctypes.unsigned_long, 5 ] 1.331 + } 1.332 + 1.333 + return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType]; 1.334 +} 1.335 + 1.336 + 1.337 +var gDebugFunc = null, 1.338 + gLogFunc = null, 1.339 + gXulRuntime = null; 1.340 + 1.341 +function LogError(s) { 1.342 + if (gLogFunc) 1.343 + gLogFunc(s); 1.344 + else 1.345 + dump(s); 1.346 +} 1.347 + 1.348 +function debugLog(s) { 1.349 + if (gDebugFunc) 1.350 + gDebugFunc(s); 1.351 +} 1.352 + 1.353 +function setTimeout(callback, timeout) { 1.354 + var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.355 + timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT); 1.356 +}; 1.357 + 1.358 +function getBytes(data) { 1.359 + var string = ''; 1.360 + data.forEach(function(x) { string += String.fromCharCode(x < 0 ? x + 256 : x) }); 1.361 + return string; 1.362 +} 1.363 + 1.364 +function readString(data, length, charset) { 1.365 + var string = '', bytes = []; 1.366 + for(var i = 0;i < length; i++) { 1.367 + if(data[i] == 0 && charset !== null) // stop on NULL character for non-binary data 1.368 + break 1.369 + bytes.push(data[i]); 1.370 + } 1.371 + if (!bytes || bytes.length == 0) 1.372 + return string; 1.373 + if(charset === null) { 1.374 + return bytes; 1.375 + } 1.376 + return convertBytes(bytes, charset); 1.377 +} 1.378 + 1.379 +function convertBytes(bytes, charset) { 1.380 + var string = ''; 1.381 + charset = charset || 'UTF-8'; 1.382 + var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.383 + .getService(Ci.nsIScriptableUnicodeConverter); 1.384 + try { 1.385 + unicodeConv.charset = charset; 1.386 + string = unicodeConv.convertFromByteArray(bytes, bytes.length); 1.387 + } catch (ex) { 1.388 + LogError("String conversion failed: "+ex.toString()+"\n") 1.389 + string = ''; 1.390 + } 1.391 + string += unicodeConv.Finish(); 1.392 + return string; 1.393 +} 1.394 + 1.395 + 1.396 +// temporary solution for removal of nsILocalFile 1.397 +function getLocalFileApi() { 1.398 + if ("nsILocalFile" in Ci) { 1.399 + return Ci.nsILocalFile; 1.400 + } 1.401 + else 1.402 + return Ci.nsIFile; 1.403 +} 1.404 + 1.405 +function getCommandStr(command) { 1.406 + let commandStr = null; 1.407 + if (typeof(command) == "string") { 1.408 + let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi()); 1.409 + file.initWithPath(command); 1.410 + if (! (file.isExecutable() && file.isFile())) 1.411 + throw("File '"+command+"' is not an executable file"); 1.412 + commandStr = command; 1.413 + } 1.414 + else { 1.415 + if (! (command.isExecutable() && command.isFile())) 1.416 + throw("File '"+command.path+"' is not an executable file"); 1.417 + commandStr = command.path; 1.418 + } 1.419 + 1.420 + return commandStr; 1.421 +} 1.422 + 1.423 +function getWorkDir(workdir) { 1.424 + let workdirStr = null; 1.425 + if (typeof(workdir) == "string") { 1.426 + let file = Cc[NS_LOCAL_FILE].createInstance(getLocalFileApi()); 1.427 + file.initWithPath(workdir); 1.428 + if (! (file.isDirectory())) 1.429 + throw("Directory '"+workdir+"' does not exist"); 1.430 + workdirStr = workdir; 1.431 + } 1.432 + else if (workdir) { 1.433 + if (! workdir.isDirectory()) 1.434 + throw("Directory '"+workdir.path+"' does not exist"); 1.435 + workdirStr = workdir.path; 1.436 + } 1.437 + return workdirStr; 1.438 +} 1.439 + 1.440 + 1.441 +var subprocess = { 1.442 + call: function(options) { 1.443 + options.mergeStderr = options.mergeStderr || false; 1.444 + options.workdir = options.workdir || null; 1.445 + options.environment = options.environment || DEFAULT_ENVIRONMENT; 1.446 + if (options.arguments) { 1.447 + var args = options.arguments; 1.448 + options.arguments = []; 1.449 + args.forEach(function(argument) { 1.450 + options.arguments.push(argument); 1.451 + }); 1.452 + } else { 1.453 + options.arguments = []; 1.454 + } 1.455 + 1.456 + options.libc = getPlatformValue(LIBNAME); 1.457 + 1.458 + if (gXulRuntime.OS.substring(0, 3) == "WIN") { 1.459 + return subprocess_win32(options); 1.460 + } else { 1.461 + return subprocess_unix(options); 1.462 + } 1.463 + 1.464 + }, 1.465 + registerDebugHandler: function(func) { 1.466 + gDebugFunc = func; 1.467 + }, 1.468 + registerLogHandler: function(func) { 1.469 + gLogFunc = func; 1.470 + } 1.471 +}; 1.472 + 1.473 + 1.474 + 1.475 +function subprocess_win32(options) { 1.476 + var kernel32dll = ctypes.open(options.libc), 1.477 + hChildProcess, 1.478 + active = true, 1.479 + done = false, 1.480 + exitCode = -1, 1.481 + child = {}, 1.482 + stdinWorker = null, 1.483 + stdoutWorker = null, 1.484 + stderrWorker = null, 1.485 + pendingWriteCount = 0, 1.486 + readers = options.mergeStderr ? 1 : 2, 1.487 + stdinOpenState = 2, 1.488 + error = '', 1.489 + output = ''; 1.490 + 1.491 + // stdin pipe states 1.492 + const OPEN = 2; 1.493 + const CLOSEABLE = 1; 1.494 + const CLOSED = 0; 1.495 + 1.496 + //api declarations 1.497 + /* 1.498 + BOOL WINAPI CloseHandle( 1.499 + __in HANDLE hObject 1.500 + ); 1.501 + */ 1.502 + var CloseHandle = kernel32dll.declare("CloseHandle", 1.503 + WinABI, 1.504 + BOOL, 1.505 + HANDLE 1.506 + ); 1.507 + 1.508 + /* 1.509 + BOOL WINAPI CreateProcess( 1.510 + __in_opt LPCTSTR lpApplicationName, 1.511 + __inout_opt LPTSTR lpCommandLine, 1.512 + __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, 1.513 + __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, 1.514 + __in BOOL bInheritHandles, 1.515 + __in DWORD dwCreationFlags, 1.516 + __in_opt LPVOID lpEnvironment, 1.517 + __in_opt LPCTSTR lpCurrentDirectory, 1.518 + __in LPSTARTUPINFO lpStartupInfo, 1.519 + __out LPPROCESS_INFORMATION lpProcessInformation 1.520 + ); 1.521 + */ 1.522 + var CreateProcessW = kernel32dll.declare("CreateProcessW", 1.523 + WinABI, 1.524 + BOOL, 1.525 + LPCTSTR, 1.526 + LPTSTR, 1.527 + SECURITY_ATTRIBUTES.ptr, 1.528 + SECURITY_ATTRIBUTES.ptr, 1.529 + BOOL, 1.530 + DWORD, 1.531 + LPVOID, 1.532 + LPCTSTR, 1.533 + STARTUPINFO.ptr, 1.534 + PROCESS_INFORMATION.ptr 1.535 + ); 1.536 + 1.537 +// /* 1.538 +// BOOL WINAPI ReadFile( 1.539 +// __in HANDLE hFile, 1.540 +// __out LPVOID ReadFileBuffer, 1.541 +// __in DWORD nNumberOfBytesToRead, 1.542 +// __out_opt LPDWORD lpNumberOfBytesRead, 1.543 +// __inout_opt LPOVERLAPPED lpOverlapped 1.544 +// ); 1.545 +// */ 1.546 +// var ReadFileBufferSize = 1024, 1.547 +// ReadFileBuffer = ctypes.char.array(ReadFileBufferSize), 1.548 +// ReadFile = kernel32dll.declare("ReadFile", 1.549 +// WinABI, 1.550 +// BOOL, 1.551 +// HANDLE, 1.552 +// ReadFileBuffer, 1.553 +// DWORD, 1.554 +// LPDWORD, 1.555 +// OVERLAPPED.ptr 1.556 +// ); 1.557 +// 1.558 +// /* 1.559 +// BOOL WINAPI PeekNamedPipe( 1.560 +// __in HANDLE hNamedPipe, 1.561 +// __out_opt LPVOID lpBuffer, 1.562 +// __in DWORD nBufferSize, 1.563 +// __out_opt LPDWORD lpBytesRead, 1.564 +// __out_opt LPDWORD lpTotalBytesAvail, 1.565 +// __out_opt LPDWORD lpBytesLeftThisMessage 1.566 +// ); 1.567 +// */ 1.568 +// var PeekNamedPipe = kernel32dll.declare("PeekNamedPipe", 1.569 +// WinABI, 1.570 +// BOOL, 1.571 +// HANDLE, 1.572 +// ReadFileBuffer, 1.573 +// DWORD, 1.574 +// LPDWORD, 1.575 +// LPDWORD, 1.576 +// LPDWORD 1.577 +// ); 1.578 +// 1.579 +// /* 1.580 +// BOOL WINAPI WriteFile( 1.581 +// __in HANDLE hFile, 1.582 +// __in LPCVOID lpBuffer, 1.583 +// __in DWORD nNumberOfBytesToWrite, 1.584 +// __out_opt LPDWORD lpNumberOfBytesWritten, 1.585 +// __inout_opt LPOVERLAPPED lpOverlapped 1.586 +// ); 1.587 +// */ 1.588 +// var WriteFile = kernel32dll.declare("WriteFile", 1.589 +// WinABI, 1.590 +// BOOL, 1.591 +// HANDLE, 1.592 +// ctypes.char.ptr, 1.593 +// DWORD, 1.594 +// LPDWORD, 1.595 +// OVERLAPPED.ptr 1.596 +// ); 1.597 + 1.598 + /* 1.599 + BOOL WINAPI CreatePipe( 1.600 + __out PHANDLE hReadPipe, 1.601 + __out PHANDLE hWritePipe, 1.602 + __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes, 1.603 + __in DWORD nSize 1.604 + ); 1.605 + */ 1.606 + var CreatePipe = kernel32dll.declare("CreatePipe", 1.607 + WinABI, 1.608 + BOOL, 1.609 + HANDLE.ptr, 1.610 + HANDLE.ptr, 1.611 + SECURITY_ATTRIBUTES.ptr, 1.612 + DWORD 1.613 + ); 1.614 + 1.615 + /* 1.616 + HANDLE WINAPI GetCurrentProcess(void); 1.617 + */ 1.618 + var GetCurrentProcess = kernel32dll.declare("GetCurrentProcess", 1.619 + WinABI, 1.620 + HANDLE 1.621 + ); 1.622 + 1.623 + /* 1.624 + DWORD WINAPI GetLastError(void); 1.625 + */ 1.626 + var GetLastError = kernel32dll.declare("GetLastError", 1.627 + WinABI, 1.628 + DWORD 1.629 + ); 1.630 + 1.631 + /* 1.632 + BOOL WINAPI DuplicateHandle( 1.633 + __in HANDLE hSourceProcessHandle, 1.634 + __in HANDLE hSourceHandle, 1.635 + __in HANDLE hTargetProcessHandle, 1.636 + __out LPHANDLE lpTargetHandle, 1.637 + __in DWORD dwDesiredAccess, 1.638 + __in BOOL bInheritHandle, 1.639 + __in DWORD dwOptions 1.640 + ); 1.641 + */ 1.642 + var DuplicateHandle = kernel32dll.declare("DuplicateHandle", 1.643 + WinABI, 1.644 + BOOL, 1.645 + HANDLE, 1.646 + HANDLE, 1.647 + HANDLE, 1.648 + HANDLE.ptr, 1.649 + DWORD, 1.650 + BOOL, 1.651 + DWORD 1.652 + ); 1.653 + 1.654 + 1.655 + /* 1.656 + BOOL WINAPI GetExitCodeProcess( 1.657 + __in HANDLE hProcess, 1.658 + __out LPDWORD lpExitCode 1.659 + ); 1.660 + */ 1.661 + var GetExitCodeProcess = kernel32dll.declare("GetExitCodeProcess", 1.662 + WinABI, 1.663 + BOOL, 1.664 + HANDLE, 1.665 + LPDWORD 1.666 + ); 1.667 + 1.668 + /* 1.669 + DWORD WINAPI WaitForSingleObject( 1.670 + __in HANDLE hHandle, 1.671 + __in DWORD dwMilliseconds 1.672 + ); 1.673 + */ 1.674 + var WaitForSingleObject = kernel32dll.declare("WaitForSingleObject", 1.675 + WinABI, 1.676 + DWORD, 1.677 + HANDLE, 1.678 + DWORD 1.679 + ); 1.680 + 1.681 + /* 1.682 + BOOL WINAPI TerminateProcess( 1.683 + __in HANDLE hProcess, 1.684 + __in UINT uExitCode 1.685 + ); 1.686 + */ 1.687 + var TerminateProcess = kernel32dll.declare("TerminateProcess", 1.688 + WinABI, 1.689 + BOOL, 1.690 + HANDLE, 1.691 + UINT 1.692 + ); 1.693 + 1.694 + //functions 1.695 + function popen(command, workdir, args, environment, child) { 1.696 + //escape arguments 1.697 + args.unshift(command); 1.698 + for (var i = 0; i < args.length; i++) { 1.699 + if (typeof args[i] != "string") { args[i] = args[i].toString(); } 1.700 + /* quote arguments with spaces */ 1.701 + if (args[i].match(/\s/)) { 1.702 + args[i] = "\"" + args[i] + "\""; 1.703 + } 1.704 + /* If backslash is followed by a quote, double it */ 1.705 + args[i] = args[i].replace(/\\\"/g, "\\\\\""); 1.706 + } 1.707 + command = args.join(' '); 1.708 + 1.709 + environment = environment || []; 1.710 + if(environment.length) { 1.711 + //An environment block consists of 1.712 + //a null-terminated block of null-terminated strings. 1.713 + //Using CREATE_UNICODE_ENVIRONMENT so needs to be jschar 1.714 + environment = ctypes.jschar.array()(environment.join('\0') + '\0'); 1.715 + } else { 1.716 + environment = null; 1.717 + } 1.718 + 1.719 + var hOutputReadTmp = new HANDLE(), 1.720 + hOutputRead = new HANDLE(), 1.721 + hOutputWrite = new HANDLE(); 1.722 + 1.723 + var hErrorRead = new HANDLE(), 1.724 + hErrorReadTmp = new HANDLE(), 1.725 + hErrorWrite = new HANDLE(); 1.726 + 1.727 + var hInputRead = new HANDLE(), 1.728 + hInputWriteTmp = new HANDLE(), 1.729 + hInputWrite = new HANDLE(); 1.730 + 1.731 + // Set up the security attributes struct. 1.732 + var sa = new SECURITY_ATTRIBUTES(); 1.733 + sa.nLength = SECURITY_ATTRIBUTES.size; 1.734 + sa.lpSecurityDescriptor = null; 1.735 + sa.bInheritHandle = true; 1.736 + 1.737 + // Create output pipe. 1.738 + 1.739 + if(!CreatePipe(hOutputReadTmp.address(), hOutputWrite.address(), sa.address(), 0)) 1.740 + LogError('CreatePipe hOutputReadTmp failed'); 1.741 + 1.742 + if(options.mergeStderr) { 1.743 + // Create a duplicate of the output write handle for the std error 1.744 + // write handle. This is necessary in case the child application 1.745 + // closes one of its std output handles. 1.746 + if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite, 1.747 + GetCurrentProcess(), hErrorWrite.address(), 0, 1.748 + true, DUPLICATE_SAME_ACCESS)) 1.749 + LogError("DuplicateHandle hOutputWrite failed"); 1.750 + } else { 1.751 + // Create error pipe. 1.752 + if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0)) 1.753 + LogError('CreatePipe hErrorReadTmp failed'); 1.754 + } 1.755 + 1.756 + // Create input pipe. 1.757 + if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0)) 1.758 + LogError("CreatePipe hInputRead failed"); 1.759 + 1.760 + // Create new output/error read handle and the input write handles. Set 1.761 + // the Properties to FALSE. Otherwise, the child inherits the 1.762 + // properties and, as a result, non-closeable handles to the pipes 1.763 + // are created. 1.764 + if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp, 1.765 + GetCurrentProcess(), 1.766 + hOutputRead.address(), // Address of new handle. 1.767 + 0, false, // Make it uninheritable. 1.768 + DUPLICATE_SAME_ACCESS)) 1.769 + LogError("DupliateHandle hOutputReadTmp failed"); 1.770 + 1.771 + if(!options.mergeStderr) { 1.772 + if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp, 1.773 + GetCurrentProcess(), 1.774 + hErrorRead.address(), // Address of new handle. 1.775 + 0, false, // Make it uninheritable. 1.776 + DUPLICATE_SAME_ACCESS)) 1.777 + LogError("DupliateHandle hErrorReadTmp failed"); 1.778 + } 1.779 + if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp, 1.780 + GetCurrentProcess(), 1.781 + hInputWrite.address(), // Address of new handle. 1.782 + 0, false, // Make it uninheritable. 1.783 + DUPLICATE_SAME_ACCESS)) 1.784 + LogError("DupliateHandle hInputWriteTmp failed"); 1.785 + 1.786 + // Close inheritable copies of the handles. 1.787 + if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed"); 1.788 + if(!options.mergeStderr) 1.789 + if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed"); 1.790 + if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed"); 1.791 + 1.792 + var pi = new PROCESS_INFORMATION(); 1.793 + var si = new STARTUPINFO(); 1.794 + 1.795 + si.cb = STARTUPINFO.size; 1.796 + si.dwFlags = STARTF_USESTDHANDLES; 1.797 + si.hStdInput = hInputRead; 1.798 + si.hStdOutput = hOutputWrite; 1.799 + si.hStdError = hErrorWrite; 1.800 + 1.801 + // Launch the process 1.802 + if(!CreateProcessW(null, // executable name 1.803 + command, // command buffer 1.804 + null, // process security attribute 1.805 + null, // thread security attribute 1.806 + true, // inherits system handles 1.807 + CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags 1.808 + environment, // envrionment block 1.809 + workdir, // set as current directory 1.810 + si.address(), // (in) startup information 1.811 + pi.address() // (out) process information 1.812 + )) 1.813 + throw("Fatal - Could not launch subprocess '"+command+"'"); 1.814 + 1.815 + // Close any unnecessary handles. 1.816 + if (!CloseHandle(pi.hThread)) 1.817 + LogError("CloseHandle pi.hThread failed"); 1.818 + 1.819 + // Close pipe handles (do not continue to modify the parent). 1.820 + // You need to make sure that no handles to the write end of the 1.821 + // output pipe are maintained in this process or else the pipe will 1.822 + // not close when the child process exits and the ReadFile will hang. 1.823 + if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed"); 1.824 + if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed"); 1.825 + if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed"); 1.826 + 1.827 + //return values 1.828 + child.stdin = hInputWrite; 1.829 + child.stdout = hOutputRead; 1.830 + child.stderr = options.mergeStderr ? undefined : hErrorRead; 1.831 + child.process = pi.hProcess; 1.832 + return pi.hProcess; 1.833 + } 1.834 + 1.835 + /* 1.836 + * createStdinWriter () 1.837 + * 1.838 + * Create a ChromeWorker object for writing data to the subprocess' stdin 1.839 + * pipe. The ChromeWorker object lives on a separate thread; this avoids 1.840 + * internal deadlocks. 1.841 + */ 1.842 + function createStdinWriter() { 1.843 + debugLog("Creating new stdin worker\n"); 1.844 + stdinWorker = new ChromeWorker(WORKER_URL_WIN); 1.845 + stdinWorker.onmessage = function(event) { 1.846 + switch(event.data) { 1.847 + case "WriteOK": 1.848 + pendingWriteCount--; 1.849 + debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n"); 1.850 + break; 1.851 + case "ClosedOK": 1.852 + stdinOpenState = CLOSED; 1.853 + debugLog("Stdin pipe closed\n"); 1.854 + break; 1.855 + default: 1.856 + debugLog("got msg from stdinWorker: "+event.data+"\n"); 1.857 + } 1.858 + } 1.859 + stdinWorker.onerror = function(error) { 1.860 + pendingWriteCount--; 1.861 + LogError("got error from stdinWorker: "+error.message+"\n"); 1.862 + } 1.863 + 1.864 + stdinWorker.postMessage({msg: "init", libc: options.libc}); 1.865 + } 1.866 + 1.867 + /* 1.868 + * writeStdin() 1.869 + * @data: String containing the data to write 1.870 + * 1.871 + * Write data to the subprocess' stdin (equals to sending a request to the 1.872 + * ChromeWorker object to write the data). 1.873 + */ 1.874 + function writeStdin(data) { 1.875 + ++pendingWriteCount; 1.876 + debugLog("sending "+data.length+" bytes to stdinWorker\n"); 1.877 + var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value); 1.878 + 1.879 + stdinWorker.postMessage({ 1.880 + msg: 'write', 1.881 + pipe: pipePtr, 1.882 + data: data 1.883 + }); 1.884 + } 1.885 + 1.886 + /* 1.887 + * closeStdinHandle() 1.888 + * 1.889 + * Close the stdin pipe, either directly or by requesting the ChromeWorker to 1.890 + * close the pipe. The ChromeWorker will only close the pipe after the last write 1.891 + * request process is done. 1.892 + */ 1.893 + 1.894 + function closeStdinHandle() { 1.895 + debugLog("trying to close stdin\n"); 1.896 + if (stdinOpenState != OPEN) return; 1.897 + stdinOpenState = CLOSEABLE; 1.898 + 1.899 + if (stdinWorker) { 1.900 + debugLog("sending close stdin to worker\n"); 1.901 + var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value); 1.902 + stdinWorker.postMessage({ 1.903 + msg: 'close', 1.904 + pipe: pipePtr 1.905 + }); 1.906 + } 1.907 + else { 1.908 + stdinOpenState = CLOSED; 1.909 + debugLog("Closing Stdin\n"); 1.910 + CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed"); 1.911 + } 1.912 + } 1.913 + 1.914 + 1.915 + /* 1.916 + * createReader(pipe, name) 1.917 + * 1.918 + * @pipe: handle to the pipe 1.919 + * @name: String containing the pipe name (stdout or stderr) 1.920 + * 1.921 + * Create a ChromeWorker object for reading data asynchronously from 1.922 + * the pipe (i.e. on a separate thread), and passing the result back to 1.923 + * the caller. 1.924 + */ 1.925 + function createReader(pipe, name, callbackFunc) { 1.926 + var worker = new ChromeWorker(WORKER_URL_WIN); 1.927 + worker.onmessage = function(event) { 1.928 + switch(event.data.msg) { 1.929 + case "data": 1.930 + debugLog("got "+event.data.count+" bytes from "+name+"\n"); 1.931 + var data = ''; 1.932 + if (options.charset === null) { 1.933 + data = getBytes(event.data.data); 1.934 + } 1.935 + else { 1.936 + try { 1.937 + data = convertBytes(event.data.data, options.charset); 1.938 + } 1.939 + catch(ex) { 1.940 + console.warn("error decoding output: " + ex); 1.941 + data = getBytes(event.data.data); 1.942 + } 1.943 + } 1.944 + 1.945 + callbackFunc(data); 1.946 + break; 1.947 + case "done": 1.948 + debugLog("Pipe "+name+" closed\n"); 1.949 + --readers; 1.950 + if (readers == 0) cleanup(); 1.951 + break; 1.952 + default: 1.953 + debugLog("Got msg from "+name+": "+event.data.data+"\n"); 1.954 + } 1.955 + } 1.956 + 1.957 + worker.onerror = function(errorMsg) { 1.958 + LogError("Got error from "+name+": "+errorMsg.message); 1.959 + } 1.960 + 1.961 + var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value); 1.962 + 1.963 + worker.postMessage({ 1.964 + msg: 'read', 1.965 + pipe: pipePtr, 1.966 + libc: options.libc, 1.967 + charset: options.charset === null ? "null" : options.charset, 1.968 + name: name 1.969 + }); 1.970 + 1.971 + return worker; 1.972 + } 1.973 + 1.974 + /* 1.975 + * readPipes() 1.976 + * 1.977 + * Open the pipes for reading from stdout and stderr 1.978 + */ 1.979 + function readPipes() { 1.980 + stdoutWorker = createReader(child.stdout, "stdout", function (data) { 1.981 + if(options.stdout) { 1.982 + setTimeout(function() { 1.983 + options.stdout(data); 1.984 + }, 0); 1.985 + } else { 1.986 + output += data; 1.987 + } 1.988 + }); 1.989 + 1.990 + 1.991 + if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) { 1.992 + if(options.stderr) { 1.993 + setTimeout(function() { 1.994 + options.stderr(data); 1.995 + }, 0); 1.996 + } else { 1.997 + error += data; 1.998 + } 1.999 + }); 1.1000 + } 1.1001 + 1.1002 + /* 1.1003 + * cleanup() 1.1004 + * 1.1005 + * close stdin if needed, get the exit code from the subprocess and invoke 1.1006 + * the caller's done() function. 1.1007 + * 1.1008 + * Note: because stdout() and stderr() are called using setTimeout, we need to 1.1009 + * do the same here in order to guarantee the message sequence. 1.1010 + */ 1.1011 + function cleanup() { 1.1012 + debugLog("Cleanup called\n"); 1.1013 + if(active) { 1.1014 + active = false; 1.1015 + 1.1016 + closeStdinHandle(); // should only be required in case of errors 1.1017 + 1.1018 + var exit = new DWORD(); 1.1019 + GetExitCodeProcess(child.process, exit.address()); 1.1020 + exitCode = exit.value; 1.1021 + 1.1022 + if (stdinWorker) 1.1023 + stdinWorker.postMessage({msg: 'stop'}) 1.1024 + 1.1025 + setTimeout(function _done() { 1.1026 + if (options.done) { 1.1027 + try { 1.1028 + options.done({ 1.1029 + exitCode: exitCode, 1.1030 + stdout: output, 1.1031 + stderr: error, 1.1032 + }); 1.1033 + } 1.1034 + catch (ex) { 1.1035 + // prevent from blocking if options.done() throws an error 1.1036 + done = true; 1.1037 + throw ex; 1.1038 + } 1.1039 + } 1.1040 + done = true; 1.1041 + }, 0); 1.1042 + kernel32dll.close(); 1.1043 + } 1.1044 + } 1.1045 + 1.1046 + var cmdStr = getCommandStr(options.command); 1.1047 + var workDir = getWorkDir(options.workdir); 1.1048 + 1.1049 + //main 1.1050 + hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child); 1.1051 + 1.1052 + readPipes(); 1.1053 + 1.1054 + if (options.stdin) { 1.1055 + createStdinWriter(); 1.1056 + 1.1057 + if(typeof(options.stdin) == 'function') { 1.1058 + try { 1.1059 + options.stdin({ 1.1060 + write: function(data) { 1.1061 + writeStdin(data); 1.1062 + }, 1.1063 + close: function() { 1.1064 + closeStdinHandle(); 1.1065 + } 1.1066 + }); 1.1067 + } 1.1068 + catch (ex) { 1.1069 + // prevent from failing if options.stdin() throws an exception 1.1070 + closeStdinHandle(); 1.1071 + throw ex; 1.1072 + } 1.1073 + } else { 1.1074 + writeStdin(options.stdin); 1.1075 + closeStdinHandle(); 1.1076 + } 1.1077 + } 1.1078 + else 1.1079 + closeStdinHandle(); 1.1080 + 1.1081 + return { 1.1082 + kill: function(hardKill) { 1.1083 + // hardKill is currently ignored on Windows 1.1084 + var r = !!TerminateProcess(child.process, 255); 1.1085 + cleanup(-1); 1.1086 + return r; 1.1087 + }, 1.1088 + wait: function() { 1.1089 + // wait for async operations to complete 1.1090 + var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread; 1.1091 + while (!done) thread.processNextEvent(true); 1.1092 + 1.1093 + return exitCode; 1.1094 + } 1.1095 + } 1.1096 +} 1.1097 + 1.1098 + 1.1099 +function subprocess_unix(options) { 1.1100 + // stdin pipe states 1.1101 + const OPEN = 2; 1.1102 + const CLOSEABLE = 1; 1.1103 + const CLOSED = 0; 1.1104 + 1.1105 + var libc = ctypes.open(options.libc), 1.1106 + active = true, 1.1107 + done = false, 1.1108 + exitCode = -1, 1.1109 + workerExitCode = 0, 1.1110 + child = {}, 1.1111 + pid = -1, 1.1112 + stdinWorker = null, 1.1113 + stdoutWorker = null, 1.1114 + stderrWorker = null, 1.1115 + pendingWriteCount = 0, 1.1116 + readers = options.mergeStderr ? 1 : 2, 1.1117 + stdinOpenState = OPEN, 1.1118 + error = '', 1.1119 + output = ''; 1.1120 + 1.1121 + //api declarations 1.1122 + 1.1123 + //pid_t fork(void); 1.1124 + var fork = libc.declare("fork", 1.1125 + ctypes.default_abi, 1.1126 + pid_t 1.1127 + ); 1.1128 + 1.1129 + //NULL terminated array of strings, argv[0] will be command >> + 2 1.1130 + var argv = ctypes.char.ptr.array(options.arguments.length + 2); 1.1131 + var envp = ctypes.char.ptr.array(options.environment.length + 1); 1.1132 + 1.1133 + // posix_spawn_file_actions_t is a complex struct that may be different on 1.1134 + // each platform. We do not care about its attributes, we don't need to 1.1135 + // get access to them, but we do need to allocate the right amount 1.1136 + // of memory for it. 1.1137 + // At 2013/10/28, its size was 80 on linux, but better be safe (and larger), 1.1138 + // than crash when posix_spawn_file_actions_init fill `action` with zeros. 1.1139 + // Use `gcc sizeof_fileaction.c && ./a.out` to check that size. 1.1140 + var posix_spawn_file_actions_t = ctypes.uint8_t.array(100); 1.1141 + 1.1142 + //int posix_spawn(pid_t *restrict pid, const char *restrict path, 1.1143 + // const posix_spawn_file_actions_t *file_actions, 1.1144 + // const posix_spawnattr_t *restrict attrp, 1.1145 + // char *const argv[restrict], char *const envp[restrict]); 1.1146 + var posix_spawn = libc.declare("posix_spawn", 1.1147 + ctypes.default_abi, 1.1148 + ctypes.int, 1.1149 + pid_t.ptr, 1.1150 + ctypes.char.ptr, 1.1151 + posix_spawn_file_actions_t.ptr, 1.1152 + ctypes.voidptr_t, 1.1153 + argv, 1.1154 + envp 1.1155 + ); 1.1156 + 1.1157 + //int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions); 1.1158 + var posix_spawn_file_actions_init = libc.declare("posix_spawn_file_actions_init", 1.1159 + ctypes.default_abi, 1.1160 + ctypes.int, 1.1161 + posix_spawn_file_actions_t.ptr 1.1162 + ); 1.1163 + 1.1164 + //int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions); 1.1165 + var posix_spawn_file_actions_destroy = libc.declare("posix_spawn_file_actions_destroy", 1.1166 + ctypes.default_abi, 1.1167 + ctypes.int, 1.1168 + posix_spawn_file_actions_t.ptr 1.1169 + ); 1.1170 + 1.1171 + // int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t * 1.1172 + // file_actions, int fildes, int newfildes); 1.1173 + var posix_spawn_file_actions_adddup2 = libc.declare("posix_spawn_file_actions_adddup2", 1.1174 + ctypes.default_abi, 1.1175 + ctypes.int, 1.1176 + posix_spawn_file_actions_t.ptr, 1.1177 + ctypes.int, 1.1178 + ctypes.int 1.1179 + ); 1.1180 + 1.1181 + // int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t * 1.1182 + // file_actions, int fildes); 1.1183 + var posix_spawn_file_actions_addclose = libc.declare("posix_spawn_file_actions_addclose", 1.1184 + ctypes.default_abi, 1.1185 + ctypes.int, 1.1186 + posix_spawn_file_actions_t.ptr, 1.1187 + ctypes.int 1.1188 + ); 1.1189 + 1.1190 + //int pipe(int pipefd[2]); 1.1191 + var pipefd = ctypes.int.array(2); 1.1192 + var pipe = libc.declare("pipe", 1.1193 + ctypes.default_abi, 1.1194 + ctypes.int, 1.1195 + pipefd 1.1196 + ); 1.1197 + 1.1198 + //int close(int fd); 1.1199 + var close = libc.declare("close", 1.1200 + ctypes.default_abi, 1.1201 + ctypes.int, 1.1202 + ctypes.int 1.1203 + ); 1.1204 + 1.1205 + //pid_t waitpid(pid_t pid, int *status, int options); 1.1206 + var waitpid = libc.declare("waitpid", 1.1207 + ctypes.default_abi, 1.1208 + pid_t, 1.1209 + pid_t, 1.1210 + ctypes.int.ptr, 1.1211 + ctypes.int 1.1212 + ); 1.1213 + 1.1214 + //int kill(pid_t pid, int sig); 1.1215 + var kill = libc.declare("kill", 1.1216 + ctypes.default_abi, 1.1217 + ctypes.int, 1.1218 + pid_t, 1.1219 + ctypes.int 1.1220 + ); 1.1221 + 1.1222 + //int read(int fd, void *buf, size_t count); 1.1223 + var bufferSize = 1024; 1.1224 + var buffer = ctypes.char.array(bufferSize); 1.1225 + var read = libc.declare("read", 1.1226 + ctypes.default_abi, 1.1227 + ctypes.int, 1.1228 + ctypes.int, 1.1229 + buffer, 1.1230 + ctypes.int 1.1231 + ); 1.1232 + 1.1233 + //ssize_t write(int fd, const void *buf, size_t count); 1.1234 + var write = libc.declare("write", 1.1235 + ctypes.default_abi, 1.1236 + ctypes.int, 1.1237 + ctypes.int, 1.1238 + ctypes.char.ptr, 1.1239 + ctypes.int 1.1240 + ); 1.1241 + 1.1242 + //int chdir(const char *path); 1.1243 + var chdir = libc.declare("chdir", 1.1244 + ctypes.default_abi, 1.1245 + ctypes.int, 1.1246 + ctypes.char.ptr 1.1247 + ); 1.1248 + 1.1249 + //int fcntl(int fd, int cmd, ... /* arg */ ); 1.1250 + var fcntl = libc.declare("fcntl", 1.1251 + ctypes.default_abi, 1.1252 + ctypes.int, 1.1253 + ctypes.int, 1.1254 + ctypes.int, 1.1255 + ctypes.int 1.1256 + ); 1.1257 + 1.1258 + function popen(command, workdir, args, environment, child) { 1.1259 + var _in, 1.1260 + _out, 1.1261 + _err, 1.1262 + pid, 1.1263 + rc; 1.1264 + _in = new pipefd(); 1.1265 + _out = new pipefd(); 1.1266 + if(!options.mergeStderr) 1.1267 + _err = new pipefd(); 1.1268 + 1.1269 + var _args = argv(); 1.1270 + args.unshift(command); 1.1271 + for(var i=0;i<args.length;i++) { 1.1272 + _args[i] = ctypes.char.array()(args[i]); 1.1273 + } 1.1274 + var _envp = envp(); 1.1275 + for(var i=0;i<environment.length;i++) { 1.1276 + _envp[i] = ctypes.char.array()(environment[i]); 1.1277 + // LogError(_envp); 1.1278 + } 1.1279 + 1.1280 + rc = pipe(_in); 1.1281 + if (rc < 0) { 1.1282 + return -1; 1.1283 + } 1.1284 + rc = pipe(_out); 1.1285 + fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK)); 1.1286 + if (rc < 0) { 1.1287 + close(_in[0]); 1.1288 + close(_in[1]); 1.1289 + return -1 1.1290 + } 1.1291 + if(!options.mergeStderr) { 1.1292 + rc = pipe(_err); 1.1293 + fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK)); 1.1294 + if (rc < 0) { 1.1295 + close(_in[0]); 1.1296 + close(_in[1]); 1.1297 + close(_out[0]); 1.1298 + close(_out[1]); 1.1299 + return -1 1.1300 + } 1.1301 + } 1.1302 + 1.1303 + let STDIN_FILENO = 0; 1.1304 + let STDOUT_FILENO = 1; 1.1305 + let STDERR_FILENO = 2; 1.1306 + 1.1307 + let action = posix_spawn_file_actions_t(); 1.1308 + posix_spawn_file_actions_init(action.address()); 1.1309 + 1.1310 + posix_spawn_file_actions_adddup2(action.address(), _in[0], STDIN_FILENO); 1.1311 + posix_spawn_file_actions_addclose(action.address(), _in[1]); 1.1312 + posix_spawn_file_actions_addclose(action.address(), _in[0]); 1.1313 + 1.1314 + posix_spawn_file_actions_adddup2(action.address(), _out[1], STDOUT_FILENO); 1.1315 + posix_spawn_file_actions_addclose(action.address(), _out[1]); 1.1316 + posix_spawn_file_actions_addclose(action.address(), _out[0]); 1.1317 + 1.1318 + if (!options.mergeStderr) { 1.1319 + posix_spawn_file_actions_adddup2(action.address(), _err[1], STDERR_FILENO); 1.1320 + posix_spawn_file_actions_addclose(action.address(), _err[1]); 1.1321 + posix_spawn_file_actions_addclose(action.address(), _err[0]); 1.1322 + } 1.1323 + 1.1324 + // posix_spawn doesn't support setting a custom workdir for the child, 1.1325 + // so change the cwd in the parent process before launching the child process. 1.1326 + if (workdir) { 1.1327 + if (chdir(workdir) < 0) { 1.1328 + throw new Error("Unable to change workdir before launching child process"); 1.1329 + } 1.1330 + } 1.1331 + 1.1332 + closeOtherFds(action, _in[1], _out[0], options.mergeStderr ? undefined : _err[0]); 1.1333 + 1.1334 + let id = pid_t(0); 1.1335 + let rv = posix_spawn(id.address(), command, action.address(), null, _args, _envp); 1.1336 + posix_spawn_file_actions_destroy(action.address()); 1.1337 + if (rv != 0) { 1.1338 + // we should not really end up here 1.1339 + if(!options.mergeStderr) { 1.1340 + close(_err[0]); 1.1341 + close(_err[1]); 1.1342 + } 1.1343 + close(_out[0]); 1.1344 + close(_out[1]); 1.1345 + close(_in[0]); 1.1346 + close(_in[1]); 1.1347 + throw new Error("Fatal - failed to create subprocess '"+command+"'"); 1.1348 + } 1.1349 + pid = id.value; 1.1350 + 1.1351 + close(_in[0]); 1.1352 + close(_out[1]); 1.1353 + if (!options.mergeStderr) 1.1354 + close(_err[1]); 1.1355 + child.stdin = _in[1]; 1.1356 + child.stdout = _out[0]; 1.1357 + child.stderr = options.mergeStderr ? undefined : _err[0]; 1.1358 + child.pid = pid; 1.1359 + 1.1360 + return pid; 1.1361 + } 1.1362 + 1.1363 + 1.1364 + // close any file descriptors that are not required for the process 1.1365 + function closeOtherFds(action, fdIn, fdOut, fdErr) { 1.1366 + // Unfortunately on mac, any fd registered in posix_spawn_file_actions_addclose 1.1367 + // that can't be closed correctly will make posix_spawn fail... 1.1368 + // Even if we ensure registering only still opened fds. 1.1369 + if (gXulRuntime.OS == "Darwin") 1.1370 + return; 1.1371 + 1.1372 + var maxFD = 256; // arbitrary max 1.1373 + 1.1374 + 1.1375 + var rlim_t = getPlatformValue(RLIM_T); 1.1376 + 1.1377 + const RLIMITS = new ctypes.StructType("RLIMITS", [ 1.1378 + {"rlim_cur": rlim_t}, 1.1379 + {"rlim_max": rlim_t} 1.1380 + ]); 1.1381 + 1.1382 + try { 1.1383 + var getrlimit = libc.declare("getrlimit", 1.1384 + ctypes.default_abi, 1.1385 + ctypes.int, 1.1386 + ctypes.int, 1.1387 + RLIMITS.ptr 1.1388 + ); 1.1389 + 1.1390 + var rl = new RLIMITS(); 1.1391 + if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) == 0) { 1.1392 + maxFD = rl.rlim_cur; 1.1393 + } 1.1394 + debugLog("getlimit: maxFD="+maxFD+"\n"); 1.1395 + 1.1396 + } 1.1397 + catch(ex) { 1.1398 + debugLog("getrlimit: no such function on this OS\n"); 1.1399 + debugLog(ex.toString()); 1.1400 + } 1.1401 + 1.1402 + // close any file descriptors 1.1403 + // fd's 0-2 are already closed 1.1404 + for (var i = 3; i < maxFD; i++) { 1.1405 + if (i != fdIn && i != fdOut && i != fdErr && fcntl(i, F_GETFD, -1) >= 0) { 1.1406 + posix_spawn_file_actions_addclose(action.address(), i); 1.1407 + } 1.1408 + } 1.1409 + } 1.1410 + 1.1411 + /* 1.1412 + * createStdinWriter () 1.1413 + * 1.1414 + * Create a ChromeWorker object for writing data to the subprocess' stdin 1.1415 + * pipe. The ChromeWorker object lives on a separate thread; this avoids 1.1416 + * internal deadlocks. 1.1417 + */ 1.1418 + function createStdinWriter() { 1.1419 + debugLog("Creating new stdin worker\n"); 1.1420 + stdinWorker = new ChromeWorker(WORKER_URL_UNIX); 1.1421 + stdinWorker.onmessage = function(event) { 1.1422 + switch (event.data.msg) { 1.1423 + case "info": 1.1424 + switch(event.data.data) { 1.1425 + case "WriteOK": 1.1426 + pendingWriteCount--; 1.1427 + debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n"); 1.1428 + break; 1.1429 + case "ClosedOK": 1.1430 + stdinOpenState = CLOSED; 1.1431 + debugLog("Stdin pipe closed\n"); 1.1432 + break; 1.1433 + default: 1.1434 + debugLog("got msg from stdinWorker: "+event.data.data+"\n"); 1.1435 + } 1.1436 + break; 1.1437 + case "debug": 1.1438 + debugLog("stdinWorker: "+event.data.data+"\n"); 1.1439 + break; 1.1440 + case "error": 1.1441 + LogError("got error from stdinWorker: "+event.data.data+"\n"); 1.1442 + pendingWriteCount = 0; 1.1443 + stdinOpenState = CLOSED; 1.1444 + } 1.1445 + } 1.1446 + stdinWorker.onerror = function(error) { 1.1447 + pendingWriteCount = 0; 1.1448 + closeStdinHandle(); 1.1449 + LogError("got error from stdinWorker: "+error.message+"\n"); 1.1450 + } 1.1451 + stdinWorker.postMessage({msg: "init", libc: options.libc}); 1.1452 + } 1.1453 + 1.1454 + /* 1.1455 + * writeStdin() 1.1456 + * @data: String containing the data to write 1.1457 + * 1.1458 + * Write data to the subprocess' stdin (equals to sending a request to the 1.1459 + * ChromeWorker object to write the data). 1.1460 + */ 1.1461 + function writeStdin(data) { 1.1462 + if (stdinOpenState == CLOSED) return; // do not write to closed pipes 1.1463 + 1.1464 + ++pendingWriteCount; 1.1465 + debugLog("sending "+data.length+" bytes to stdinWorker\n"); 1.1466 + var pipe = parseInt(child.stdin); 1.1467 + 1.1468 + stdinWorker.postMessage({ 1.1469 + msg: 'write', 1.1470 + pipe: pipe, 1.1471 + data: data 1.1472 + }); 1.1473 + } 1.1474 + 1.1475 + 1.1476 + /* 1.1477 + * closeStdinHandle() 1.1478 + * 1.1479 + * Close the stdin pipe, either directly or by requesting the ChromeWorker to 1.1480 + * close the pipe. The ChromeWorker will only close the pipe after the last write 1.1481 + * request process is done. 1.1482 + */ 1.1483 + 1.1484 + function closeStdinHandle() { 1.1485 + debugLog("trying to close stdin\n"); 1.1486 + if (stdinOpenState != OPEN) return; 1.1487 + stdinOpenState = CLOSEABLE; 1.1488 + 1.1489 + if (stdinWorker) { 1.1490 + debugLog("sending close stdin to worker\n"); 1.1491 + var pipePtr = parseInt(child.stdin); 1.1492 + 1.1493 + stdinWorker.postMessage({ 1.1494 + msg: 'close', 1.1495 + pipe: pipePtr 1.1496 + }); 1.1497 + } 1.1498 + else { 1.1499 + stdinOpenState = CLOSED; 1.1500 + debugLog("Closing Stdin\n"); 1.1501 + close(child.stdin) && LogError("CloseHandle stdin failed"); 1.1502 + } 1.1503 + } 1.1504 + 1.1505 + 1.1506 + /* 1.1507 + * createReader(pipe, name) 1.1508 + * 1.1509 + * @pipe: handle to the pipe 1.1510 + * @name: String containing the pipe name (stdout or stderr) 1.1511 + * @callbackFunc: function to be called with the read data 1.1512 + * 1.1513 + * Create a ChromeWorker object for reading data asynchronously from 1.1514 + * the pipe (i.e. on a separate thread), and passing the result back to 1.1515 + * the caller. 1.1516 + * 1.1517 + */ 1.1518 + function createReader(pipe, name, callbackFunc) { 1.1519 + var worker = new ChromeWorker(WORKER_URL_UNIX); 1.1520 + worker.onmessage = function(event) { 1.1521 + switch(event.data.msg) { 1.1522 + case "data": 1.1523 + debugLog("got "+event.data.count+" bytes from "+name+"\n"); 1.1524 + var data = ''; 1.1525 + if (options.charset === null) { 1.1526 + data = getBytes(event.data.data); 1.1527 + } 1.1528 + else { 1.1529 + try { 1.1530 + data = convertBytes(event.data.data, options.charset); 1.1531 + } 1.1532 + catch(ex) { 1.1533 + console.warn("error decoding output: " + ex); 1.1534 + data = getBytes(event.data.data); 1.1535 + } 1.1536 + } 1.1537 + 1.1538 + callbackFunc(data); 1.1539 + break; 1.1540 + case "done": 1.1541 + debugLog("Pipe "+name+" closed\n"); 1.1542 + if (event.data.data > 0) workerExitCode = event.data.data; 1.1543 + --readers; 1.1544 + if (readers == 0) cleanup(); 1.1545 + break; 1.1546 + default: 1.1547 + debugLog("Got msg from "+name+": "+event.data.data+"\n"); 1.1548 + } 1.1549 + } 1.1550 + worker.onerror = function(error) { 1.1551 + LogError("Got error from "+name+": "+error.message); 1.1552 + } 1.1553 + 1.1554 + worker.postMessage({ 1.1555 + msg: 'read', 1.1556 + pipe: pipe, 1.1557 + pid: pid, 1.1558 + libc: options.libc, 1.1559 + charset: options.charset === null ? "null" : options.charset, 1.1560 + name: name 1.1561 + }); 1.1562 + 1.1563 + return worker; 1.1564 + } 1.1565 + 1.1566 + /* 1.1567 + * readPipes() 1.1568 + * 1.1569 + * Open the pipes for reading from stdout and stderr 1.1570 + */ 1.1571 + function readPipes() { 1.1572 + 1.1573 + stdoutWorker = createReader(child.stdout, "stdout", function (data) { 1.1574 + if(options.stdout) { 1.1575 + setTimeout(function() { 1.1576 + options.stdout(data); 1.1577 + }, 0); 1.1578 + } else { 1.1579 + output += data; 1.1580 + } 1.1581 + }); 1.1582 + 1.1583 + if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) { 1.1584 + if(options.stderr) { 1.1585 + setTimeout(function() { 1.1586 + options.stderr(data); 1.1587 + }, 0); 1.1588 + } else { 1.1589 + error += data; 1.1590 + } 1.1591 + }); 1.1592 + } 1.1593 + 1.1594 + function cleanup() { 1.1595 + debugLog("Cleanup called\n"); 1.1596 + if(active) { 1.1597 + active = false; 1.1598 + 1.1599 + closeStdinHandle(); // should only be required in case of errors 1.1600 + 1.1601 + var result, status = ctypes.int(); 1.1602 + result = waitpid(child.pid, status.address(), 0); 1.1603 + if (result > 0) 1.1604 + exitCode = status.value 1.1605 + else 1.1606 + if (workerExitCode >= 0) 1.1607 + exitCode = workerExitCode 1.1608 + else 1.1609 + exitCode = status.value; 1.1610 + 1.1611 + if (stdinWorker) 1.1612 + stdinWorker.postMessage({msg: 'stop'}) 1.1613 + 1.1614 + setTimeout(function _done() { 1.1615 + if (options.done) { 1.1616 + try { 1.1617 + options.done({ 1.1618 + exitCode: exitCode, 1.1619 + stdout: output, 1.1620 + stderr: error, 1.1621 + }); 1.1622 + } 1.1623 + catch(ex) { 1.1624 + // prevent from blocking if options.done() throws an error 1.1625 + done = true; 1.1626 + throw ex; 1.1627 + } 1.1628 + 1.1629 + } 1.1630 + done = true; 1.1631 + }, 0); 1.1632 + 1.1633 + libc.close(); 1.1634 + } 1.1635 + } 1.1636 + 1.1637 + //main 1.1638 + 1.1639 + var cmdStr = getCommandStr(options.command); 1.1640 + var workDir = getWorkDir(options.workdir); 1.1641 + 1.1642 + child = {}; 1.1643 + pid = popen(cmdStr, workDir, options.arguments, options.environment, child); 1.1644 + 1.1645 + debugLog("subprocess started; got PID "+pid+"\n"); 1.1646 + 1.1647 + readPipes(); 1.1648 + 1.1649 + if (options.stdin) { 1.1650 + createStdinWriter(); 1.1651 + if(typeof(options.stdin) == 'function') { 1.1652 + try { 1.1653 + options.stdin({ 1.1654 + write: function(data) { 1.1655 + writeStdin(data); 1.1656 + }, 1.1657 + close: function() { 1.1658 + closeStdinHandle(); 1.1659 + } 1.1660 + }); 1.1661 + } 1.1662 + catch(ex) { 1.1663 + // prevent from failing if options.stdin() throws an exception 1.1664 + closeStdinHandle(); 1.1665 + throw ex; 1.1666 + } 1.1667 + } else { 1.1668 + writeStdin(options.stdin); 1.1669 + closeStdinHandle(); 1.1670 + } 1.1671 + } 1.1672 + 1.1673 + return { 1.1674 + wait: function() { 1.1675 + // wait for async operations to complete 1.1676 + var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread; 1.1677 + while (! done) thread.processNextEvent(true) 1.1678 + return exitCode; 1.1679 + }, 1.1680 + kill: function(hardKill) { 1.1681 + var rv = kill(pid, (hardKill ? 9: 15)); 1.1682 + cleanup(-1); 1.1683 + return rv; 1.1684 + }, 1.1685 + pid: pid 1.1686 + } 1.1687 +} 1.1688 + 1.1689 + 1.1690 +module.exports = subprocess;