# io/exec.js

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