addon-sdk/source/lib/sdk/system/child_process.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     4 'use strict';
     6 module.metadata = {
     7   'stability': 'experimental'
     8 };
    10 let { Ci } = require('chrome');
    11 let subprocess = require('./child_process/subprocess');
    12 let { EventTarget } = require('../event/target');
    13 let { Stream } = require('../io/stream');
    14 let { on, emit, off } = require('../event/core');
    15 let { Class } = require('../core/heritage');
    16 let { platform } = require('../system');
    17 let { isFunction, isArray } = require('../lang/type');
    18 let { delay } = require('../lang/functional');
    19 let { merge } = require('../util/object');
    20 let { setTimeout, clearTimeout } = require('../timers');
    21 let isWindows = platform.indexOf('win') === 0;
    23 let processes = WeakMap();
    26 /**
    27  * The `Child` class wraps a subprocess command, exposes
    28  * the stdio streams, and methods to manipulate the subprocess
    29  */
    30 let Child = Class({
    31   implements: [EventTarget],
    32   initialize: function initialize (options) {
    33     let child = this;
    34     let proc;
    36     this.killed = false;
    37     this.exitCode = undefined;
    38     this.signalCode = undefined;
    40     this.stdin = Stream();
    41     this.stdout = Stream();
    42     this.stderr = Stream();
    44     try {
    45       proc = subprocess.call({
    46         command: options.file,
    47         arguments: options.cmdArgs,
    48         environment: serializeEnv(options.env),
    49         workdir: options.cwd,
    50         charset: options.encoding,
    51         stdout: data => emit(child.stdout, 'data', data),
    52         stderr: data => emit(child.stderr, 'data', data),
    53         stdin: stream => {
    54           child.stdin.on('data', pumpStdin);
    55           child.stdin.on('end', function closeStdin () {
    56             child.stdin.off('data', pumpStdin);
    57             child.stdin.off('end', closeStdin);
    58             stream.close();
    59           });
    60           function pumpStdin (data) {
    61             stream.write(data);
    62           }
    63         },
    64         done: function (result) {
    65           // Only emit if child is not killed; otherwise,
    66           // the `kill` method will handle this
    67           if (!child.killed) {
    68             child.exitCode = result.exitCode;
    69             child.signalCode = null;
    71             // If process exits with < 0, there was an error
    72             if (child.exitCode < 0) {
    73               handleError(new Error('Process exited with exit code ' + child.exitCode));
    74             }
    75             else {
    76               // Also do 'exit' event as there's not much of
    77               // a difference in our implementation as we're not using
    78               // node streams
    79               emit(child, 'exit', child.exitCode, child.signalCode);
    80             }
    82             // Emit 'close' event with exit code and signal,
    83             // which is `null`, as it was not a killed process
    84             emit(child, 'close', child.exitCode, child.signalCode);
    85           }
    86         }
    87       });
    88       processes.set(child, proc);
    89     } catch (e) {
    90       // Delay the error handling so an error handler can be set
    91       // during the same tick that the Child was created
    92       delay(() => handleError(e));
    93     }
    95     // `handleError` is called when process could not even
    96     // be spawned
    97     function handleError (e) {
    98       // If error is an nsIObject, make a fresh error object
    99       // so we're not exposing nsIObjects, and we can modify it
   100       // with additional process information, like node
   101       let error = e;
   102       if (e instanceof Ci.nsISupports) {
   103         error = new Error(e.message, e.filename, e.lineNumber);
   104       }
   105       emit(child, 'error', error);
   106       child.exitCode = -1;
   107       child.signalCode = null;
   108       emit(child, 'close', child.exitCode, child.signalCode);
   109     }
   110   },
   111   kill: function kill (signal) {
   112     let proc = processes.get(this);
   113     proc.kill(signal);
   114     this.killed = true;
   115     this.exitCode = null;
   116     this.signalCode = signal;
   117     emit(this, 'exit', this.exitCode, this.signalCode);
   118     emit(this, 'close', this.exitCode, this.signalCode);
   119   },
   120   get pid() { return processes.get(this, {}).pid || -1; }
   121 });
   123 function spawn (file, ...args) {
   124   let cmdArgs = [];
   125   // Default options
   126   let options = {
   127     cwd: null,
   128     env: null,
   129     encoding: 'UTF-8'
   130   };
   132   if (args[1]) {
   133     merge(options, args[1]);
   134     cmdArgs = args[0];
   135   }
   136   else {
   137     if (isArray(args[0]))
   138       cmdArgs = args[0];
   139     else
   140       merge(options, args[0]);
   141   }
   143   if ('gid' in options)
   144     console.warn('`gid` option is not yet supported for `child_process`');
   145   if ('uid' in options)
   146     console.warn('`uid` option is not yet supported for `child_process`');
   147   if ('detached' in options)
   148     console.warn('`detached` option is not yet supported for `child_process`');
   150   options.file = file;
   151   options.cmdArgs = cmdArgs;
   153   return Child(options);
   154 }
   156 exports.spawn = spawn;
   158 /**
   159  * exec(command, options, callback)
   160  */
   161 function exec (cmd, ...args) {
   162   let file, cmdArgs, callback, options = {};
   164   if (isFunction(args[0]))
   165     callback = args[0];
   166   else {
   167     merge(options, args[0]);
   168     callback = args[1];
   169   }
   171   if (isWindows) {
   172     file = 'C:\\Windows\\System32\\cmd.exe';
   173     cmdArgs = ['/s', '/c', (cmd || '').split(' ')];
   174   }
   175   else {
   176     file = '/bin/sh';
   177     cmdArgs = ['-c', cmd];
   178   }
   180   // Undocumented option from node being able to specify shell
   181   if (options && options.shell)
   182     file = options.shell;
   184   return execFile(file, cmdArgs, options, callback);
   185 }
   186 exports.exec = exec;
   187 /**
   188  * execFile (file, args, options, callback)
   189  */
   190 function execFile (file, ...args) {
   191   let cmdArgs = [], callback;
   192   // Default options
   193   let options = {
   194     cwd: null,
   195     env: null,
   196     encoding: 'utf8',
   197     timeout: 0,
   198     maxBuffer: 200 * 1024,
   199     killSignal: 'SIGTERM'
   200   };
   202   if (isFunction(args[args.length - 1]))
   203     callback = args[args.length - 1];
   205   if (isArray(args[0])) {
   206     cmdArgs = args[0];
   207     merge(options, args[1]);
   208   } else if (!isFunction(args[0]))
   209     merge(options, args[0]);
   211   let child = spawn(file, cmdArgs, options);
   212   let exited = false;
   213   let stdout = '';
   214   let stderr = '';
   215   let error = null;
   216   let timeoutId = null;
   218   child.stdout.setEncoding(options.encoding);
   219   child.stderr.setEncoding(options.encoding);
   221   on(child.stdout, 'data', pumpStdout);
   222   on(child.stderr, 'data', pumpStderr);
   223   on(child, 'close', exitHandler);
   224   on(child, 'error', errorHandler);
   226   if (options.timeout > 0) {
   227     setTimeout(() => {
   228       kill();
   229       timeoutId = null;
   230     }, options.timeout);
   231   }
   233   function exitHandler (code, signal) {
   235     // Return if exitHandler called previously, occurs
   236     // when multiple maxBuffer errors thrown and attempt to kill multiple
   237     // times
   238     if (exited) return;
   239     exited = true;
   241     if (!isFunction(callback)) return;
   243     if (timeoutId) {
   244       clearTimeout(timeoutId);
   245       timeoutId = null;
   246     }
   248     if (!error && (code !== 0 || signal !== null))
   249       error = createProcessError(new Error('Command failed: ' + stderr), {
   250         code: code,
   251         signal: signal,
   252         killed: !!child.killed
   253       });
   255     callback(error, stdout, stderr);
   257     off(child.stdout, 'data', pumpStdout);
   258     off(child.stderr, 'data', pumpStderr);
   259     off(child, 'close', exitHandler);
   260     off(child, 'error', errorHandler);
   261   }
   263   function errorHandler (e) {
   264     error = e;
   265     exitHandler();
   266   }
   268   function kill () {
   269     try {
   270       child.kill(options.killSignal);
   271     } catch (e) {
   272       // In the scenario where the kill signal happens when
   273       // the process is already closing, just abort the kill fail
   274       if (/library is not open/.test(e))
   275         return;
   276       error = e;
   277       exitHandler(-1, options.killSignal);
   278     }
   279   }
   281   function pumpStdout (data) {
   282     stdout += data;
   283     if (stdout.length > options.maxBuffer) {
   284       error = new Error('stdout maxBuffer exceeded');
   285       kill();
   286     }
   287   }
   289   function pumpStderr (data) {
   290     stderr += data;
   291     if (stderr.length > options.maxBuffer) {
   292       error = new Error('stderr maxBuffer exceeded');
   293       kill();
   294     }
   295   }
   297   return child;
   298 }
   299 exports.execFile = execFile;
   301 exports.fork = function fork () {
   302   throw new Error("child_process#fork is not currently supported");
   303 };
   305 function serializeEnv (obj) {
   306   return Object.keys(obj || {}).map(prop => prop + '=' + obj[prop]);
   307 }
   309 function createProcessError (err, options = {}) {
   310   // If code and signal look OK, this was probably a failure
   311   // attempting to spawn the process (like ENOENT in node) -- use
   312   // the code from the error message
   313   if (!options.code && !options.signal) {
   314     let match = err.message.match(/(NS_ERROR_\w*)/);
   315     if (match && match.length > 1)
   316       err.code = match[1];
   317     else {
   318       // If no good error message found, use the passed in exit code;
   319       // this occurs when killing a process that's already closing,
   320       // where we want both a valid exit code (0) and the error
   321       err.code = options.code != null ? options.code : null;
   322     }
   323   }
   324   else
   325     err.code = options.code != null ? options.code : null;
   326   err.signal = options.signal || null;
   327   err.killed = options.killed || false;
   328   return err;
   329 }

mercurial