node/lib/child_process.js
Robert Chiras 765de1ae11 child_process: Check stderr before accessing it
If something bad happens in spawnSync, stderr might be null. Therefore,
we have to check it before using it, so we won't mask the actual
exception.

PR-URL: https://github.com/nodejs/node/pull/6877
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Robert Jefe Lindstädt <robert.lindstaedt@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
2016-07-19 13:27:30 +02:00

528 lines
13 KiB
JavaScript

'use strict';
const util = require('util');
const internalUtil = require('internal/util');
const debug = util.debuglog('child_process');
const constants = process.binding('constants').os.signals;
const uv = process.binding('uv');
const spawn_sync = process.binding('spawn_sync');
const Buffer = require('buffer').Buffer;
const Pipe = process.binding('pipe_wrap').Pipe;
const child_process = require('internal/child_process');
const errnoException = util._errnoException;
const _validateStdio = child_process._validateStdio;
const setupChannel = child_process.setupChannel;
const ChildProcess = exports.ChildProcess = child_process.ChildProcess;
exports.fork = function(modulePath /*, args, options*/) {
// Get options and args arguments.
var execArgv;
var options = {};
var args = [];
var pos = 1;
if (pos < arguments.length && Array.isArray(arguments[pos])) {
args = arguments[pos++];
}
if (pos < arguments.length && arguments[pos] != null) {
if (typeof arguments[pos] !== 'object') {
throw new TypeError('Incorrect value of args option');
} else {
options = util._extend({}, arguments[pos++]);
}
}
// Prepare arguments for fork:
execArgv = options.execArgv || process.execArgv;
if (execArgv === process.execArgv && process._eval != null) {
const index = execArgv.lastIndexOf(process._eval);
if (index > 0) {
// Remove the -e switch to avoid fork bombing ourselves.
execArgv = execArgv.slice();
execArgv.splice(index - 1, 2);
}
}
args = execArgv.concat([modulePath], args);
// Leave stdin open for the IPC channel. stdout and stderr should be the
// same as the parent's if silent isn't set.
options.stdio = options.silent ? ['pipe', 'pipe', 'pipe', 'ipc'] :
[0, 1, 2, 'ipc'];
options.execPath = options.execPath || process.execPath;
return spawn(options.execPath, args, options);
};
exports._forkChild = function(fd) {
// set process.send()
var p = new Pipe(true);
p.open(fd);
p.unref();
const control = setupChannel(process, p);
process.on('newListener', function(name) {
if (name === 'message' || name === 'disconnect') control.ref();
});
process.on('removeListener', function(name) {
if (name === 'message' || name === 'disconnect') control.unref();
});
};
function normalizeExecArgs(command /*, options, callback*/) {
let options;
let callback;
if (typeof arguments[1] === 'function') {
options = undefined;
callback = arguments[1];
} else {
options = arguments[1];
callback = arguments[2];
}
// Make a shallow copy so we don't clobber the user's options object.
options = Object.assign({}, options);
options.shell = typeof options.shell === 'string' ? options.shell : true;
return {
file: command,
options: options,
callback: callback
};
}
exports.exec = function(command /*, options, callback*/) {
var opts = normalizeExecArgs.apply(null, arguments);
return exports.execFile(opts.file,
opts.options,
opts.callback);
};
exports.execFile = function(file /*, args, options, callback*/) {
var args = [], callback;
var options = {
encoding: 'utf8',
timeout: 0,
maxBuffer: 200 * 1024,
killSignal: 'SIGTERM',
cwd: null,
env: null,
shell: false
};
// Parse the optional positional parameters.
var pos = 1;
if (pos < arguments.length && Array.isArray(arguments[pos])) {
args = arguments[pos++];
} else if (pos < arguments.length && arguments[pos] == null) {
pos++;
}
if (pos < arguments.length && typeof arguments[pos] === 'object') {
options = util._extend(options, arguments[pos++]);
} else if (pos < arguments.length && arguments[pos] == null) {
pos++;
}
if (pos < arguments.length && typeof arguments[pos] === 'function') {
callback = arguments[pos++];
}
if (!callback && arguments[pos] != null) {
throw new TypeError('Incorrect value of args option');
}
var child = spawn(file, args, {
cwd: options.cwd,
env: options.env,
gid: options.gid,
uid: options.uid,
shell: options.shell,
windowsVerbatimArguments: !!options.windowsVerbatimArguments
});
var encoding;
var _stdout;
var _stderr;
if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
encoding = options.encoding;
_stdout = '';
_stderr = '';
} else {
_stdout = [];
_stderr = [];
encoding = null;
}
var stdoutLen = 0;
var stderrLen = 0;
var killed = false;
var exited = false;
var timeoutId;
var ex = null;
function exithandler(code, signal) {
if (exited) return;
exited = true;
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
if (!callback) return;
// merge chunks
var stdout;
var stderr;
if (encoding) {
stdout = _stdout;
stderr = _stderr;
} else {
stdout = Buffer.concat(_stdout);
stderr = Buffer.concat(_stderr);
}
if (ex) {
// Will be handled later
} else if (code === 0 && signal === null) {
callback(null, stdout, stderr);
return;
}
var cmd = file;
if (args.length !== 0)
cmd += ' ' + args.join(' ');
if (!ex) {
ex = new Error('Command failed: ' + cmd + '\n' + stderr);
ex.killed = child.killed || killed;
ex.code = code < 0 ? uv.errname(code) : code;
ex.signal = signal;
}
ex.cmd = cmd;
callback(ex, stdout, stderr);
}
function errorhandler(e) {
ex = e;
if (child.stdout)
child.stdout.destroy();
if (child.stderr)
child.stderr.destroy();
exithandler();
}
function kill() {
if (child.stdout)
child.stdout.destroy();
if (child.stderr)
child.stderr.destroy();
killed = true;
try {
child.kill(options.killSignal);
} catch (e) {
ex = e;
exithandler();
}
}
if (options.timeout > 0) {
timeoutId = setTimeout(function() {
kill();
timeoutId = null;
}, options.timeout);
}
if (child.stdout) {
if (encoding)
child.stdout.setEncoding(encoding);
child.stdout.addListener('data', function(chunk) {
stdoutLen += encoding ? Buffer.byteLength(chunk, encoding) : chunk.length;
if (stdoutLen > options.maxBuffer) {
ex = new Error('stdout maxBuffer exceeded');
kill();
} else {
if (encoding)
_stdout += chunk;
else
_stdout.push(chunk);
}
});
}
if (child.stderr) {
if (encoding)
child.stderr.setEncoding(encoding);
child.stderr.addListener('data', function(chunk) {
stderrLen += encoding ? Buffer.byteLength(chunk, encoding) : chunk.length;
if (stderrLen > options.maxBuffer) {
ex = new Error('stderr maxBuffer exceeded');
kill();
} else {
if (encoding)
_stderr += chunk;
else
_stderr.push(chunk);
}
});
}
child.addListener('close', exithandler);
child.addListener('error', errorhandler);
return child;
};
var _deprecatedCustomFds = internalUtil.deprecate(function(options) {
options.stdio = options.customFds.map(function(fd) {
return fd === -1 ? 'pipe' : fd;
});
}, 'child_process: options.customFds option is deprecated. ' +
'Use options.stdio instead.');
function _convertCustomFds(options) {
if (options && options.customFds && !options.stdio) {
_deprecatedCustomFds(options);
}
}
function normalizeSpawnArguments(file /*, args, options*/) {
var args, options;
if (Array.isArray(arguments[1])) {
args = arguments[1].slice(0);
options = arguments[2];
} else if (arguments[1] !== undefined &&
(arguments[1] === null || typeof arguments[1] !== 'object')) {
throw new TypeError('Incorrect value of args option');
} else {
args = [];
options = arguments[1];
}
if (options === undefined)
options = {};
else if (options === null || typeof options !== 'object')
throw new TypeError('"options" argument must be an object');
// Make a shallow copy so we don't clobber the user's options object.
options = Object.assign({}, options);
if (options.shell) {
const command = [file].concat(args).join(' ');
if (process.platform === 'win32') {
file = typeof options.shell === 'string' ? options.shell :
process.env.comspec || 'cmd.exe';
args = ['/s', '/c', '"' + command + '"'];
options.windowsVerbatimArguments = true;
} else {
if (typeof options.shell === 'string')
file = options.shell;
else if (process.platform === 'android')
file = '/system/bin/sh';
else
file = '/bin/sh';
args = ['-c', command];
}
}
args.unshift(file);
var env = options.env || process.env;
var envPairs = [];
for (var key in env) {
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 options = opts.options;
var child = new ChildProcess();
debug('spawn', opts.args, options);
child.spawn({
file: opts.file,
args: opts.args,
cwd: options.cwd,
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
detached: !!options.detached,
envPairs: opts.envPairs,
stdio: options.stdio,
uid: options.uid,
gid: options.gid
});
return child;
};
function lookupSignal(signal) {
if (typeof signal === 'number')
return signal;
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 i;
debug('spawnSync', opts.args, options);
options.file = opts.file;
options.args = opts.args;
options.envPairs = opts.envPairs;
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 (typeof input === 'string')
pipe.input = Buffer.from(input, options.encoding);
else
throw new TypeError(util.format(
'stdio[%d] should be Buffer or string not %s',
i,
typeof input));
}
}
var result = spawn_sync.spawn(options);
if (result.output && options.encoding && options.encoding !== 'buffer') {
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 ' + opts.file);
result.error.path = opts.file;
result.error.spawnargs = opts.args.slice(1);
}
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 msg = 'Command failed: ';
msg += ret.cmd || ret.args.join(' ');
if (ret.stderr)
msg += '\n' + ret.stderr.toString();
err = new Error(msg);
}
util._extend(err, ret);
return err;
}
return false;
}
function execFileSync(/*command, args, options*/) {
var opts = normalizeSpawnArguments.apply(null, arguments);
var inheritStderr = !opts.options.stdio;
var ret = spawnSync(opts.file, opts.args.slice(1), opts.options);
if (inheritStderr && ret.stderr)
process.stderr.write(ret.stderr);
var err = checkExecSyncError(ret);
if (err)
throw err;
else
return ret.stdout;
}
exports.execFileSync = execFileSync;
function execSync(command /*, options*/) {
var opts = normalizeExecArgs.apply(null, arguments);
var inheritStderr = opts.options ? !opts.options.stdio : true;
var ret = spawnSync(opts.file, opts.options);
ret.cmd = command;
if (inheritStderr && ret.stderr)
process.stderr.write(ret.stderr);
var err = checkExecSyncError(ret);
if (err)
throw err;
else
return ret.stdout;
}
exports.execSync = execSync;