# io/exec.js

1 | // Copyright 2014 Selenium committers
2 | // Copyright 2014 Software Freedom Conservancy
3 | //
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 at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // 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 and
14 | // limitations under the License.
15 |
16 | 'use strict';
17 |
18 | var childProcess = require('child_process');
19 |
20 | var promise = require('..').promise;
21 |
22 |
23 | /**
24 | * A hash with configuration options for an executed command.
25 | *
26 | * - `args` - Command line arguments.
27 | * - `env` - Command environment; will inherit from the current process if
28 | * missing.
29 | * - `stdio` - IO configuration for the spawned server process. For more
30 | * information, refer to the documentation of `child_process.spawn`.
31 | *
32 | * @typedef {{
33 | * args: (!Array.<string>|undefined),
34 | * env: (!Object.<string, string>|undefined),
35 | * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
36 | * }}
37 | */
38 | var Options;
39 |
40 |
41 | /**
42 | * Describes a command's termination conditions.
43 | * @param {?number} code The exit code, or {@code null} if the command did not
44 | * exit normally.
45 | * @param {?string} signal The signal used to kill the command, or
46 | * {@code null}.
47 | * @constructor
48 | */
49 | var Result = function(code, signal) {
50 | /** @type {?number} */
51 | this.code = code;
52 |
53 | /** @type {?string} */
54 | this.signal = signal;
55 | };
56 |
57 |
58 | /** @override */
59 | Result.prototype.toString = function() {
60 | return 'Result(code=' + this.code + ', signal=' + this.signal + ')';
61 | };
62 |
63 |
64 |
65 | /**
66 | * Represents a command running in a sub-process.
67 | * @param {!promise.Promise.<!Result>} result The command result.
68 | * @constructor
69 | */
70 | var Command = function(result, onKill) {
71 | /** @return {boolean} Whether this command is still running. */
72 | this.isRunning = function() {
73 | return result.isPending();
74 | };
75 |
76 | /**
77 | * @return {!promise.Promise.<!Result>} A promise for the result of this
78 | * command.
79 | */
80 | this.result = function() {
81 | return result;
82 | };
83 |
84 | /**
85 | * Sends a signal to the underlying process.
86 | * @param {string=} opt_signal The signal to send; defaults to
87 | * {@code SIGTERM}.
88 | */
89 | this.kill = function(opt_signal) {
90 | onKill(opt_signal || 'SIGTERM');
91 | };
92 | };
93 |
94 |
95 | // PUBLIC API
96 |
97 |
98 | /**
99 | * Spawns a child process. The returned {@link Command} may be used to wait
100 | * for the process result or to send signals to the process.
101 | *
102 | * @param {string} command The executable to spawn.
103 | * @param {Options=} opt_options The command options.
104 | * @return {!Command} The launched command.
105 | */
106 | module.exports = function(command, opt_options) {
107 | var options = opt_options || {};
108 |
109 | var proc = childProcess.spawn(command, options.args || [], {
110 | env: options.env || process.env,
111 | stdio: options.stdio || 'ignore'
112 | }).once('exit', onExit);
113 |
114 | // This process should not wait on the spawned child, however, we do
115 | // want to ensure the child is killed when this process exits.
116 | proc.unref();
117 | process.once('exit', killCommand);
118 |
119 | var result = promise.defer();
120 | var cmd = new Command(result.promise, function(signal) {
121 | if (!result.isPending() || !proc) {
122 | return; // No longer running.
123 | }
124 | proc.kill(signal);
125 | });
126 | return cmd;
127 |
128 | function onExit(code, signal) {
129 | proc = null;
130 | process.removeListener('exit', killCommand);
131 | result.fulfill(new Result(code, signal));
132 | }
133 |
134 | function killCommand() {
135 | process.removeListener('exit', killCommand);
136 | proc && proc.kill('SIGTERM');
137 | }
138 | };