# io/exec.js

    1// Copyright 2014 Selenium committers2// Copyright 2014 Software Freedom Conservancy3//4// Licensed under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6//     You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516'use strict';1718var childProcess = require('child_process');1920var promise = require('..').promise;212223/**24 * A hash with configuration options for an executed command.25 * <ul>26 * <li>27 * <li>{@code args} - Command line arguments.28 * <li>{@code env} - Command environment; will inherit from the current process29 *     if missing.30 * <li>{@code stdio} - IO configuration for the spawned server process. For31 *     more information, refer to the documentation of32 *     {@code child_process.spawn}.33 * </ul>34 *35 * @typedef {{36 *   args: (!Array.<string>|undefined),37 *   env: (!Object.<string, string>|undefined),38 *   stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)39 * }}40 */41var Options;424344/**45 * Describes a command's termination conditions.46 * @param {?number} code The exit code, or {@code null} if the command did not47 *     exit normally.48 * @param {?string} signal The signal used to kill the command, or49 *     {@code null}.50 * @constructor51 */52var Result = function(code, signal) {53  /** @type {?number} */54  this.code = code;5556  /** @type {?string} */57  this.signal = signal;58};596061/** @override */62Result.prototype.toString = function() {63  return 'Result(code=' + this.code + ', signal=' + this.signal + ')';64};65666768/**69 * Represents a command running in a sub-process.70 * @param {!promise.Promise.<!Result>} result The command result.71 * @constructor72 */73var Command = function(result, onKill) {74  /** @return {boolean} Whether this command is still running. */75  this.isRunning = function() {76    return result.isPending();77  };7879  /**80   * @return {!promise.Promise.<!Result>} A promise for the result of this81   *     command.82   */83  this.result = function() {84    return result;85  };8687  /**88   * Sends a signal to the underlying process.89   * @param {string=} opt_signal The signal to send; defaults to90   *     {@code SIGTERM}.91   */92  this.kill = function(opt_signal) {93    onKill(opt_signal || 'SIGTERM');94  };95};969798// PUBLIC API99100101/**102 * Spawns a child process. The returned {@link Command} may be used to wait103 * for the process result or to send signals to the process.104 *105 * @param {string} command The executable to spawn.106 * @param {Options=} opt_options The command options.107 * @return {!Command} The launched command.108 */109module.exports = function(command, opt_options) {110  var options = opt_options || {};111112  var proc = childProcess.spawn(command, options.args || [], {113    env: options.env || process.env,114    stdio: options.stdio || 'ignore'115  }).once('exit', onExit);116117  // This process should not wait on the spawned child, however, we do118  // want to ensure the child is killed when this process exits.119  proc.unref();120  process.once('exit', killCommand);121122  var result = promise.defer();123  var cmd = new Command(result.promise, function(signal) {124    if (!result.isPending() || !proc) {125      return;  // No longer running.126    }127    proc.kill(signal);128  });129  return cmd;130131  function onExit(code, signal) {132    proc = null;133    process.removeListener('exit', killCommand);134    result.fulfill(new Result(code, signal));135  }136137  function killCommand() {138    process.removeListener('exit', killCommand);139    proc && proc.kill('SIGTERM');140  }141};