| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| 'use strict'; |
| |
| const { |
| ArrayIsArray, |
| ArrayPrototypeFilter, |
| ArrayPrototypeIncludes, |
| ArrayPrototypeJoin, |
| ArrayPrototypeLastIndexOf, |
| ArrayPrototypePush, |
| ArrayPrototypeSlice, |
| ArrayPrototypeSort, |
| ArrayPrototypeSplice, |
| ArrayPrototypeUnshift, |
| ArrayPrototypePushApply, |
| NumberIsInteger, |
| ObjectAssign, |
| ObjectDefineProperty, |
| ObjectPrototypeHasOwnProperty, |
| RegExpPrototypeExec, |
| SafeSet, |
| StringPrototypeSlice, |
| StringPrototypeToUpperCase, |
| } = primordials; |
| |
| const { |
| convertToValidSignal, |
| createDeferredPromise, |
| getSystemErrorName, |
| kEmptyObject, |
| promisify, |
| } = require('internal/util'); |
| const { isArrayBufferView } = require('internal/util/types'); |
| let debug = require('internal/util/debuglog').debuglog( |
| 'child_process', |
| (fn) => { |
| debug = fn; |
| } |
| ); |
| const { Buffer } = require('buffer'); |
| const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); |
| |
| const { |
| AbortError, |
| codes: errorCodes, |
| genericNodeError, |
| } = require('internal/errors'); |
| const { |
| ERR_INVALID_ARG_VALUE, |
| ERR_CHILD_PROCESS_IPC_REQUIRED, |
| ERR_CHILD_PROCESS_STDIO_MAXBUFFER, |
| ERR_INVALID_ARG_TYPE, |
| ERR_OUT_OF_RANGE, |
| } = errorCodes; |
| const { clearTimeout, setTimeout } = require('timers'); |
| const { getValidatedPath } = require('internal/fs/utils'); |
| const { |
| isInt32, |
| validateAbortSignal, |
| validateArray, |
| validateBoolean, |
| validateFunction, |
| validateObject, |
| validateString, |
| } = require('internal/validators'); |
| const child_process = require('internal/child_process'); |
| const { |
| getValidStdio, |
| setupChannel, |
| ChildProcess, |
| stdioStringToArray |
| } = child_process; |
| |
| const MAX_BUFFER = 1024 * 1024; |
| |
| const isZOS = process.platform === 'os390'; |
| |
| /** |
| * Spawns a new Node.js process + fork. |
| * @param {string|URL} modulePath |
| * @param {string[]} [args] |
| * @param {{ |
| * cwd?: string; |
| * detached?: boolean; |
| * env?: Record<string, string>; |
| * execPath?: string; |
| * execArgv?: string[]; |
| * gid?: number; |
| * serialization?: string; |
| * signal?: AbortSignal; |
| * killSignal?: string | number; |
| * silent?: boolean; |
| * stdio?: Array | string; |
| * uid?: number; |
| * windowsVerbatimArguments?: boolean; |
| * timeout?: number; |
| * }} [options] |
| * @returns {ChildProcess} |
| */ |
| function fork(modulePath, args = [], options) { |
| modulePath = getValidatedPath(modulePath, 'modulePath'); |
| |
| // Get options and args arguments. |
| let execArgv; |
| |
| if (args == null) { |
| args = []; |
| } else if (typeof args === 'object' && !ArrayIsArray(args)) { |
| options = args; |
| args = []; |
| } else { |
| validateArray(args, 'args'); |
| } |
| |
| if (options != null) { |
| validateObject(options, 'options'); |
| } |
| options = { ...options, shell: false }; |
| options.execPath = options.execPath || process.execPath; |
| |
| // Prepare arguments for fork: |
| execArgv = options.execArgv || process.execArgv; |
| |
| if (execArgv === process.execArgv && process._eval != null) { |
| const index = ArrayPrototypeLastIndexOf(execArgv, process._eval); |
| if (index > 0) { |
| // Remove the -e switch to avoid fork bombing ourselves. |
| execArgv = ArrayPrototypeSlice(execArgv); |
| ArrayPrototypeSplice(execArgv, index - 1, 2); |
| } |
| } |
| |
| args = [...execArgv, modulePath, ...args]; |
| |
| if (typeof options.stdio === 'string') { |
| options.stdio = stdioStringToArray(options.stdio, 'ipc'); |
| } else if (!ArrayIsArray(options.stdio)) { |
| // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout, |
| // and stderr from the parent if silent isn't set. |
| options.stdio = stdioStringToArray( |
| options.silent ? 'pipe' : 'inherit', |
| 'ipc'); |
| } else if (!ArrayPrototypeIncludes(options.stdio, 'ipc')) { |
| throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio'); |
| } |
| |
| return spawn(options.execPath, args, options); |
| } |
| |
| function _forkChild(fd, serializationMode) { |
| // set process.send() |
| const p = new Pipe(PipeConstants.IPC); |
| p.open(fd); |
| p.unref(); |
| const control = setupChannel(process, p, serializationMode); |
| process.on('newListener', function onNewListener(name) { |
| if (name === 'message' || name === 'disconnect') control.refCounted(); |
| }); |
| process.on('removeListener', function onRemoveListener(name) { |
| if (name === 'message' || name === 'disconnect') control.unrefCounted(); |
| }); |
| } |
| |
| function normalizeExecArgs(command, options, callback) { |
| if (typeof options === 'function') { |
| callback = options; |
| options = undefined; |
| } |
| |
| // Make a shallow copy so we don't clobber the user's options object. |
| options = { ...options }; |
| options.shell = typeof options.shell === 'string' ? options.shell : true; |
| |
| return { |
| file: command, |
| options: options, |
| callback: callback |
| }; |
| } |
| |
| /** |
| * Spawns a shell executing the given command. |
| * @param {string} command |
| * @param {{ |
| * cmd?: string; |
| * env?: Record<string, string>; |
| * encoding?: string; |
| * shell?: string; |
| * signal?: AbortSignal; |
| * timeout?: number; |
| * maxBuffer?: number; |
| * killSignal?: string | number; |
| * uid?: number; |
| * gid?: number; |
| * windowsHide?: boolean; |
| * }} [options] |
| * @param {( |
| * error?: Error, |
| * stdout?: string | Buffer, |
| * stderr?: string | Buffer |
| * ) => any} [callback] |
| * @returns {ChildProcess} |
| */ |
| function exec(command, options, callback) { |
| const opts = normalizeExecArgs(command, options, callback); |
| return module.exports.execFile(opts.file, |
| opts.options, |
| opts.callback); |
| } |
| |
| const customPromiseExecFunction = (orig) => { |
| return (...args) => { |
| const { promise, resolve, reject } = createDeferredPromise(); |
| |
| promise.child = orig(...args, (err, stdout, stderr) => { |
| if (err !== null) { |
| err.stdout = stdout; |
| err.stderr = stderr; |
| reject(err); |
| } else { |
| resolve({ stdout, stderr }); |
| } |
| }); |
| |
| return promise; |
| }; |
| }; |
| |
| ObjectDefineProperty(exec, promisify.custom, { |
| __proto__: null, |
| enumerable: false, |
| value: customPromiseExecFunction(exec) |
| }); |
| |
| function normalizeExecFileArgs(file, args, options, callback) { |
| if (ArrayIsArray(args)) { |
| args = ArrayPrototypeSlice(args); |
| } else if (args != null && typeof args === 'object') { |
| callback = options; |
| options = args; |
| args = null; |
| } else if (typeof args === 'function') { |
| callback = args; |
| options = null; |
| args = null; |
| } |
| |
| if (args == null) { |
| args = []; |
| } |
| |
| if (typeof options === 'function') { |
| callback = options; |
| } else if (options != null) { |
| validateObject(options, 'options'); |
| } |
| |
| if (options == null) { |
| options = kEmptyObject; |
| } |
| |
| if (callback != null) { |
| validateFunction(callback, 'callback'); |
| } |
| |
| // Validate argv0, if present. |
| if (options.argv0 != null) { |
| validateString(options.argv0, 'options.argv0'); |
| } |
| |
| return { file, args, options, callback }; |
| } |
| |
| /** |
| * Spawns the specified file as a shell. |
| * @param {string} file |
| * @param {string[]} [args] |
| * @param {{ |
| * cwd?: string; |
| * env?: Record<string, string>; |
| * encoding?: string; |
| * timeout?: number; |
| * maxBuffer?: number; |
| * killSignal?: string | number; |
| * uid?: number; |
| * gid?: number; |
| * windowsHide?: boolean; |
| * windowsVerbatimArguments?: boolean; |
| * shell?: boolean | string; |
| * signal?: AbortSignal; |
| * }} [options] |
| * @param {( |
| * error?: Error, |
| * stdout?: string | Buffer, |
| * stderr?: string | Buffer |
| * ) => any} [callback] |
| * @returns {ChildProcess} |
| */ |
| function execFile(file, args, options, callback) { |
| ({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback)); |
| |
| options = { |
| encoding: 'utf8', |
| timeout: 0, |
| maxBuffer: MAX_BUFFER, |
| killSignal: 'SIGTERM', |
| cwd: null, |
| env: null, |
| shell: false, |
| ...options |
| }; |
| |
| // Validate the timeout, if present. |
| validateTimeout(options.timeout); |
| |
| // Validate maxBuffer, if present. |
| validateMaxBuffer(options.maxBuffer); |
| |
| options.killSignal = sanitizeKillSignal(options.killSignal); |
| |
| const child = spawn(file, args, { |
| cwd: options.cwd, |
| env: options.env, |
| gid: options.gid, |
| shell: options.shell, |
| signal: options.signal, |
| uid: options.uid, |
| windowsHide: !!options.windowsHide, |
| windowsVerbatimArguments: !!options.windowsVerbatimArguments |
| }); |
| |
| let encoding; |
| const _stdout = []; |
| const _stderr = []; |
| if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) { |
| encoding = options.encoding; |
| } else { |
| encoding = null; |
| } |
| let stdoutLen = 0; |
| let stderrLen = 0; |
| let killed = false; |
| let exited = false; |
| let timeoutId; |
| |
| let ex = null; |
| |
| let cmd = file; |
| |
| function exithandler(code, signal) { |
| if (exited) return; |
| exited = true; |
| |
| if (timeoutId) { |
| clearTimeout(timeoutId); |
| timeoutId = null; |
| } |
| |
| if (!callback) return; |
| |
| // merge chunks |
| let stdout; |
| let stderr; |
| if (encoding || |
| ( |
| child.stdout && |
| child.stdout.readableEncoding |
| )) { |
| stdout = ArrayPrototypeJoin(_stdout, ''); |
| } else { |
| stdout = Buffer.concat(_stdout); |
| } |
| if (encoding || |
| ( |
| child.stderr && |
| child.stderr.readableEncoding |
| )) { |
| stderr = ArrayPrototypeJoin(_stderr, ''); |
| } else { |
| stderr = Buffer.concat(_stderr); |
| } |
| |
| if (!ex && code === 0 && signal === null) { |
| callback(null, stdout, stderr); |
| return; |
| } |
| |
| if (args?.length) |
| cmd += ` ${ArrayPrototypeJoin(args, ' ')}`; |
| |
| if (!ex) { |
| ex = genericNodeError(`Command failed: ${cmd}\n${stderr}`, { |
| code: code < 0 ? getSystemErrorName(code) : code, |
| killed: child.killed || killed, |
| 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 delayedKill() { |
| kill(); |
| timeoutId = null; |
| }, options.timeout); |
| } |
| |
| if (child.stdout) { |
| if (encoding) |
| child.stdout.setEncoding(encoding); |
| |
| child.stdout.on('data', function onChildStdout(chunk) { |
| // Do not need to count the length |
| if (options.maxBuffer === Infinity) { |
| ArrayPrototypePush(_stdout, chunk); |
| return; |
| } |
| const encoding = child.stdout.readableEncoding; |
| const length = encoding ? |
| Buffer.byteLength(chunk, encoding) : |
| chunk.length; |
| const slice = encoding ? StringPrototypeSlice : |
| (buf, ...args) => buf.slice(...args); |
| stdoutLen += length; |
| |
| if (stdoutLen > options.maxBuffer) { |
| const truncatedLen = options.maxBuffer - (stdoutLen - length); |
| ArrayPrototypePush(_stdout, slice(chunk, 0, truncatedLen)); |
| |
| ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout'); |
| kill(); |
| } else { |
| ArrayPrototypePush(_stdout, chunk); |
| } |
| }); |
| } |
| |
| if (child.stderr) { |
| if (encoding) |
| child.stderr.setEncoding(encoding); |
| |
| child.stderr.on('data', function onChildStderr(chunk) { |
| // Do not need to count the length |
| if (options.maxBuffer === Infinity) { |
| ArrayPrototypePush(_stderr, chunk); |
| return; |
| } |
| const encoding = child.stderr.readableEncoding; |
| const length = encoding ? |
| Buffer.byteLength(chunk, encoding) : |
| chunk.length; |
| stderrLen += length; |
| |
| if (stderrLen > options.maxBuffer) { |
| const truncatedLen = options.maxBuffer - (stderrLen - length); |
| ArrayPrototypePush(_stderr, |
| chunk.slice(0, truncatedLen)); |
| |
| ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr'); |
| kill(); |
| } else { |
| ArrayPrototypePush(_stderr, chunk); |
| } |
| }); |
| } |
| |
| child.addListener('close', exithandler); |
| child.addListener('error', errorhandler); |
| |
| return child; |
| } |
| |
| ObjectDefineProperty(execFile, promisify.custom, { |
| __proto__: null, |
| enumerable: false, |
| value: customPromiseExecFunction(execFile) |
| }); |
| |
| function copyProcessEnvToEnv(env, name, optionEnv) { |
| if (process.env[name] && |
| (!optionEnv || |
| !ObjectPrototypeHasOwnProperty(optionEnv, name))) { |
| env[name] = process.env[name]; |
| } |
| } |
| |
| function normalizeSpawnArguments(file, args, options) { |
| validateString(file, 'file'); |
| |
| if (file.length === 0) |
| throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty'); |
| |
| if (ArrayIsArray(args)) { |
| args = ArrayPrototypeSlice(args); |
| } else if (args == null) { |
| args = []; |
| } else if (typeof args !== 'object') { |
| throw new ERR_INVALID_ARG_TYPE('args', 'object', args); |
| } else { |
| options = args; |
| args = []; |
| } |
| |
| if (options === undefined) |
| options = kEmptyObject; |
| else |
| validateObject(options, 'options'); |
| |
| let cwd = options.cwd; |
| |
| // Validate the cwd, if present. |
| if (cwd != null) { |
| cwd = getValidatedPath(cwd, 'options.cwd'); |
| } |
| |
| // Validate detached, if present. |
| if (options.detached != null) { |
| validateBoolean(options.detached, 'options.detached'); |
| } |
| |
| // Validate the uid, if present. |
| if (options.uid != null && !isInt32(options.uid)) { |
| throw new ERR_INVALID_ARG_TYPE('options.uid', 'int32', options.uid); |
| } |
| |
| // Validate the gid, if present. |
| if (options.gid != null && !isInt32(options.gid)) { |
| throw new ERR_INVALID_ARG_TYPE('options.gid', 'int32', options.gid); |
| } |
| |
| // Validate the shell, if present. |
| if (options.shell != null && |
| typeof options.shell !== 'boolean' && |
| typeof options.shell !== 'string') { |
| throw new ERR_INVALID_ARG_TYPE('options.shell', |
| ['boolean', 'string'], options.shell); |
| } |
| |
| // Validate argv0, if present. |
| if (options.argv0 != null) { |
| validateString(options.argv0, 'options.argv0'); |
| } |
| |
| // Validate windowsHide, if present. |
| if (options.windowsHide != null) { |
| validateBoolean(options.windowsHide, 'options.windowsHide'); |
| } |
| |
| // Validate windowsVerbatimArguments, if present. |
| let { windowsVerbatimArguments } = options; |
| if (windowsVerbatimArguments != null) { |
| validateBoolean(windowsVerbatimArguments, |
| 'options.windowsVerbatimArguments'); |
| } |
| |
| if (options.shell) { |
| const command = ArrayPrototypeJoin([file, ...args], ' '); |
| // Set the shell, switches, and commands. |
| if (process.platform === 'win32') { |
| if (typeof options.shell === 'string') |
| file = options.shell; |
| else |
| file = process.env.comspec || 'cmd.exe'; |
| // '/d /s /c' is used only for cmd.exe. |
| if (RegExpPrototypeExec(/^(?:.*\\)?cmd(?:\.exe)?$/i, file) !== null) { |
| args = ['/d', '/s', '/c', `"${command}"`]; |
| windowsVerbatimArguments = true; |
| } else { |
| args = ['-c', command]; |
| } |
| } 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') { |
| ArrayPrototypeUnshift(args, options.argv0); |
| } else { |
| ArrayPrototypeUnshift(args, file); |
| } |
| |
| const env = options.env || process.env; |
| const envPairs = []; |
| |
| // process.env.NODE_V8_COVERAGE always propagates, making it possible to |
| // collect coverage for programs that spawn with white-listed environment. |
| copyProcessEnvToEnv(env, 'NODE_V8_COVERAGE', options.env); |
| |
| if (isZOS) { |
| // The following environment variables must always propagate if set. |
| copyProcessEnvToEnv(env, '_BPXK_AUTOCVT', options.env); |
| copyProcessEnvToEnv(env, '_CEE_RUNOPTS', options.env); |
| copyProcessEnvToEnv(env, '_TAG_REDIR_ERR', options.env); |
| copyProcessEnvToEnv(env, '_TAG_REDIR_IN', options.env); |
| copyProcessEnvToEnv(env, '_TAG_REDIR_OUT', options.env); |
| copyProcessEnvToEnv(env, 'STEPLIB', options.env); |
| copyProcessEnvToEnv(env, 'LIBPATH', options.env); |
| copyProcessEnvToEnv(env, '_EDC_SIG_DFLT', options.env); |
| copyProcessEnvToEnv(env, '_EDC_SUSV3', options.env); |
| } |
| |
| let envKeys = []; |
| // Prototype values are intentionally included. |
| for (const key in env) { |
| ArrayPrototypePush(envKeys, key); |
| } |
| |
| if (process.platform === 'win32') { |
| // On Windows env keys are case insensitive. Filter out duplicates, |
| // keeping only the first one (in lexicographic order) |
| const sawKey = new SafeSet(); |
| envKeys = ArrayPrototypeFilter( |
| ArrayPrototypeSort(envKeys), |
| (key) => { |
| const uppercaseKey = StringPrototypeToUpperCase(key); |
| if (sawKey.has(uppercaseKey)) { |
| return false; |
| } |
| sawKey.add(uppercaseKey); |
| return true; |
| } |
| ); |
| } |
| |
| for (const key of envKeys) { |
| const value = env[key]; |
| if (value !== undefined) { |
| ArrayPrototypePush(envPairs, `${key}=${value}`); |
| } |
| } |
| |
| return { |
| // Make a shallow copy so we don't clobber the user's options object. |
| ...options, |
| args, |
| cwd, |
| detached: !!options.detached, |
| envPairs, |
| file, |
| windowsHide: !!options.windowsHide, |
| windowsVerbatimArguments: !!windowsVerbatimArguments, |
| }; |
| } |
| |
| function abortChildProcess(child, killSignal) { |
| if (!child) |
| return; |
| try { |
| if (child.kill(killSignal)) { |
| child.emit('error', new AbortError()); |
| } |
| } catch (err) { |
| child.emit('error', err); |
| } |
| } |
| |
| /** |
| * Spawns a new process using the given `file`. |
| * @param {string} file |
| * @param {string[]} [args] |
| * @param {{ |
| * cwd?: string; |
| * env?: Record<string, string>; |
| * argv0?: string; |
| * stdio?: Array | string; |
| * detached?: boolean; |
| * uid?: number; |
| * gid?: number; |
| * serialization?: string; |
| * shell?: boolean | string; |
| * windowsVerbatimArguments?: boolean; |
| * windowsHide?: boolean; |
| * signal?: AbortSignal; |
| * timeout?: number; |
| * killSignal?: string | number; |
| * }} [options] |
| * @returns {ChildProcess} |
| */ |
| function spawn(file, args, options) { |
| options = normalizeSpawnArguments(file, args, options); |
| validateTimeout(options.timeout); |
| validateAbortSignal(options.signal, 'options.signal'); |
| const killSignal = sanitizeKillSignal(options.killSignal); |
| const child = new ChildProcess(); |
| |
| debug('spawn', options); |
| child.spawn(options); |
| |
| if (options.timeout > 0) { |
| let timeoutId = setTimeout(() => { |
| if (timeoutId) { |
| try { |
| child.kill(killSignal); |
| } catch (err) { |
| child.emit('error', err); |
| } |
| timeoutId = null; |
| } |
| }, options.timeout); |
| |
| child.once('exit', () => { |
| if (timeoutId) { |
| clearTimeout(timeoutId); |
| timeoutId = null; |
| } |
| }); |
| } |
| |
| if (options.signal) { |
| const signal = options.signal; |
| if (signal.aborted) { |
| process.nextTick(onAbortListener); |
| } else { |
| signal.addEventListener('abort', onAbortListener, { once: true }); |
| child.once('exit', |
| () => signal.removeEventListener('abort', onAbortListener)); |
| } |
| |
| function onAbortListener() { |
| abortChildProcess(child, killSignal); |
| } |
| } |
| |
| return child; |
| } |
| |
| /** |
| * Spawns a new process synchronously using the given `file`. |
| * @param {string} file |
| * @param {string[]} [args] |
| * @param {{ |
| * cwd?: string; |
| * input?: string | Buffer | TypedArray | DataView; |
| * argv0?: string; |
| * stdio?: string | Array; |
| * env?: Record<string, string>; |
| * uid?: number; |
| * gid?: number; |
| * timeout?: number; |
| * killSignal?: string | number; |
| * maxBuffer?: number; |
| * encoding?: string; |
| * shell?: boolean | string; |
| * windowsVerbatimArguments?: boolean; |
| * windowsHide?: boolean; |
| * }} [options] |
| * @returns {{ |
| * pid: number; |
| * output: Array; |
| * stdout: Buffer | string; |
| * stderr: Buffer | string; |
| * status: number | null; |
| * signal: string | null; |
| * error: Error; |
| * }} |
| */ |
| function spawnSync(file, args, options) { |
| options = { |
| maxBuffer: MAX_BUFFER, |
| ...normalizeSpawnArguments(file, args, options) |
| }; |
| |
| debug('spawnSync', options); |
| |
| // Validate the timeout, if present. |
| validateTimeout(options.timeout); |
| |
| // Validate maxBuffer, if present. |
| validateMaxBuffer(options.maxBuffer); |
| |
| // Validate and translate the kill signal, if present. |
| options.killSignal = sanitizeKillSignal(options.killSignal); |
| |
| options.stdio = getValidStdio(options.stdio || 'pipe', true).stdio; |
| |
| if (options.input) { |
| const stdin = options.stdio[0] = { ...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 (let i = 0; i < options.stdio.length; i++) { |
| const input = options.stdio[i] && options.stdio[i].input; |
| if (input != null) { |
| const pipe = options.stdio[i] = { ...options.stdio[i] }; |
| if (isArrayBufferView(input)) { |
| pipe.input = input; |
| } else if (typeof input === 'string') { |
| pipe.input = Buffer.from(input, options.encoding); |
| } else { |
| throw new ERR_INVALID_ARG_TYPE(`options.stdio[${i}]`, |
| ['Buffer', |
| 'TypedArray', |
| 'DataView', |
| 'string'], |
| input); |
| } |
| } |
| } |
| |
| return child_process.spawnSync(options); |
| } |
| |
| |
| function checkExecSyncError(ret, args, cmd) { |
| let err; |
| if (ret.error) { |
| err = ret.error; |
| ObjectAssign(err, ret); |
| } else if (ret.status !== 0) { |
| let msg = 'Command failed: '; |
| msg += cmd || ArrayPrototypeJoin(args, ' '); |
| if (ret.stderr && ret.stderr.length > 0) |
| msg += `\n${ret.stderr.toString()}`; |
| err = genericNodeError(msg, ret); |
| } |
| return err; |
| } |
| |
| /** |
| * Spawns a file as a shell synchronously. |
| * @param {string} file |
| * @param {string[]} [args] |
| * @param {{ |
| * cwd?: string; |
| * input?: string | Buffer | TypedArray | DataView; |
| * stdio?: string | Array; |
| * env?: Record<string, string>; |
| * uid?: number; |
| * gid?: number; |
| * timeout?: number; |
| * killSignal?: string | number; |
| * maxBuffer?: number; |
| * encoding?: string; |
| * windowsHide?: boolean; |
| * shell?: boolean | string; |
| * }} [options] |
| * @returns {Buffer | string} |
| */ |
| function execFileSync(file, args, options) { |
| ({ file, args, options } = normalizeExecFileArgs(file, args, options)); |
| |
| const inheritStderr = !options.stdio; |
| const ret = spawnSync(file, args, options); |
| |
| if (inheritStderr && ret.stderr) |
| process.stderr.write(ret.stderr); |
| |
| const errArgs = [options.argv0 || file]; |
| ArrayPrototypePushApply(errArgs, args); |
| const err = checkExecSyncError(ret, errArgs); |
| |
| if (err) |
| throw err; |
| |
| return ret.stdout; |
| } |
| |
| /** |
| * Spawns a shell executing the given `command` synchronously. |
| * @param {string} command |
| * @param {{ |
| * cwd?: string; |
| * input?: string | Buffer | TypedArray | DataView; |
| * stdio?: string | Array; |
| * env?: Record<string, string>; |
| * shell?: string; |
| * uid?: number; |
| * gid?: number; |
| * timeout?: number; |
| * killSignal?: string | number; |
| * maxBuffer?: number; |
| * encoding?: string; |
| * windowsHide?: boolean; |
| * }} [options] |
| * @returns {Buffer | string} |
| */ |
| function execSync(command, options) { |
| const opts = normalizeExecArgs(command, options, null); |
| const inheritStderr = !opts.options.stdio; |
| |
| const ret = spawnSync(opts.file, opts.options); |
| |
| if (inheritStderr && ret.stderr) |
| process.stderr.write(ret.stderr); |
| |
| const err = checkExecSyncError(ret, opts.args, command); |
| |
| if (err) |
| throw err; |
| |
| return ret.stdout; |
| } |
| |
| |
| function validateTimeout(timeout) { |
| if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) { |
| throw new ERR_OUT_OF_RANGE('timeout', 'an unsigned integer', timeout); |
| } |
| } |
| |
| |
| function validateMaxBuffer(maxBuffer) { |
| if (maxBuffer != null && !(typeof maxBuffer === 'number' && maxBuffer >= 0)) { |
| throw new ERR_OUT_OF_RANGE('options.maxBuffer', |
| 'a positive number', |
| maxBuffer); |
| } |
| } |
| |
| |
| function sanitizeKillSignal(killSignal) { |
| if (typeof killSignal === 'string' || typeof killSignal === 'number') { |
| return convertToValidSignal(killSignal); |
| } else if (killSignal != null) { |
| throw new ERR_INVALID_ARG_TYPE('options.killSignal', |
| ['string', 'number'], |
| killSignal); |
| } |
| } |
| |
| module.exports = { |
| _forkChild, |
| ChildProcess, |
| exec, |
| execFile, |
| execFileSync, |
| execSync, |
| fork, |
| spawn, |
| spawnSync |
| }; |