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