~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to mailnews/extensions/enigmail/ipc/modules/subprocess.jsm

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 <j@mailb.org>
 
20
 * are Copyright (C) 2011 Jan Gerber.
 
21
 * All Rights Reserved.
 
22
 *
 
23
 * Contributor(s):
 
24
 * Patrick Brunschwig <patrick@enigmail.net>
 
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 <command>, 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
let 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
// stdin pipe states
 
212
const PIPE_STATE_NOT_INIT = 3;
 
213
const PIPE_STATE_OPEN = 2;
 
214
const PIPE_STATE_CLOSEABLE = 1;
 
215
const PIPE_STATE_CLOSED = 0;
 
216
 
 
217
/*
 
218
typedef struct _SECURITY_ATTRIBUTES {
 
219
 DWORD  nLength;
 
220
 LPVOID lpSecurityDescriptor;
 
221
 BOOL   bInheritHandle;
 
222
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
 
223
*/
 
224
const SECURITY_ATTRIBUTES = new ctypes.StructType("SECURITY_ATTRIBUTES", [
 
225
    {"nLength": DWORD},
 
226
    {"lpSecurityDescriptor": LPVOID},
 
227
    {"bInheritHandle": BOOL}
 
228
]);
 
229
 
 
230
/*
 
231
typedef struct _STARTUPINFO {
 
232
  DWORD  cb;
 
233
  LPTSTR lpReserved;
 
234
  LPTSTR lpDesktop;
 
235
  LPTSTR lpTitle;
 
236
  DWORD  dwX;
 
237
  DWORD  dwY;
 
238
  DWORD  dwXSize;
 
239
  DWORD  dwYSize;
 
240
  DWORD  dwXCountChars;
 
241
  DWORD  dwYCountChars;
 
242
  DWORD  dwFillAttribute;
 
243
  DWORD  dwFlags;
 
244
  WORD   wShowWindow;
 
245
  WORD   cbReserved2;
 
246
  LPBYTE lpReserved2;
 
247
  HANDLE hStdInput;
 
248
  HANDLE hStdOutput;
 
249
  HANDLE hStdError;
 
250
} STARTUPINFO, *LPSTARTUPINFO;
 
251
*/
 
252
const STARTUPINFO = new ctypes.StructType("STARTUPINFO", [
 
253
    {"cb": DWORD},
 
254
    {"lpReserved": LPTSTR},
 
255
    {"lpDesktop": LPTSTR},
 
256
    {"lpTitle": LPTSTR},
 
257
    {"dwX": DWORD},
 
258
    {"dwY": DWORD},
 
259
    {"dwXSize": DWORD},
 
260
    {"dwYSize": DWORD},
 
261
    {"dwXCountChars": DWORD},
 
262
    {"dwYCountChars": DWORD},
 
263
    {"dwFillAttribute": DWORD},
 
264
    {"dwFlags": DWORD},
 
265
    {"wShowWindow": WORD},
 
266
    {"cbReserved2": WORD},
 
267
    {"lpReserved2": LPBYTE},
 
268
    {"hStdInput": HANDLE},
 
269
    {"hStdOutput": HANDLE},
 
270
    {"hStdError": HANDLE}
 
271
]);
 
272
 
 
273
/*
 
274
typedef struct _PROCESS_INFORMATION {
 
275
  HANDLE hProcess;
 
276
  HANDLE hThread;
 
277
  DWORD  dwProcessId;
 
278
  DWORD  dwThreadId;
 
279
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
 
280
*/
 
281
const PROCESS_INFORMATION = new ctypes.StructType("PROCESS_INFORMATION", [
 
282
    {"hProcess": HANDLE},
 
283
    {"hThread": HANDLE},
 
284
    {"dwProcessId": DWORD},
 
285
    {"dwThreadId": DWORD}
 
286
]);
 
287
 
 
288
/*
 
289
typedef struct _OVERLAPPED {
 
290
  ULONG_PTR Internal;
 
291
  ULONG_PTR InternalHigh;
 
292
  union {
 
293
    struct {
 
294
      DWORD Offset;
 
295
      DWORD OffsetHigh;
 
296
    };
 
297
    PVOID  Pointer;
 
298
  };
 
299
  HANDLE    hEvent;
 
300
} OVERLAPPED, *LPOVERLAPPED;
 
301
*/
 
302
const OVERLAPPED = new ctypes.StructType("OVERLAPPED");
 
303
 
 
304
//UNIX definitions
 
305
const pid_t = ctypes.int32_t;
 
306
const WNOHANG = 1;
 
307
const F_SETFL = 4;
 
308
 
 
309
const LIBNAME       = 0;
 
310
const O_NONBLOCK    = 1;
 
311
const RLIM_T        = 2;
 
312
const RLIMIT_NOFILE = 3;
 
313
 
 
314
function getPlatformValue(valueType) {
 
315
 
 
316
    if (! gXulRuntime)
 
317
        gXulRuntime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 
318
 
 
319
    const platformDefaults = {
 
320
        // Windows API:
 
321
        'winnt':   [ 'kernel32.dll' ],
 
322
 
 
323
        // Unix API:
 
324
        //            library name   O_NONBLOCK RLIM_T                RLIMIT_NOFILE
 
325
        'darwin':  [ 'libc.dylib',   0x04     , ctypes.uint64_t     , 8 ],
 
326
        'linux':   [ 'libc.so.6',    2024     , ctypes.unsigned_long, 7 ],
 
327
        'freebsd': [ 'libc.so.7',    0x04     , ctypes.int64_t      , 8 ],
 
328
        'openbsd': [ 'libc.so.61.0', 0x04     , ctypes.int64_t      , 8 ],
 
329
        'sunos':   [ 'libc.so',      0x80     , ctypes.unsigned_long, 5 ]
 
330
    };
 
331
 
 
332
    return platformDefaults[gXulRuntime.OS.toLowerCase()][valueType];
 
333
}
 
334
 
 
335
 
 
336
var gDebugFunc = null,
 
337
    gLogFunc = null,
 
338
    gXulRuntime = null,
 
339
    gLibcWrapper = null;
 
340
 
 
341
function LogError(s) {
 
342
    if (gLogFunc)
 
343
        gLogFunc(s);
 
344
    else
 
345
        dump(s);
 
346
}
 
347
 
 
348
function debugLog(s) {
 
349
    if (gDebugFunc)
 
350
        gDebugFunc(s);
 
351
}
 
352
 
 
353
function setTimeout(callback, timeout) {
 
354
    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
355
    timer.initWithCallback(callback, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
 
356
};
 
357
 
 
358
function convertBytes(data, charset) {
 
359
    var string = '';
 
360
    charset = charset || 'UTF-8';
 
361
    var unicodeConv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
 
362
                        .getService(Ci.nsIScriptableUnicodeConverter);
 
363
    try {
 
364
        unicodeConv.charset = charset;
 
365
        string = unicodeConv.ConvertToUnicode(data);
 
366
    } catch (ex) {
 
367
        LogError("String conversion failed: "+ex.toString()+"\n");
 
368
        string = '';
 
369
    }
 
370
    string += unicodeConv.Finish();
 
371
    return string;
 
372
}
 
373
 
 
374
function getCommandStr(command) {
 
375
    let commandStr = null;
 
376
    if (typeof(command) == "string") {
 
377
        let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
 
378
        file.initWithPath(command);
 
379
        if (! (file.isExecutable() && file.isFile()))
 
380
            throw("File '"+command+"' is not an executable file");
 
381
        commandStr = command;
 
382
    }
 
383
    else {
 
384
        if (! (command.isExecutable() && command.isFile()))
 
385
            throw("File '"+command.path+"' is not an executable file");
 
386
        commandStr = command.path;
 
387
    }
 
388
 
 
389
    return commandStr;
 
390
}
 
391
 
 
392
function getWorkDir(workdir) {
 
393
    let workdirStr = null;
 
394
    if (typeof(workdir) == "string") {
 
395
        let file = Cc[NS_LOCAL_FILE].createInstance(Ci.nsIFile);
 
396
        file.initWithPath(workdir);
 
397
        if (! (file.isDirectory()))
 
398
            throw("Directory '"+workdir+"' does not exist");
 
399
        workdirStr = workdir;
 
400
    }
 
401
    else if (workdir) {
 
402
        if (! workdir.isDirectory())
 
403
            throw("Directory '"+workdir.path+"' does not exist");
 
404
        workdirStr = workdir.path;
 
405
    }
 
406
    return workdirStr;
 
407
}
 
408
 
 
409
 
 
410
var subprocess = {
 
411
    call: function(options) {
 
412
        options.mergeStderr = options.mergeStderr || false;
 
413
        options.bufferedOutput = options.bufferedOutput || false;
 
414
        options.workdir = options.workdir ||  null;
 
415
        options.environment = options.environment ||  [];
 
416
        if (options.arguments) {
 
417
            var args = options.arguments;
 
418
            options.arguments = [];
 
419
            args.forEach(function(argument) {
 
420
                options.arguments.push(argument);
 
421
            });
 
422
        } else {
 
423
            options.arguments = [];
 
424
        }
 
425
 
 
426
        options.libc = getPlatformValue(LIBNAME);
 
427
 
 
428
        if (gXulRuntime.OS.substring(0, 3) == "WIN") {
 
429
            return subprocess_win32(options);
 
430
        } else {
 
431
            return subprocess_unix(options);
 
432
        }
 
433
 
 
434
    },
 
435
    registerDebugHandler: function(func) {
 
436
        gDebugFunc = func;
 
437
    },
 
438
    registerLogHandler: function(func) {
 
439
        gLogFunc = func;
 
440
    },
 
441
    registerLibcWrapper: function(dllName) {
 
442
        gLibcWrapper = dllName;
 
443
    },
 
444
 
 
445
    getPlatformValue: getPlatformValue
 
446
};
 
447
 
 
448
 
 
449
 
 
450
function subprocess_win32(options) {
 
451
    var kernel32dll = ctypes.open(options.libc),
 
452
        hChildProcess,
 
453
        active = true,
 
454
        done = false,
 
455
        exitCode = -1,
 
456
        child = {},
 
457
        stdinWorker = null,
 
458
        stdoutWorker = null,
 
459
        stderrWorker = null,
 
460
        pendingWriteCount = 0,
 
461
        readers = options.mergeStderr ? 1 : 2,
 
462
        stdinOpenState = PIPE_STATE_NOT_INIT,
 
463
        error = '',
 
464
        output = '';
 
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
        if(options.mergeStderr) {
 
713
          // Create a duplicate of the output write handle for the std error
 
714
          // write handle. This is necessary in case the child application
 
715
          // closes one of its std output handles.
 
716
          if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
 
717
                               GetCurrentProcess(), hErrorWrite.address(), 0,
 
718
                               true, DUPLICATE_SAME_ACCESS))
 
719
             LogError("DuplicateHandle hOutputWrite failed");
 
720
        } else {
 
721
            // Create error pipe.
 
722
            if(!CreatePipe(hErrorReadTmp.address(), hErrorWrite.address(), sa.address(), 0))
 
723
                LogError('CreatePipe hErrorReadTmp failed');
 
724
        }
 
725
 
 
726
        // Create input pipe.
 
727
        if (!CreatePipe(hInputRead.address(),hInputWriteTmp.address(),sa.address(), 0))
 
728
            LogError("CreatePipe hInputRead failed");
 
729
 
 
730
        // Create new output/error read handle and the input write handles. Set
 
731
        // the Properties to FALSE. Otherwise, the child inherits the
 
732
        // properties and, as a result, non-closeable handles to the pipes
 
733
        // are created.
 
734
        if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
 
735
                             GetCurrentProcess(),
 
736
                             hOutputRead.address(), // Address of new handle.
 
737
                             0, false, // Make it uninheritable.
 
738
                             DUPLICATE_SAME_ACCESS))
 
739
             LogError("DupliateHandle hOutputReadTmp failed");
 
740
 
 
741
        if(!options.mergeStderr) {
 
742
            if (!DuplicateHandle(GetCurrentProcess(), hErrorReadTmp,
 
743
                             GetCurrentProcess(),
 
744
                             hErrorRead.address(), // Address of new handle.
 
745
                             0, false, // Make it uninheritable.
 
746
                             DUPLICATE_SAME_ACCESS))
 
747
             LogError("DupliateHandle hErrorReadTmp failed");
 
748
        }
 
749
        if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp,
 
750
                             GetCurrentProcess(),
 
751
                             hInputWrite.address(), // Address of new handle.
 
752
                             0, false, // Make it uninheritable.
 
753
                             DUPLICATE_SAME_ACCESS))
 
754
          LogError("DupliateHandle hInputWriteTmp failed");
 
755
 
 
756
        // Close inheritable copies of the handles.
 
757
        if (!CloseHandle(hOutputReadTmp)) LogError("CloseHandle hOutputReadTmp failed");
 
758
        if(!options.mergeStderr)
 
759
            if (!CloseHandle(hErrorReadTmp)) LogError("CloseHandle hErrorReadTmp failed");
 
760
        if (!CloseHandle(hInputWriteTmp)) LogError("CloseHandle failed");
 
761
 
 
762
        var pi = new PROCESS_INFORMATION();
 
763
        var si = new STARTUPINFO();
 
764
 
 
765
        si.cb = STARTUPINFO.size;
 
766
        si.dwFlags = STARTF_USESTDHANDLES;
 
767
        si.hStdInput  = hInputRead;
 
768
        si.hStdOutput = hOutputWrite;
 
769
        si.hStdError  = hErrorWrite;
 
770
 
 
771
        // Launch the process
 
772
        if(!CreateProcessW(null,            // executable name
 
773
                           command,         // command buffer
 
774
                           null,            // process security attribute
 
775
                           null,            // thread security attribute
 
776
                           true,            // inherits system handles
 
777
                           CREATE_UNICODE_ENVIRONMENT|CREATE_NO_WINDOW, // process flags
 
778
                           environment,     // envrionment block
 
779
                           workdir,          // set as current directory
 
780
                           si.address(),    // (in) startup information
 
781
                           pi.address()     // (out) process information
 
782
        ))
 
783
            throw("Fatal - Could not launch subprocess '"+command+"'");
 
784
 
 
785
        // Close any unnecessary handles.
 
786
        if (!CloseHandle(pi.hThread))
 
787
            LogError("CloseHandle pi.hThread failed");
 
788
 
 
789
        // Close pipe handles (do not continue to modify the parent).
 
790
        // You need to make sure that no handles to the write end of the
 
791
        // output pipe are maintained in this process or else the pipe will
 
792
        // not close when the child process exits and the ReadFile will hang.
 
793
        if (!CloseHandle(hInputRead)) LogError("CloseHandle hInputRead failed");
 
794
        if (!CloseHandle(hOutputWrite)) LogError("CloseHandle hOutputWrite failed");
 
795
        if (!CloseHandle(hErrorWrite)) LogError("CloseHandle hErrorWrite failed");
 
796
 
 
797
        //return values
 
798
        child.stdin = hInputWrite;
 
799
        child.stdout = hOutputRead;
 
800
        child.stderr = options.mergeStderr ? undefined : hErrorRead;
 
801
        child.process = pi.hProcess;
 
802
        return pi.hProcess;
 
803
    }
 
804
 
 
805
    /*
 
806
     * createStdinWriter ()
 
807
     *
 
808
     * Create a ChromeWorker object for writing data to the subprocess' stdin
 
809
     * pipe. The ChromeWorker object lives on a separate thread; this avoids
 
810
     * internal deadlocks.
 
811
     */
 
812
    function createStdinWriter() {
 
813
        debugLog("Creating new stdin worker\n");
 
814
        stdinWorker = new ChromeWorker("subprocess_worker_win.js");
 
815
        stdinWorker.onmessage = function(event) {
 
816
            switch(event.data) {
 
817
            case "WriteOK":
 
818
                pendingWriteCount--;
 
819
                debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
 
820
                break;
 
821
            case "InitOK":
 
822
                stdinOpenState = PIPE_STATE_OPEN;
 
823
                debugLog("Stdin pipe opened\n");
 
824
                break;
 
825
            case "ClosedOK":
 
826
                stdinOpenState = PIPE_STATE_CLOSED;
 
827
                debugLog("Stdin pipe closed\n");
 
828
                break;
 
829
            default:
 
830
                debugLog("got msg from stdinWorker: "+event.data+"\n");
 
831
            }
 
832
        };
 
833
        stdinWorker.onerror = function(error) {
 
834
            pendingWriteCount--;
 
835
            exitCode = -2;
 
836
            LogError("got error from stdinWorker: "+error.message+"\n");
 
837
        };
 
838
 
 
839
        stdinWorker.postMessage({msg: "init", libc: options.libc});
 
840
    }
 
841
 
 
842
    /*
 
843
     * writeStdin()
 
844
     * @data: String containing the data to write
 
845
     *
 
846
     * Write data to the subprocess' stdin (equals to sending a request to the
 
847
     * ChromeWorker object to write the data).
 
848
     */
 
849
    function writeStdin(data) {
 
850
        if (stdinOpenState == PIPE_STATE_CLOSED) {
 
851
          LogError("trying to write data to closed stdin");
 
852
          return;
 
853
        }
 
854
 
 
855
        ++pendingWriteCount;
 
856
        debugLog("sending "+data.length+" bytes to stdinWorker\n");
 
857
 
 
858
        var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
 
859
 
 
860
        stdinWorker.postMessage({
 
861
                msg: 'write',
 
862
                pipe: pipePtr,
 
863
                data: data
 
864
            });
 
865
    }
 
866
 
 
867
    /*
 
868
     * closeStdinHandle()
 
869
     *
 
870
     * Close the stdin pipe, either directly or by requesting the ChromeWorker to
 
871
     * close the pipe. The ChromeWorker will only close the pipe after the last write
 
872
     * request process is done.
 
873
     */
 
874
 
 
875
    function closeStdinHandle() {
 
876
        debugLog("trying to close stdin\n");
 
877
        if (stdinOpenState != PIPE_STATE_OPEN) return;
 
878
        stdinOpenState = PIPE_STATE_CLOSEABLE;
 
879
 
 
880
        if (stdinWorker) {
 
881
            debugLog("sending close stdin to worker\n");
 
882
            var pipePtr = parseInt(ctypes.cast(child.stdin.address(), ctypes.uintptr_t).value);
 
883
            stdinWorker.postMessage({
 
884
                msg: 'close',
 
885
                pipe: pipePtr
 
886
            });
 
887
        }
 
888
        else {
 
889
            stdinOpenState = PIPE_STATE_CLOSED;
 
890
            debugLog("Closing Stdin\n");
 
891
            CloseHandle(child.stdin) || LogError("CloseHandle hInputWrite failed");
 
892
        }
 
893
    }
 
894
 
 
895
 
 
896
    /*
 
897
     * createReader(pipe, name)
 
898
     *
 
899
     * @pipe: handle to the pipe
 
900
     * @name: String containing the pipe name (stdout or stderr)
 
901
     *
 
902
     * Create a ChromeWorker object for reading data asynchronously from
 
903
     * the pipe (i.e. on a separate thread), and passing the result back to
 
904
     * the caller.
 
905
     */
 
906
    function createReader(pipe, name, callbackFunc) {
 
907
        var worker = new ChromeWorker("subprocess_worker_win.js");
 
908
        worker.onmessage = function(event) {
 
909
            switch(event.data.msg) {
 
910
            case "data":
 
911
                debugLog("got "+event.data.count+" bytes from "+name+"\n");
 
912
                var data = '';
 
913
                if (options.charset === null) {
 
914
                    data = event.data.data;
 
915
                }
 
916
                else
 
917
                    data = convertBytes(event.data.data, options.charset);
 
918
 
 
919
                callbackFunc(data);
 
920
                break;
 
921
            case "done":
 
922
                debugLog("Pipe "+name+" closed\n");
 
923
                --readers;
 
924
                if (readers == 0) cleanup();
 
925
                break;
 
926
            case "error":
 
927
                exitCode = -2;
 
928
                                LogError("Got msg from "+name+": "+event.data.data+"\n");
 
929
                break;
 
930
            default:
 
931
                debugLog("Got msg from "+name+": "+event.data.data+"\n");
 
932
            }
 
933
        };
 
934
 
 
935
        worker.onerror = function(errorMsg) {
 
936
            LogError("Got error from "+name+": "+errorMsg.message);
 
937
            exitCode = -2;
 
938
        };
 
939
 
 
940
        var pipePtr = parseInt(ctypes.cast(pipe.address(), ctypes.uintptr_t).value);
 
941
 
 
942
        worker.postMessage({
 
943
                msg: 'read',
 
944
                pipe: pipePtr,
 
945
                libc: options.libc,
 
946
                charset: options.charset === null ? "null" : options.charset,
 
947
                bufferedOutput: options.bufferedOutput,
 
948
                name: name
 
949
            });
 
950
 
 
951
        return worker;
 
952
    }
 
953
 
 
954
    /*
 
955
     * readPipes()
 
956
     *
 
957
     * Open the pipes for reading from stdout and stderr
 
958
     */
 
959
    function readPipes() {
 
960
 
 
961
        stdoutWorker = createReader(child.stdout, "stdout", function (data) {
 
962
            if(options.stdout) {
 
963
                setTimeout(function() {
 
964
                    options.stdout(data);
 
965
                }, 0);
 
966
            } else {
 
967
                output += data;
 
968
            }
 
969
        });
 
970
 
 
971
 
 
972
        if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
 
973
            if(options.stderr) {
 
974
                setTimeout(function() {
 
975
                    options.stderr(data);
 
976
                }, 0);
 
977
            } else {
 
978
                error += data;
 
979
            }
 
980
        });
 
981
    }
 
982
 
 
983
    /*
 
984
     * cleanup()
 
985
     *
 
986
     * close stdin if needed, get the exit code from the subprocess and invoke
 
987
     * the caller's done() function.
 
988
     *
 
989
     * Note: because stdout() and stderr() are called using setTimeout, we need to
 
990
     * do the same here in order to guarantee the message sequence.
 
991
     */
 
992
    function cleanup() {
 
993
        debugLog("Cleanup called\n");
 
994
        if(active) {
 
995
            active = false;
 
996
 
 
997
            closeStdinHandle(); // should only be required in case of errors
 
998
 
 
999
            var exit = new DWORD();
 
1000
            GetExitCodeProcess(child.process, exit.address());
 
1001
 
 
1002
            if (exitCode > -2)
 
1003
              exitCode = exit.value;
 
1004
 
 
1005
            if (stdinWorker)
 
1006
                stdinWorker.postMessage({msg: 'stop'});
 
1007
 
 
1008
            setTimeout(function _done() {
 
1009
                if (options.done) {
 
1010
                    try {
 
1011
                        options.done({
 
1012
                            exitCode: exitCode,
 
1013
                            stdout: output,
 
1014
                            stderr: error
 
1015
                        });
 
1016
                    }
 
1017
                    catch (ex) {
 
1018
                        // prevent from blocking if options.done() throws an error
 
1019
                        done = true;
 
1020
                        throw ex;
 
1021
                    }
 
1022
                }
 
1023
                done = true;
 
1024
            }, 0);
 
1025
            kernel32dll.close();
 
1026
        }
 
1027
    }
 
1028
 
 
1029
    function startWriting() {
 
1030
        debugLog("startWriting called\n");
 
1031
 
 
1032
        if (stdinOpenState == PIPE_STATE_NOT_INIT) {
 
1033
          setTimeout(function _f() {
 
1034
              startWriting();
 
1035
            }, 1);
 
1036
          return;
 
1037
        }
 
1038
 
 
1039
        if(typeof(options.stdin) == 'function') {
 
1040
            try {
 
1041
                options.stdin({
 
1042
                    write: function(data) {
 
1043
                        writeStdin(data);
 
1044
                    },
 
1045
                    close: function() {
 
1046
                        closeStdinHandle();
 
1047
                    }
 
1048
                });
 
1049
            }
 
1050
            catch (ex) {
 
1051
                // prevent from failing if options.stdin() throws an exception
 
1052
                closeStdinHandle();
 
1053
                throw ex;
 
1054
            }
 
1055
        } else {
 
1056
            writeStdin(options.stdin);
 
1057
            closeStdinHandle();
 
1058
        }
 
1059
    }
 
1060
 
 
1061
    //main
 
1062
 
 
1063
    var cmdStr = getCommandStr(options.command);
 
1064
    var workDir = getWorkDir(options.workdir);
 
1065
 
 
1066
    hChildProcess = popen(cmdStr, workDir, options.arguments, options.environment, child);
 
1067
 
 
1068
    readPipes();
 
1069
 
 
1070
    if (options.stdin) {
 
1071
       createStdinWriter();
 
1072
       startWriting();
 
1073
 
 
1074
    }
 
1075
    else
 
1076
        closeStdinHandle();
 
1077
 
 
1078
    return {
 
1079
        kill: function(hardKill) {
 
1080
            // hardKill is currently ignored on Windows
 
1081
            var r = !!TerminateProcess(child.process, 255);
 
1082
            cleanup(-1);
 
1083
            return r;
 
1084
        },
 
1085
        wait: function() {
 
1086
            // wait for async operations to complete
 
1087
            var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
 
1088
            while (!done) thread.processNextEvent(true);
 
1089
 
 
1090
            return exitCode;
 
1091
        }
 
1092
    };
 
1093
}
 
1094
 
 
1095
 
 
1096
function subprocess_unix(options) {
 
1097
    var libc = ctypes.open(options.libc),
 
1098
        active = true,
 
1099
        done = false,
 
1100
        exitCode = -1,
 
1101
        workerExitCode = 0,
 
1102
        child = {},
 
1103
        pid = -1,
 
1104
        stdinWorker = null,
 
1105
        stdoutWorker = null,
 
1106
        stderrWorker = null,
 
1107
        pendingWriteCount = 0,
 
1108
        readers = options.mergeStderr ? 1 : 2,
 
1109
        stdinOpenState = PIPE_STATE_NOT_INIT,
 
1110
        error = '',
 
1111
        output = '';
 
1112
 
 
1113
    //api declarations
 
1114
 
 
1115
    //pid_t fork(void);
 
1116
    var fork = libc.declare("fork",
 
1117
                         ctypes.default_abi,
 
1118
                         pid_t
 
1119
    );
 
1120
 
 
1121
    //int pipe(int pipefd[2]);
 
1122
    var pipefd = ctypes.int.array(2);
 
1123
    var pipe = libc.declare("pipe",
 
1124
                         ctypes.default_abi,
 
1125
                         ctypes.int,
 
1126
                         pipefd
 
1127
    );
 
1128
 
 
1129
    //int dup(int oldfd);
 
1130
    var dup= libc.declare("dup",
 
1131
                          ctypes.default_abi,
 
1132
                          ctypes.int,
 
1133
                          ctypes.int
 
1134
    );
 
1135
 
 
1136
    //int close(int fd);
 
1137
    var close = libc.declare("close",
 
1138
                          ctypes.default_abi,
 
1139
                          ctypes.int,
 
1140
                          ctypes.int
 
1141
    );
 
1142
 
 
1143
    //NULL terminated array of strings, argv[0] will be command >> + 2
 
1144
    var argv = ctypes.char.ptr.array(options.arguments.length + 2);
 
1145
    var envp = ctypes.char.ptr.array(options.environment.length + 1);
 
1146
    var execve = libc.declare("execve",
 
1147
                          ctypes.default_abi,
 
1148
                          ctypes.int,
 
1149
                          ctypes.char.ptr,
 
1150
                          argv,
 
1151
                          envp
 
1152
    );
 
1153
 
 
1154
    //void exit(int status);
 
1155
    var exit = libc.declare("exit",
 
1156
                          ctypes.default_abi,
 
1157
                          ctypes.void_t,
 
1158
                          ctypes.int
 
1159
    );
 
1160
 
 
1161
    //pid_t waitpid(pid_t pid, int *status, int options);
 
1162
    var waitpid = libc.declare("waitpid",
 
1163
                          ctypes.default_abi,
 
1164
                          pid_t,
 
1165
                          pid_t,
 
1166
                          ctypes.int.ptr,
 
1167
                          ctypes.int
 
1168
    );
 
1169
 
 
1170
    //int kill(pid_t pid, int sig);
 
1171
    var kill = libc.declare("kill",
 
1172
                          ctypes.default_abi,
 
1173
                          ctypes.int,
 
1174
                          pid_t,
 
1175
                          ctypes.int
 
1176
    );
 
1177
 
 
1178
    //int read(int fd, void *buf, size_t count);
 
1179
    var bufferSize = 1024;
 
1180
    var buffer = ctypes.char.array(bufferSize);
 
1181
    var read = libc.declare("read",
 
1182
                          ctypes.default_abi,
 
1183
                          ctypes.int,
 
1184
                          ctypes.int,
 
1185
                          buffer,
 
1186
                          ctypes.int
 
1187
    );
 
1188
 
 
1189
    //ssize_t write(int fd, const void *buf, size_t count);
 
1190
    var write = libc.declare("write",
 
1191
                          ctypes.default_abi,
 
1192
                          ctypes.int,
 
1193
                          ctypes.int,
 
1194
                          ctypes.char.ptr,
 
1195
                          ctypes.int
 
1196
    );
 
1197
 
 
1198
    //int chdir(const char *path);
 
1199
    var chdir = libc.declare("chdir",
 
1200
                          ctypes.default_abi,
 
1201
                          ctypes.int,
 
1202
                          ctypes.char.ptr
 
1203
    );
 
1204
 
 
1205
    //int sleep(int);
 
1206
    var sleep = libc.declare("sleep",
 
1207
                          ctypes.default_abi,
 
1208
                          ctypes.int,
 
1209
                          ctypes.int)
 
1210
 
 
1211
 
 
1212
    //int fcntl(int fd, int cmd, ... /* arg */ );
 
1213
    var fcntl = libc.declare("fcntl",
 
1214
                          ctypes.default_abi,
 
1215
                          ctypes.int,
 
1216
                          ctypes.int,
 
1217
                          ctypes.int,
 
1218
                          ctypes.int
 
1219
    );
 
1220
 
 
1221
    var libcWrapper = null,
 
1222
        launchProcess = null;
 
1223
 
 
1224
    if (gLibcWrapper) {
 
1225
      debugLog("Trying to use LibcWrapper\n");
 
1226
 
 
1227
      try {
 
1228
        libcWrapper = ctypes.open(gLibcWrapper);
 
1229
 
 
1230
        launchProcess = libcWrapper.declare("launchProcess",
 
1231
                           ctypes.default_abi,
 
1232
                           pid_t,
 
1233
                           ctypes.char.ptr,
 
1234
                           argv,
 
1235
                           envp,
 
1236
                           ctypes.char.ptr,
 
1237
                           pipefd,
 
1238
                           pipefd,
 
1239
                           pipefd);
 
1240
 
 
1241
      }
 
1242
      catch (ex) {
 
1243
        LogError("could not initialize libc wrapper "+gLibcWrapper+"\n");
 
1244
        LogError(ex+"\n");
 
1245
        gLibcWrapper = null;
 
1246
      }
 
1247
    }
 
1248
 
 
1249
    function popen(command, workdir, args, environment, child) {
 
1250
        var _in,
 
1251
            _out,
 
1252
            _err,
 
1253
            pid,
 
1254
            rc,
 
1255
            i;
 
1256
        _in = new pipefd();
 
1257
        _out = new pipefd();
 
1258
        if(!options.mergeStderr)
 
1259
            _err = new pipefd();
 
1260
 
 
1261
        var _args = argv();
 
1262
        args.unshift(command);
 
1263
        for(i=0;i<args.length;i++) {
 
1264
            _args[i] = ctypes.char.array()(args[i]);
 
1265
        }
 
1266
        var _envp = envp();
 
1267
        for(i=0;i<environment.length;i++) {
 
1268
            _envp[i] = ctypes.char.array()(environment[i]);
 
1269
        }
 
1270
 
 
1271
        rc = pipe(_in);
 
1272
        if (rc < 0) {
 
1273
            return -1;
 
1274
        }
 
1275
        rc = pipe(_out);
 
1276
        fcntl(_out[0], F_SETFL, getPlatformValue(O_NONBLOCK));
 
1277
        if (rc < 0) {
 
1278
            close(_in[0]);
 
1279
            close(_in[1]);
 
1280
            return -1;
 
1281
        }
 
1282
        if(!options.mergeStderr) {
 
1283
            rc = pipe(_err);
 
1284
            fcntl(_err[0], F_SETFL, getPlatformValue(O_NONBLOCK));
 
1285
            if (rc < 0) {
 
1286
                close(_in[0]);
 
1287
                close(_in[1]);
 
1288
                close(_out[0]);
 
1289
                close(_out[1]);
 
1290
                return -1;
 
1291
            }
 
1292
        }
 
1293
 
 
1294
        if (launchProcess) {
 
1295
          pid = launchProcess(command, _args, _envp, workdir,
 
1296
            _in, _out, options.mergeStderr ? null : _err);
 
1297
 
 
1298
          if (pid > 0) { // parent
 
1299
              close(_in[0]);
 
1300
              close(_out[1]);
 
1301
              if(!options.mergeStderr)
 
1302
                  close(_err[1]);
 
1303
              child.stdin  = _in[1];
 
1304
              child.stdinFd = _in;
 
1305
              child.stdout = _out[0];
 
1306
              child.stderr = options.mergeStderr ? undefined : _err[0];
 
1307
              child.pid = pid;
 
1308
          }
 
1309
        }
 
1310
        else {
 
1311
          pid = fork();
 
1312
          if (pid > 0) { // parent
 
1313
              close(_in[0]);
 
1314
              close(_out[1]);
 
1315
              if(!options.mergeStderr)
 
1316
                  close(_err[1]);
 
1317
              child.stdin  = _in[1];
 
1318
              child.stdinFd = _in;
 
1319
              child.stdout = _out[0];
 
1320
              child.stderr = options.mergeStderr ? undefined : _err[0];
 
1321
              child.pid = pid;
 
1322
              return pid;
 
1323
          } else if (pid == 0) { // child
 
1324
              if (workdir) {
 
1325
                  if (chdir(workdir) < 0) {
 
1326
                      exit(126);
 
1327
                  }
 
1328
              }
 
1329
              closeOtherFds(_in[0], _out[1], options.mergeStderr ? _out[1] : _err[1]);
 
1330
              close(_in[1]);
 
1331
              close(_out[0]);
 
1332
              if(!options.mergeStderr)
 
1333
                  close(_err[0]);
 
1334
              close(0);
 
1335
              dup(_in[0]);
 
1336
              close(1);
 
1337
              dup(_out[1]);
 
1338
              close(2);
 
1339
              dup(options.mergeStderr ? _out[1] : _err[1]);
 
1340
              execve(command, _args, _envp);
 
1341
              exit(1);
 
1342
          } else {
 
1343
              // we should not really end up here
 
1344
              if(!options.mergeStderr) {
 
1345
                  close(_err[0]);
 
1346
                  close(_err[1]);
 
1347
              }
 
1348
              close(_out[0]);
 
1349
              close(_out[1]);
 
1350
              close(_in[0]);
 
1351
              close(_in[1]);
 
1352
              throw("Fatal - failed to create subprocess '"+command+"'");
 
1353
          }
 
1354
        }
 
1355
 
 
1356
        return pid;
 
1357
    }
 
1358
 
 
1359
 
 
1360
    // close any file descriptors that are not required for the process
 
1361
    function closeOtherFds(fdIn, fdOut, fdErr) {
 
1362
 
 
1363
        var maxFD = 256; // arbitrary max
 
1364
 
 
1365
 
 
1366
        var rlim_t = getPlatformValue(RLIM_T);
 
1367
 
 
1368
        const RLIMITS = new ctypes.StructType("RLIMITS", [
 
1369
            {"rlim_cur": rlim_t},
 
1370
            {"rlim_max": rlim_t}
 
1371
        ]);
 
1372
 
 
1373
        try {
 
1374
            var getrlimit = libc.declare("getrlimit",
 
1375
                                  ctypes.default_abi,
 
1376
                                  ctypes.int,
 
1377
                                  ctypes.int,
 
1378
                                  RLIMITS.ptr
 
1379
            );
 
1380
 
 
1381
            var rl = new RLIMITS();
 
1382
            if (getrlimit(getPlatformValue(RLIMIT_NOFILE), rl.address()) == 0) {
 
1383
                if (rl.rlim_cur <  Math.pow(2,20)) // ignore too high numbers
 
1384
                  maxFD = rl.rlim_cur;
 
1385
            }
 
1386
            debugLog("getlimit: maxFD="+maxFD+"\n");
 
1387
 
 
1388
        }
 
1389
        catch(ex) {
 
1390
            debugLog("getrlimit: no such function on this OS\n");
 
1391
            debugLog(ex.toString());
 
1392
        }
 
1393
 
 
1394
        // close any file descriptors
 
1395
        // fd's 0-2 are already closed
 
1396
        for (var i = 3; i < maxFD; i++) {
 
1397
            if (i != fdIn && i != fdOut && i != fdErr)
 
1398
                close(i);
 
1399
        }
 
1400
    }
 
1401
 
 
1402
    /*
 
1403
     * createStdinWriter ()
 
1404
     *
 
1405
     * Create a ChromeWorker object for writing data to the subprocess' stdin
 
1406
     * pipe. The ChromeWorker object lives on a separate thread; this avoids
 
1407
     * internal deadlocks.
 
1408
     */
 
1409
    function createStdinWriter() {
 
1410
        debugLog("Creating new stdin worker\n");
 
1411
        stdinWorker = new ChromeWorker("subprocess_worker_unix.js");
 
1412
        stdinWorker.onmessage = function(event) {
 
1413
            switch (event.data.msg) {
 
1414
            case "info":
 
1415
                switch(event.data.data) {
 
1416
                case "WriteOK":
 
1417
                    pendingWriteCount--;
 
1418
                    debugLog("got OK from stdinWorker - remaining count: "+pendingWriteCount+"\n");
 
1419
                    break;
 
1420
                case "InitOK":
 
1421
                    stdinOpenState = PIPE_STATE_OPEN;
 
1422
                    debugLog("Stdin pipe opened\n");
 
1423
                    break;
 
1424
                case "ClosedOK":
 
1425
                    stdinOpenState = PIPE_STATE_CLOSED;
 
1426
                    debugLog("Stdin pipe closed\n");
 
1427
                    break;
 
1428
                default:
 
1429
                    debugLog("got msg from stdinWorker: "+event.data.data+"\n");
 
1430
                }
 
1431
                break;
 
1432
            case "debug":
 
1433
                debugLog("stdinWorker: "+event.data.data+"\n");
 
1434
                break;
 
1435
            case "error":
 
1436
                LogError("got error from stdinWorker: "+event.data.data+"\n");
 
1437
                pendingWriteCount = 0;
 
1438
                stdinOpenState = PIPE_STATE_CLOSED;
 
1439
                exitCode = -2;
 
1440
            }
 
1441
        };
 
1442
        stdinWorker.onerror = function(error) {
 
1443
            pendingWriteCount = 0;
 
1444
            exitCode = -2;
 
1445
            closeStdinHandle();
 
1446
            LogError("got error from stdinWorker: "+error.message+"\n");
 
1447
        };
 
1448
        stdinWorker.postMessage({msg: "init", libc: options.libc});
 
1449
    }
 
1450
 
 
1451
    /*
 
1452
     * writeStdin()
 
1453
     * @data: String containing the data to write
 
1454
     *
 
1455
     * Write data to the subprocess' stdin (equals to sending a request to the
 
1456
     * ChromeWorker object to write the data).
 
1457
     */
 
1458
    function writeStdin(data) {
 
1459
        if (stdinOpenState == PIPE_STATE_CLOSED) {
 
1460
          LogError("trying to write data to closed stdin");
 
1461
          return;
 
1462
        }
 
1463
 
 
1464
        ++pendingWriteCount;
 
1465
        debugLog("sending "+data.length+" bytes to stdinWorker\n");
 
1466
        var pipe = parseInt(child.stdin);
 
1467
 
 
1468
        stdinWorker.postMessage({
 
1469
            msg: 'write',
 
1470
            pipe: pipe,
 
1471
            data: data
 
1472
        });
 
1473
    }
 
1474
 
 
1475
 
 
1476
    /*
 
1477
     * closeStdinHandle()
 
1478
     *
 
1479
     * Close the stdin pipe, either directly or by requesting the ChromeWorker to
 
1480
     * close the pipe. The ChromeWorker will only close the pipe after the last write
 
1481
     * request process is done.
 
1482
     */
 
1483
 
 
1484
    function closeStdinHandle() {
 
1485
        debugLog("trying to close stdin\n");
 
1486
        if (stdinOpenState != PIPE_STATE_OPEN) return;
 
1487
        stdinOpenState = PIPE_STATE_CLOSEABLE;
 
1488
 
 
1489
        if (stdinWorker) {
 
1490
            debugLog("sending close stdin to worker\n");
 
1491
            var pipePtr = parseInt(child.stdin);
 
1492
 
 
1493
            stdinWorker.postMessage({
 
1494
                msg: 'close',
 
1495
                pipe: pipePtr
 
1496
            });
 
1497
        }
 
1498
        else {
 
1499
            stdinOpenState = PIPE_STATE_CLOSED;
 
1500
            debugLog("Closing Stdin\n");
 
1501
            close(child.stdin) && LogError("CloseHandle stdin failed");
 
1502
        }
 
1503
    }
 
1504
 
 
1505
 
 
1506
    /*
 
1507
     * createReader(pipe, name)
 
1508
     *
 
1509
     * @pipe: handle to the pipe
 
1510
     * @name: String containing the pipe name (stdout or stderr)
 
1511
     * @callbackFunc: function to be called with the read data
 
1512
     *
 
1513
     * Create a ChromeWorker object for reading data asynchronously from
 
1514
     * the pipe (i.e. on a separate thread), and passing the result back to
 
1515
     * the caller.
 
1516
     *
 
1517
     */
 
1518
    function createReader(pipe, name, callbackFunc) {
 
1519
        var worker = new ChromeWorker("subprocess_worker_unix.js");
 
1520
        worker.onmessage = function(event) {
 
1521
            switch(event.data.msg) {
 
1522
            case "data":
 
1523
                debugLog("got "+event.data.count+" bytes from "+name+"\n");
 
1524
                var data = '';
 
1525
                if (options.charset === null) {
 
1526
                    data = event.data.data;
 
1527
                }
 
1528
                else
 
1529
                    data = convertBytes(event.data.data, options.charset);
 
1530
 
 
1531
                callbackFunc(data);
 
1532
                break;
 
1533
            case "done":
 
1534
                debugLog("Pipe "+name+" closed\n");
 
1535
                if (event.data.data != 0) workerExitCode = event.data.data;
 
1536
                --readers;
 
1537
                if (readers == 0) cleanup();
 
1538
                break;
 
1539
            case "error":
 
1540
                LogError("Got error from "+name+": "+event.data.data);
 
1541
                exitCode = -2;
 
1542
                break;
 
1543
            default:
 
1544
                debugLog("Got msg from "+name+": "+event.data.data+"\n");
 
1545
            }
 
1546
        };
 
1547
        worker.onerror = function(error) {
 
1548
            LogError("Got error from "+name+": "+error.message);
 
1549
            exitCode = -2;
 
1550
        };
 
1551
 
 
1552
        worker.postMessage({
 
1553
                msg: 'read',
 
1554
                pipe: pipe,
 
1555
                pid: pid,
 
1556
                libc: options.libc,
 
1557
                charset: options.charset === null ? "null" : options.charset,
 
1558
                bufferedOutput: options.bufferedOutput,
 
1559
                name: name
 
1560
            });
 
1561
 
 
1562
        return worker;
 
1563
    }
 
1564
 
 
1565
    /*
 
1566
     * readPipes()
 
1567
     *
 
1568
     * Open the pipes for reading from stdout and stderr
 
1569
     */
 
1570
    function readPipes() {
 
1571
 
 
1572
        stdoutWorker = createReader(child.stdout, "stdout", function (data) {
 
1573
            if(options.stdout) {
 
1574
                setTimeout(function() {
 
1575
                    options.stdout(data);
 
1576
                }, 0);
 
1577
            } else {
 
1578
                output += data;
 
1579
            }
 
1580
        });
 
1581
 
 
1582
        if (!options.mergeStderr) stderrWorker = createReader(child.stderr, "stderr", function (data) {
 
1583
            if(options.stderr) {
 
1584
                setTimeout(function() {
 
1585
                    options.stderr(data);
 
1586
                 }, 0);
 
1587
            } else {
 
1588
                error += data;
 
1589
            }
 
1590
        });
 
1591
    }
 
1592
 
 
1593
    function cleanup() {
 
1594
        debugLog("Cleanup called\n");
 
1595
        if(active) {
 
1596
            active = false;
 
1597
 
 
1598
            closeStdinHandle(); // should only be required in case of errors
 
1599
 
 
1600
            var result, status = ctypes.int();
 
1601
            result = waitpid(child.pid, status.address(), 0);
 
1602
 
 
1603
            if (exitCode > -2) {
 
1604
              if (result > 0)
 
1605
                  exitCode = status.value;
 
1606
              else
 
1607
                  if (workerExitCode >= 0)
 
1608
                      exitCode = workerExitCode;
 
1609
                  else
 
1610
                      exitCode = status.value;
 
1611
            }
 
1612
 
 
1613
            if (stdinWorker)
 
1614
                stdinWorker.postMessage({msg: 'stop'});
 
1615
 
 
1616
            setTimeout(function _done() {
 
1617
                if (options.done) {
 
1618
                    try {
 
1619
                        options.done({
 
1620
                            exitCode: exitCode,
 
1621
                            stdout: output,
 
1622
                            stderr: error
 
1623
                        });
 
1624
                    }
 
1625
                    catch(ex) {
 
1626
                        // prevent from blocking if options.done() throws an error
 
1627
                        done = true;
 
1628
                        throw ex;
 
1629
                    }
 
1630
 
 
1631
                }
 
1632
                done = true;
 
1633
            }, 0);
 
1634
 
 
1635
            libc.close();
 
1636
        }
 
1637
    }
 
1638
 
 
1639
 
 
1640
    function startWriting() {
 
1641
        debugLog("startWriting called\n");
 
1642
 
 
1643
        if (stdinOpenState == PIPE_STATE_NOT_INIT) {
 
1644
          setTimeout(function _f() {
 
1645
              startWriting();
 
1646
            }, 2);
 
1647
          return;
 
1648
        }
 
1649
 
 
1650
        if(typeof(options.stdin) == 'function') {
 
1651
            try {
 
1652
                options.stdin({
 
1653
                    write: function(data) {
 
1654
                        writeStdin(data);
 
1655
                    },
 
1656
                    close: function() {
 
1657
                        closeStdinHandle();
 
1658
                    }
 
1659
                });
 
1660
            }
 
1661
            catch(ex) {
 
1662
                // prevent from failing if options.stdin() throws an exception
 
1663
                closeStdinHandle();
 
1664
                throw ex;
 
1665
            }
 
1666
        } else {
 
1667
            writeStdin(options.stdin);
 
1668
            closeStdinHandle();
 
1669
        }
 
1670
    }
 
1671
    //main
 
1672
 
 
1673
    var cmdStr = getCommandStr(options.command);
 
1674
    var workDir = getWorkDir(options.workdir);
 
1675
 
 
1676
    child = {};
 
1677
    pid = popen(cmdStr, workDir, options.arguments, options.environment, child);
 
1678
 
 
1679
    debugLog("subprocess started; got PID "+pid+"\n");
 
1680
 
 
1681
    readPipes();
 
1682
 
 
1683
    if (options.stdin) {
 
1684
        createStdinWriter();
 
1685
        startWriting();
 
1686
    }
 
1687
    else
 
1688
        closeStdinHandle();
 
1689
 
 
1690
 
 
1691
    return {
 
1692
        wait: function() {
 
1693
            // wait for async operations to complete
 
1694
            var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
 
1695
            while (! done) thread.processNextEvent(true);
 
1696
            return exitCode;
 
1697
        },
 
1698
        kill: function(hardKill) {
 
1699
            var rv = kill(pid, (hardKill ? 9: 15));
 
1700
            cleanup(-1);
 
1701
            return rv;
 
1702
        }
 
1703
    };
 
1704
}