mirror of
https://github.com/nodejs/node.git
synced 2025-05-02 18:44:40 +00:00

The require('constants') module is currently undocumented and mashes together unrelated constants. This refactors the require('constants') in favor of distinct os.constants, fs.constants, and crypto.constants that are specific to the modules for which they are relevant. The next step is to document those within the specific modules. PR-URL: https://github.com/nodejs/node/pull/6534 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Robert Lindstaedt <robert.lindstaedt@gmail.com>
523 lines
12 KiB
JavaScript
523 lines
12 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 options, args, execArgv;
|
|
if (Array.isArray(arguments[1])) {
|
|
args = arguments[1];
|
|
options = util._extend({}, arguments[2]);
|
|
} else if (arguments[1] && typeof arguments[1] !== 'object') {
|
|
throw new TypeError('Incorrect value of args option');
|
|
} else {
|
|
args = [];
|
|
options = util._extend({}, arguments[1]);
|
|
}
|
|
|
|
// 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 (pos === 1 && arguments.length > 1) {
|
|
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 = Buffer.concat(_stdout);
|
|
stderr = Buffer.concat(_stderr);
|
|
} else {
|
|
stdout = _stdout;
|
|
stderr = _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 += chunk.length;
|
|
|
|
if (stdoutLen > options.maxBuffer) {
|
|
ex = new Error('stdout maxBuffer exceeded');
|
|
kill();
|
|
} else {
|
|
if (!encoding)
|
|
_stdout.push(chunk);
|
|
else
|
|
_stdout += chunk;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (child.stderr) {
|
|
if (encoding)
|
|
child.stderr.setEncoding(encoding);
|
|
|
|
child.stderr.addListener('data', function(chunk) {
|
|
stderrLen += chunk.length;
|
|
|
|
if (stderrLen > options.maxBuffer) {
|
|
ex = new Error('stderr maxBuffer exceeded');
|
|
kill();
|
|
} else {
|
|
if (!encoding)
|
|
_stderr.push(chunk);
|
|
else
|
|
_stderr += 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) {
|
|
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)
|
|
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)
|
|
process.stderr.write(ret.stderr);
|
|
|
|
var err = checkExecSyncError(ret);
|
|
|
|
if (err)
|
|
throw err;
|
|
else
|
|
return ret.stdout;
|
|
}
|
|
exports.execSync = execSync;
|