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