summaryrefslogtreecommitdiff
path: root/lib/child_process.js
diff options
context:
space:
mode:
authorTimothy J Fontaine <tjfontaine@gmail.com>2014-02-10 21:40:48 +0100
committerBert Belder <bertbelder@gmail.com>2014-02-10 21:41:44 +0100
commit297fee70b978ee0c5fa492b5656109ca1a087239 (patch)
treee2beed9f46476480ac270471b5eea8505c0e0521 /lib/child_process.js
parentfa4eb47caacde4435c16f4ebef0c4f3fa001ccd2 (diff)
downloadnode-execSync-wip.tar.gz
child_process: js bits for spawnSync/execSyncexecSync-wip
This implements the user-facing APIs that lets one run a child process and block until it exits. Some logic that these new functions had in common with the existing spawn/exec/execFile implementation was refactored into separate functions, so it could be shared. Docs and tests are included.
Diffstat (limited to 'lib/child_process.js')
-rw-r--r--lib/child_process.js376
1 files changed, 296 insertions, 80 deletions
diff --git a/lib/child_process.js b/lib/child_process.js
index d32b13d03..1f121d901 100644
--- a/lib/child_process.js
+++ b/lib/child_process.js
@@ -29,6 +29,7 @@ var util = require('util');
var Process = process.binding('process_wrap').Process;
var uv = process.binding('uv');
+var spawn_sync; // Lazy-loaded process.binding('spawn_sync')
var constants; // Lazy-loaded process.binding('constants')
var errnoException = util._errnoException;
@@ -505,7 +506,7 @@ function setupChannel(target, channel) {
// queue is flushed.
if (!this._handleQueue)
this._disconnect();
- }
+ };
target._disconnect = function() {
assert(this._channel);
@@ -585,7 +586,7 @@ exports._forkChild = function(fd) {
};
-exports.exec = function(command /*, options, callback */) {
+function normalizeExecArgs(command /*, options, callback */) {
var file, args, options, callback;
if (util.isFunction(arguments[1])) {
@@ -611,7 +612,22 @@ exports.exec = function(command /*, options, callback */) {
if (options && options.shell)
file = options.shell;
- return exports.execFile(file, args, options, callback);
+ return {
+ cmd: command,
+ file: file,
+ args: args,
+ options: options,
+ callback: callback
+ };
+}
+
+
+exports.exec = function(command /*, options, callback */) {
+ var opts = normalizeExecArgs.apply(null, arguments);
+ return exports.execFile(opts.file,
+ opts.args,
+ opts.options,
+ opts.callback);
};
@@ -777,8 +793,132 @@ exports.execFile = function(file /* args, options, callback */) {
};
-var spawn = exports.spawn = function(file /*, args, options*/) {
+function _convertCustomFds(options) {
+ if (options && options.customFds && !options.stdio) {
+ options.stdio = options.customFds.map(function(fd) {
+ return fd === -1 ? 'pipe' : fd;
+ });
+ }
+}
+
+
+function _validateStdio(stdio, sync) {
+ var ipc,
+ ipcFd;
+
+ // Replace shortcut with an array
+ if (util.isString(stdio)) {
+ switch (stdio) {
+ case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break;
+ case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break;
+ case 'inherit': stdio = [0, 1, 2]; break;
+ default: throw new TypeError('Incorrect value of stdio option: ' + stdio);
+ }
+ } else if (!util.isArray(stdio)) {
+ throw new TypeError('Incorrect value of stdio option: ' +
+ util.inspect(stdio));
+ }
+
+ // At least 3 stdio will be created
+ // Don't concat() a new Array() because it would be sparse, and
+ // stdio.reduce() would skip the sparse elements of stdio.
+ // See http://stackoverflow.com/a/5501711/3561
+ while (stdio.length < 3) stdio.push(undefined);
+
+ // Translate stdio into C++-readable form
+ // (i.e. PipeWraps or fds)
+ stdio = stdio.reduce(function(acc, stdio, i) {
+ function cleanup() {
+ acc.filter(function(stdio) {
+ return stdio.type === 'pipe' || stdio.type === 'ipc';
+ }).forEach(function(stdio) {
+ if (stdio.handle)
+ stdio.handle.close();
+ });
+ }
+
+ // Defaults
+ if (util.isNullOrUndefined(stdio)) {
+ stdio = i < 3 ? 'pipe' : 'ignore';
+ }
+
+ if (stdio === null || stdio === 'ignore') {
+ acc.push({type: 'ignore'});
+ } else if (stdio === 'pipe' || util.isNumber(stdio) && stdio < 0) {
+ var a = {
+ type: 'pipe',
+ readable: i === 0,
+ writable: i !== 0
+ };
+
+ if (!sync)
+ a.handle = createPipe();
+
+ acc.push(a);
+ } else if (stdio === 'ipc') {
+ if (sync || !util.isUndefined(ipc)) {
+ // Cleanup previously created pipes
+ cleanup();
+ if (!sync)
+ throw Error('Child process can have only one IPC pipe');
+ else
+ throw Error('You cannot use IPC with synchronous forks');
+ }
+
+ ipc = createPipe(true);
+ ipcFd = i;
+
+ acc.push({
+ type: 'pipe',
+ handle: ipc,
+ ipc: true
+ });
+ } else if (stdio === 'inherit') {
+ acc.push({
+ type: 'inherit',
+ fd: i
+ });
+ } else if (util.isNumber(stdio) || util.isNumber(stdio.fd)) {
+ acc.push({
+ type: 'fd',
+ fd: stdio.fd || stdio
+ });
+ } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) ||
+ getHandleWrapType(stdio._handle)) {
+ var handle = getHandleWrapType(stdio) ?
+ stdio :
+ getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle;
+
+ acc.push({
+ type: 'wrap',
+ wrapType: getHandleWrapType(handle),
+ handle: handle
+ });
+ } else if (util.isBuffer(stdio) || util.isString(stdio)) {
+ if (!sync) {
+ cleanup();
+ throw new TypeError('Asynchronous forks do not support Buffer input: ' +
+ util.inspect(stdio));
+ }
+ } else {
+ // Cleanup
+ cleanup();
+ throw new TypeError('Incorrect value for stdio stream: ' +
+ util.inspect(stdio));
+ }
+
+ return acc;
+ }, []);
+
+ return {stdio: stdio, ipc: ipc, ipcFd: ipcFd};
+}
+
+
+function normalizeSpawnArguments(/*file, args, options*/) {
var args, options;
+
+ var file = arguments[0];
+
if (Array.isArray(arguments[1])) {
args = arguments[1].slice(0);
options = arguments[2];
@@ -787,6 +927,9 @@ var spawn = exports.spawn = function(file /*, args, options*/) {
options = arguments[1];
}
+ if (!options)
+ options = {};
+
args.unshift(file);
var env = (options ? options.env : null) || process.env;
@@ -795,12 +938,26 @@ var spawn = exports.spawn = function(file /*, args, options*/) {
envPairs.push(key + '=' + env[key]);
}
+ _convertCustomFds(options);
+
+ return {
+ file: file,
+ args: args,
+ options: options,
+ envPairs: envPairs
+ };
+}
+
+
+var spawn = exports.spawn = function(/*file, args, options*/) {
+ var opts = normalizeSpawnArguments.apply(null, arguments);
+
+ var file = opts.file;
+ var args = opts.args;
+ var options = opts.options;
+ var envPairs = opts.envPairs;
+
var child = new ChildProcess();
- if (options && options.customFds && !options.stdio) {
- options.stdio = options.customFds.map(function(fd) {
- return fd === -1 ? 'pipe' : fd;
- });
- }
child.spawn({
file: file,
@@ -923,78 +1080,11 @@ ChildProcess.prototype.spawn = function(options) {
// If no `stdio` option was given - use default
stdio = options.stdio || 'pipe';
- // Replace shortcut with an array
- if (util.isString(stdio)) {
- switch (stdio) {
- case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break;
- case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break;
- case 'inherit': stdio = [0, 1, 2]; break;
- default: throw new TypeError('Incorrect value of stdio option: ' + stdio);
- }
- } else if (!util.isArray(stdio)) {
- throw new TypeError('Incorrect value of stdio option: ' + stdio);
- }
-
- // At least 3 stdio will be created
- // Don't concat() a new Array() because it would be sparse, and
- // stdio.reduce() would skip the sparse elements of stdio.
- // See http://stackoverflow.com/a/5501711/3561
- while (stdio.length < 3) stdio.push(undefined);
-
- // Translate stdio into C++-readable form
- // (i.e. PipeWraps or fds)
- stdio = stdio.reduce(function(acc, stdio, i) {
- function cleanup() {
- acc.filter(function(stdio) {
- return stdio.type === 'pipe' || stdio.type === 'ipc';
- }).forEach(function(stdio) {
- stdio.handle.close();
- });
- }
-
- // Defaults
- if (util.isNullOrUndefined(stdio)) {
- stdio = i < 3 ? 'pipe' : 'ignore';
- }
-
- if (stdio === 'ignore') {
- acc.push({type: 'ignore'});
- } else if (stdio === 'pipe' || util.isNumber(stdio) && stdio < 0) {
- acc.push({type: 'pipe', handle: createPipe()});
- } else if (stdio === 'ipc') {
- if (!util.isUndefined(ipc)) {
- // Cleanup previously created pipes
- cleanup();
- throw Error('Child process can have only one IPC pipe');
- }
-
- ipc = createPipe(true);
- ipcFd = i;
-
- acc.push({ type: 'pipe', handle: ipc, ipc: true });
- } else if (util.isNumber(stdio) || util.isNumber(stdio.fd)) {
- acc.push({ type: 'fd', fd: stdio.fd || stdio });
- } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) ||
- getHandleWrapType(stdio._handle)) {
- var handle = getHandleWrapType(stdio) ?
- stdio :
- getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle;
-
- acc.push({
- type: 'wrap',
- wrapType: getHandleWrapType(handle),
- handle: handle
- });
- } else {
- // Cleanup
- cleanup();
- throw new TypeError('Incorrect value for stdio stream: ' + stdio);
- }
-
- return acc;
- }, []);
+ stdio = _validateStdio(stdio, false);
- options.stdio = stdio;
+ ipc = stdio.ipc;
+ ipcFd = stdio.ipcFd;
+ stdio = options.stdio = stdio.stdio;
if (!util.isUndefined(ipc)) {
// Let child process know about opened IPC channel
@@ -1113,3 +1203,129 @@ ChildProcess.prototype.ref = function() {
ChildProcess.prototype.unref = function() {
if (this._handle) this._handle.unref();
};
+
+
+function lookupSignal(signal) {
+ if (typeof signal === 'number')
+ return signal;
+
+ if (!constants)
+ constants = process.binding('constants');
+
+ if (!(signal in constants))
+ throw new Error('Unknown signal: ' + signal);
+
+ return constants[signal];
+}
+
+
+function spawnSync(/*file, args, options*/) {
+ var opts = normalizeSpawnArguments.apply(null, arguments);
+
+ var options = opts.options;
+ var envPairs = opts.envPairs;
+
+ var i;
+
+ options.file = opts.file;
+ options.args = opts.args;
+
+ if (options.killSignal)
+ options.killSignal = lookupSignal(options.killSignal);
+
+ options.stdio = _validateStdio(options.stdio || 'pipe', true).stdio;
+
+ if (options.input) {
+ var stdin = options.stdio[0] = util._extend({}, options.stdio[0]);
+ stdin.input = options.input;
+ }
+
+ // We may want to pass data in on any given fd, ensure it is a valid buffer
+ for (i = 0; i < options.stdio.length; i++) {
+ var input = options.stdio[i] && options.stdio[i].input;
+ if (input != null) {
+ var pipe = options.stdio[i] = util._extend({}, options.stdio[i]);
+ if (Buffer.isBuffer(input))
+ pipe.input = input;
+ else if (util.isString(input))
+ pipe.input = new Buffer(input, options.encoding);
+ else
+ throw new TypeError(util.format(
+ 'stdio[%d] should be Buffer or string not %s',
+ i,
+ typeof input));
+ }
+ }
+
+ if (!spawn_sync)
+ spawn_sync = process.binding('spawn_sync');
+
+ var result = spawn_sync.spawn(options);
+
+ if (result.output && options.encoding) {
+ for (i = 0; i < result.output.length; i++) {
+ if (!result.output[i])
+ continue;
+ result.output[i] = result.output[i].toString(options.encoding);
+ }
+ }
+
+ result.stdout = result.output && result.output[1];
+ result.stderr = result.output && result.output[2];
+
+ if (result.error)
+ result.error = errnoException(result.error, 'spawnSync');
+
+ util._extend(result, opts);
+
+ return result;
+}
+exports.spawnSync = spawnSync;
+
+
+function checkExecSyncError(ret) {
+ if (ret.error || ret.status !== 0) {
+ var err = ret.error;
+ ret.error = null;
+
+ if (!err) {
+ var cmd = ret.cmd ? ret.cmd : ret.args.join(' ');
+ err = new Error(util.format('Command failed: %s\n%s',
+ cmd,
+ ret.stderr.toString()));
+ }
+
+ util._extend(err, ret);
+ return err;
+ }
+
+ return false;
+}
+
+
+function execFileSync(/*command, options*/) {
+ var ret = spawnSync.apply(null, arguments);
+
+ var err = checkExecSyncError(ret);
+
+ if (err)
+ throw err;
+ else
+ return ret.stdout;
+}
+exports.execFileSync = execFileSync;
+
+
+function execSync(/*comand, options*/) {
+ var opts = normalizeExecArgs.apply(null, arguments);
+ var ret = spawnSync(opts.file, opts.args, opts.options);
+ ret.cmd = opts.cmd;
+
+ var err = checkExecSyncError(ret);
+
+ if (err)
+ throw err;
+ else
+ return ret.stdout;
+}
+exports.execSync = execSync;