node/lib/child_process.js

536 строки
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);
if (!Array.isArray(options.stdio)) {
// 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'];
} else if (options.stdio.indexOf('ipc') === -1) {
throw new TypeError('Forked processes must have an IPC channel');
}
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 = ['/d', '/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];
}
}
if (typeof options.argv0 === 'string') {
args.unshift(options.argv0);
} else {
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;