diff options
Diffstat (limited to 'lib/child_process.js')
-rw-r--r-- | lib/child_process.js | 376 |
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; |