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