1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayIsArray, 26 ArrayPrototypeFilter, 27 ArrayPrototypeIncludes, 28 ArrayPrototypeJoin, 29 ArrayPrototypeLastIndexOf, 30 ArrayPrototypePush, 31 ArrayPrototypeSlice, 32 ArrayPrototypeSort, 33 ArrayPrototypeSplice, 34 ArrayPrototypeUnshift, 35 ArrayPrototypePushApply, 36 NumberIsInteger, 37 ObjectAssign, 38 ObjectDefineProperty, 39 ObjectPrototypeHasOwnProperty, 40 RegExpPrototypeExec, 41 SafeSet, 42 StringPrototypeIncludes, 43 StringPrototypeSlice, 44 StringPrototypeToUpperCase, 45 SymbolDispose, 46} = primordials; 47 48const { 49 convertToValidSignal, 50 createDeferredPromise, 51 getSystemErrorName, 52 kEmptyObject, 53 promisify, 54} = require('internal/util'); 55const { isArrayBufferView } = require('internal/util/types'); 56let debug = require('internal/util/debuglog').debuglog( 57 'child_process', 58 (fn) => { 59 debug = fn; 60 }, 61); 62const { Buffer } = require('buffer'); 63const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); 64 65const { 66 AbortError, 67 codes: errorCodes, 68 genericNodeError, 69} = require('internal/errors'); 70const { 71 ERR_INVALID_ARG_VALUE, 72 ERR_CHILD_PROCESS_IPC_REQUIRED, 73 ERR_CHILD_PROCESS_STDIO_MAXBUFFER, 74 ERR_INVALID_ARG_TYPE, 75 ERR_OUT_OF_RANGE, 76} = errorCodes; 77const { clearTimeout, setTimeout } = require('timers'); 78const { getValidatedPath } = require('internal/fs/utils'); 79const { 80 isInt32, 81 validateAbortSignal, 82 validateArray, 83 validateBoolean, 84 validateFunction, 85 validateObject, 86 validateString, 87} = require('internal/validators'); 88const child_process = require('internal/child_process'); 89const { 90 getValidStdio, 91 setupChannel, 92 ChildProcess, 93 stdioStringToArray, 94} = child_process; 95 96const MAX_BUFFER = 1024 * 1024; 97 98const isZOS = process.platform === 'os390'; 99let addAbortListener; 100 101/** 102 * Spawns a new Node.js process + fork. 103 * @param {string|URL} modulePath 104 * @param {string[]} [args] 105 * @param {{ 106 * cwd?: string | URL; 107 * detached?: boolean; 108 * env?: Record<string, string>; 109 * execPath?: string; 110 * execArgv?: string[]; 111 * gid?: number; 112 * serialization?: string; 113 * signal?: AbortSignal; 114 * killSignal?: string | number; 115 * silent?: boolean; 116 * stdio?: Array | string; 117 * uid?: number; 118 * windowsVerbatimArguments?: boolean; 119 * timeout?: number; 120 * }} [options] 121 * @returns {ChildProcess} 122 */ 123function fork(modulePath, args = [], options) { 124 modulePath = getValidatedPath(modulePath, 'modulePath'); 125 126 // Get options and args arguments. 127 let execArgv; 128 129 if (args == null) { 130 args = []; 131 } else if (typeof args === 'object' && !ArrayIsArray(args)) { 132 options = args; 133 args = []; 134 } else { 135 validateArray(args, 'args'); 136 } 137 138 if (options != null) { 139 validateObject(options, 'options'); 140 } 141 options = { __proto__: null, ...options, shell: false }; 142 options.execPath = options.execPath || process.execPath; 143 validateArgumentNullCheck(options.execPath, 'options.execPath'); 144 145 // Prepare arguments for fork: 146 execArgv = options.execArgv || process.execArgv; 147 validateArgumentsNullCheck(execArgv, 'options.execArgv'); 148 149 if (execArgv === process.execArgv && process._eval != null) { 150 const index = ArrayPrototypeLastIndexOf(execArgv, process._eval); 151 if (index > 0) { 152 // Remove the -e switch to avoid fork bombing ourselves. 153 execArgv = ArrayPrototypeSlice(execArgv); 154 ArrayPrototypeSplice(execArgv, index - 1, 2); 155 } 156 } 157 158 args = [...execArgv, modulePath, ...args]; 159 160 if (typeof options.stdio === 'string') { 161 options.stdio = stdioStringToArray(options.stdio, 'ipc'); 162 } else if (!ArrayIsArray(options.stdio)) { 163 // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout, 164 // and stderr from the parent if silent isn't set. 165 options.stdio = stdioStringToArray( 166 options.silent ? 'pipe' : 'inherit', 167 'ipc'); 168 } else if (!ArrayPrototypeIncludes(options.stdio, 'ipc')) { 169 throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio'); 170 } 171 172 return spawn(options.execPath, args, options); 173} 174 175function _forkChild(fd, serializationMode) { 176 // set process.send() 177 const p = new Pipe(PipeConstants.IPC); 178 p.open(fd); 179 p.unref(); 180 const control = setupChannel(process, p, serializationMode); 181 process.on('newListener', function onNewListener(name) { 182 if (name === 'message' || name === 'disconnect') control.refCounted(); 183 }); 184 process.on('removeListener', function onRemoveListener(name) { 185 if (name === 'message' || name === 'disconnect') control.unrefCounted(); 186 }); 187} 188 189function normalizeExecArgs(command, options, callback) { 190 validateString(command, 'command'); 191 validateArgumentNullCheck(command, 'command'); 192 193 if (typeof options === 'function') { 194 callback = options; 195 options = undefined; 196 } 197 198 // Make a shallow copy so we don't clobber the user's options object. 199 options = { __proto__: null, ...options }; 200 options.shell = typeof options.shell === 'string' ? options.shell : true; 201 202 return { 203 file: command, 204 options: options, 205 callback: callback, 206 }; 207} 208 209/** 210 * Spawns a shell executing the given command. 211 * @param {string} command 212 * @param {{ 213 * cmd?: string; 214 * env?: Record<string, string>; 215 * encoding?: string; 216 * shell?: string; 217 * signal?: AbortSignal; 218 * timeout?: number; 219 * maxBuffer?: number; 220 * killSignal?: string | number; 221 * uid?: number; 222 * gid?: number; 223 * windowsHide?: boolean; 224 * }} [options] 225 * @param {( 226 * error?: Error, 227 * stdout?: string | Buffer, 228 * stderr?: string | Buffer 229 * ) => any} [callback] 230 * @returns {ChildProcess} 231 */ 232function exec(command, options, callback) { 233 const opts = normalizeExecArgs(command, options, callback); 234 return module.exports.execFile(opts.file, 235 opts.options, 236 opts.callback); 237} 238 239const customPromiseExecFunction = (orig) => { 240 return (...args) => { 241 const { promise, resolve, reject } = createDeferredPromise(); 242 243 promise.child = orig(...args, (err, stdout, stderr) => { 244 if (err !== null) { 245 err.stdout = stdout; 246 err.stderr = stderr; 247 reject(err); 248 } else { 249 resolve({ stdout, stderr }); 250 } 251 }); 252 253 return promise; 254 }; 255}; 256 257ObjectDefineProperty(exec, promisify.custom, { 258 __proto__: null, 259 enumerable: false, 260 value: customPromiseExecFunction(exec), 261}); 262 263function normalizeExecFileArgs(file, args, options, callback) { 264 if (ArrayIsArray(args)) { 265 args = ArrayPrototypeSlice(args); 266 } else if (args != null && typeof args === 'object') { 267 callback = options; 268 options = args; 269 args = null; 270 } else if (typeof args === 'function') { 271 callback = args; 272 options = null; 273 args = null; 274 } 275 276 if (args == null) { 277 args = []; 278 } 279 280 if (typeof options === 'function') { 281 callback = options; 282 } else if (options != null) { 283 validateObject(options, 'options'); 284 } 285 286 if (options == null) { 287 options = kEmptyObject; 288 } 289 290 if (callback != null) { 291 validateFunction(callback, 'callback'); 292 } 293 294 // Validate argv0, if present. 295 if (options.argv0 != null) { 296 validateString(options.argv0, 'options.argv0'); 297 validateArgumentNullCheck(options.argv0, 'options.argv0'); 298 } 299 300 return { file, args, options, callback }; 301} 302 303/** 304 * Spawns the specified file as a shell. 305 * @param {string} file 306 * @param {string[]} [args] 307 * @param {{ 308 * cwd?: string | URL; 309 * env?: Record<string, string>; 310 * encoding?: string; 311 * timeout?: number; 312 * maxBuffer?: number; 313 * killSignal?: string | number; 314 * uid?: number; 315 * gid?: number; 316 * windowsHide?: boolean; 317 * windowsVerbatimArguments?: boolean; 318 * shell?: boolean | string; 319 * signal?: AbortSignal; 320 * }} [options] 321 * @param {( 322 * error?: Error, 323 * stdout?: string | Buffer, 324 * stderr?: string | Buffer 325 * ) => any} [callback] 326 * @returns {ChildProcess} 327 */ 328function execFile(file, args, options, callback) { 329 ({ file, args, options, callback } = normalizeExecFileArgs(file, args, options, callback)); 330 331 options = { 332 __proto__: null, 333 encoding: 'utf8', 334 timeout: 0, 335 maxBuffer: MAX_BUFFER, 336 killSignal: 'SIGTERM', 337 cwd: null, 338 env: null, 339 shell: false, 340 ...options, 341 }; 342 343 // Validate the timeout, if present. 344 validateTimeout(options.timeout); 345 346 // Validate maxBuffer, if present. 347 validateMaxBuffer(options.maxBuffer); 348 349 options.killSignal = sanitizeKillSignal(options.killSignal); 350 351 const child = spawn(file, args, { 352 cwd: options.cwd, 353 env: options.env, 354 gid: options.gid, 355 shell: options.shell, 356 signal: options.signal, 357 uid: options.uid, 358 windowsHide: !!options.windowsHide, 359 windowsVerbatimArguments: !!options.windowsVerbatimArguments, 360 }); 361 362 let encoding; 363 const _stdout = []; 364 const _stderr = []; 365 if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) { 366 encoding = options.encoding; 367 } else { 368 encoding = null; 369 } 370 let stdoutLen = 0; 371 let stderrLen = 0; 372 let killed = false; 373 let exited = false; 374 let timeoutId; 375 376 let ex = null; 377 378 let cmd = file; 379 380 function exithandler(code, signal) { 381 if (exited) return; 382 exited = true; 383 384 if (timeoutId) { 385 clearTimeout(timeoutId); 386 timeoutId = null; 387 } 388 389 if (!callback) return; 390 391 // merge chunks 392 let stdout; 393 let stderr; 394 if (encoding || 395 ( 396 child.stdout && 397 child.stdout.readableEncoding 398 )) { 399 stdout = ArrayPrototypeJoin(_stdout, ''); 400 } else { 401 stdout = Buffer.concat(_stdout); 402 } 403 if (encoding || 404 ( 405 child.stderr && 406 child.stderr.readableEncoding 407 )) { 408 stderr = ArrayPrototypeJoin(_stderr, ''); 409 } else { 410 stderr = Buffer.concat(_stderr); 411 } 412 413 if (!ex && code === 0 && signal === null) { 414 callback(null, stdout, stderr); 415 return; 416 } 417 418 if (args?.length) 419 cmd += ` ${ArrayPrototypeJoin(args, ' ')}`; 420 421 if (!ex) { 422 ex = genericNodeError(`Command failed: ${cmd}\n${stderr}`, { 423 code: code < 0 ? getSystemErrorName(code) : code, 424 killed: child.killed || killed, 425 signal: signal, 426 }); 427 } 428 429 ex.cmd = cmd; 430 callback(ex, stdout, stderr); 431 } 432 433 function errorhandler(e) { 434 ex = e; 435 436 if (child.stdout) 437 child.stdout.destroy(); 438 439 if (child.stderr) 440 child.stderr.destroy(); 441 442 exithandler(); 443 } 444 445 function kill() { 446 if (child.stdout) 447 child.stdout.destroy(); 448 449 if (child.stderr) 450 child.stderr.destroy(); 451 452 killed = true; 453 try { 454 child.kill(options.killSignal); 455 } catch (e) { 456 ex = e; 457 exithandler(); 458 } 459 } 460 461 if (options.timeout > 0) { 462 timeoutId = setTimeout(function delayedKill() { 463 kill(); 464 timeoutId = null; 465 }, options.timeout); 466 } 467 468 if (child.stdout) { 469 if (encoding) 470 child.stdout.setEncoding(encoding); 471 472 child.stdout.on('data', function onChildStdout(chunk) { 473 // Do not need to count the length 474 if (options.maxBuffer === Infinity) { 475 ArrayPrototypePush(_stdout, chunk); 476 return; 477 } 478 const encoding = child.stdout.readableEncoding; 479 const length = encoding ? 480 Buffer.byteLength(chunk, encoding) : 481 chunk.length; 482 const slice = encoding ? StringPrototypeSlice : 483 (buf, ...args) => buf.slice(...args); 484 stdoutLen += length; 485 486 if (stdoutLen > options.maxBuffer) { 487 const truncatedLen = options.maxBuffer - (stdoutLen - length); 488 ArrayPrototypePush(_stdout, slice(chunk, 0, truncatedLen)); 489 490 ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout'); 491 kill(); 492 } else { 493 ArrayPrototypePush(_stdout, chunk); 494 } 495 }); 496 } 497 498 if (child.stderr) { 499 if (encoding) 500 child.stderr.setEncoding(encoding); 501 502 child.stderr.on('data', function onChildStderr(chunk) { 503 // Do not need to count the length 504 if (options.maxBuffer === Infinity) { 505 ArrayPrototypePush(_stderr, chunk); 506 return; 507 } 508 const encoding = child.stderr.readableEncoding; 509 const length = encoding ? 510 Buffer.byteLength(chunk, encoding) : 511 chunk.length; 512 stderrLen += length; 513 514 if (stderrLen > options.maxBuffer) { 515 const truncatedLen = options.maxBuffer - (stderrLen - length); 516 ArrayPrototypePush(_stderr, 517 chunk.slice(0, truncatedLen)); 518 519 ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr'); 520 kill(); 521 } else { 522 ArrayPrototypePush(_stderr, chunk); 523 } 524 }); 525 } 526 527 child.addListener('close', exithandler); 528 child.addListener('error', errorhandler); 529 530 return child; 531} 532 533ObjectDefineProperty(execFile, promisify.custom, { 534 __proto__: null, 535 enumerable: false, 536 value: customPromiseExecFunction(execFile), 537}); 538 539function copyProcessEnvToEnv(env, name, optionEnv) { 540 if (process.env[name] && 541 (!optionEnv || 542 !ObjectPrototypeHasOwnProperty(optionEnv, name))) { 543 env[name] = process.env[name]; 544 } 545} 546 547function normalizeSpawnArguments(file, args, options) { 548 validateString(file, 'file'); 549 validateArgumentNullCheck(file, 'file'); 550 551 if (file.length === 0) 552 throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty'); 553 554 if (ArrayIsArray(args)) { 555 args = ArrayPrototypeSlice(args); 556 } else if (args == null) { 557 args = []; 558 } else if (typeof args !== 'object') { 559 throw new ERR_INVALID_ARG_TYPE('args', 'object', args); 560 } else { 561 options = args; 562 args = []; 563 } 564 565 validateArgumentsNullCheck(args, 'args'); 566 567 if (options === undefined) 568 options = kEmptyObject; 569 else 570 validateObject(options, 'options'); 571 572 let cwd = options.cwd; 573 574 // Validate the cwd, if present. 575 if (cwd != null) { 576 cwd = getValidatedPath(cwd, 'options.cwd'); 577 } 578 579 // Validate detached, if present. 580 if (options.detached != null) { 581 validateBoolean(options.detached, 'options.detached'); 582 } 583 584 // Validate the uid, if present. 585 if (options.uid != null && !isInt32(options.uid)) { 586 throw new ERR_INVALID_ARG_TYPE('options.uid', 'int32', options.uid); 587 } 588 589 // Validate the gid, if present. 590 if (options.gid != null && !isInt32(options.gid)) { 591 throw new ERR_INVALID_ARG_TYPE('options.gid', 'int32', options.gid); 592 } 593 594 // Validate the shell, if present. 595 if (options.shell != null && 596 typeof options.shell !== 'boolean' && 597 typeof options.shell !== 'string') { 598 throw new ERR_INVALID_ARG_TYPE('options.shell', 599 ['boolean', 'string'], options.shell); 600 } 601 602 // Validate argv0, if present. 603 if (options.argv0 != null) { 604 validateString(options.argv0, 'options.argv0'); 605 validateArgumentNullCheck(options.argv0, 'options.argv0'); 606 } 607 608 // Validate windowsHide, if present. 609 if (options.windowsHide != null) { 610 validateBoolean(options.windowsHide, 'options.windowsHide'); 611 } 612 613 // Validate windowsVerbatimArguments, if present. 614 let { windowsVerbatimArguments } = options; 615 if (windowsVerbatimArguments != null) { 616 validateBoolean(windowsVerbatimArguments, 617 'options.windowsVerbatimArguments'); 618 } 619 620 if (options.shell) { 621 validateArgumentNullCheck(options.shell, 'options.shell'); 622 const command = ArrayPrototypeJoin([file, ...args], ' '); 623 // Set the shell, switches, and commands. 624 if (process.platform === 'win32') { 625 if (typeof options.shell === 'string') 626 file = options.shell; 627 else 628 file = process.env.comspec || 'cmd.exe'; 629 // '/d /s /c' is used only for cmd.exe. 630 if (RegExpPrototypeExec(/^(?:.*\\)?cmd(?:\.exe)?$/i, file) !== null) { 631 args = ['/d', '/s', '/c', `"${command}"`]; 632 windowsVerbatimArguments = true; 633 } else { 634 args = ['-c', command]; 635 } 636 } else { 637 if (typeof options.shell === 'string') 638 file = options.shell; 639 else if (process.platform === 'android') 640 file = '/system/bin/sh'; 641 else 642 file = '/bin/sh'; 643 args = ['-c', command]; 644 } 645 } 646 647 if (typeof options.argv0 === 'string') { 648 ArrayPrototypeUnshift(args, options.argv0); 649 } else { 650 ArrayPrototypeUnshift(args, file); 651 } 652 653 const env = options.env || process.env; 654 const envPairs = []; 655 656 // process.env.NODE_V8_COVERAGE always propagates, making it possible to 657 // collect coverage for programs that spawn with white-listed environment. 658 copyProcessEnvToEnv(env, 'NODE_V8_COVERAGE', options.env); 659 660 if (isZOS) { 661 // The following environment variables must always propagate if set. 662 copyProcessEnvToEnv(env, '_BPXK_AUTOCVT', options.env); 663 copyProcessEnvToEnv(env, '_CEE_RUNOPTS', options.env); 664 copyProcessEnvToEnv(env, '_TAG_REDIR_ERR', options.env); 665 copyProcessEnvToEnv(env, '_TAG_REDIR_IN', options.env); 666 copyProcessEnvToEnv(env, '_TAG_REDIR_OUT', options.env); 667 copyProcessEnvToEnv(env, 'STEPLIB', options.env); 668 copyProcessEnvToEnv(env, 'LIBPATH', options.env); 669 copyProcessEnvToEnv(env, '_EDC_SIG_DFLT', options.env); 670 copyProcessEnvToEnv(env, '_EDC_SUSV3', options.env); 671 } 672 673 let envKeys = []; 674 // Prototype values are intentionally included. 675 for (const key in env) { 676 ArrayPrototypePush(envKeys, key); 677 } 678 679 if (process.platform === 'win32') { 680 // On Windows env keys are case insensitive. Filter out duplicates, 681 // keeping only the first one (in lexicographic order) 682 const sawKey = new SafeSet(); 683 envKeys = ArrayPrototypeFilter( 684 ArrayPrototypeSort(envKeys), 685 (key) => { 686 const uppercaseKey = StringPrototypeToUpperCase(key); 687 if (sawKey.has(uppercaseKey)) { 688 return false; 689 } 690 sawKey.add(uppercaseKey); 691 return true; 692 }, 693 ); 694 } 695 696 for (const key of envKeys) { 697 const value = env[key]; 698 if (value !== undefined) { 699 validateArgumentNullCheck(key, `options.env['${key}']`); 700 validateArgumentNullCheck(value, `options.env['${key}']`); 701 ArrayPrototypePush(envPairs, `${key}=${value}`); 702 } 703 } 704 705 return { 706 // Make a shallow copy so we don't clobber the user's options object. 707 __proto__: null, 708 ...options, 709 args, 710 cwd, 711 detached: !!options.detached, 712 envPairs, 713 file, 714 windowsHide: !!options.windowsHide, 715 windowsVerbatimArguments: !!windowsVerbatimArguments, 716 }; 717} 718 719function abortChildProcess(child, killSignal, reason) { 720 if (!child) 721 return; 722 try { 723 if (child.kill(killSignal)) { 724 child.emit('error', new AbortError(undefined, { cause: reason })); 725 } 726 } catch (err) { 727 child.emit('error', err); 728 } 729} 730 731/** 732 * Spawns a new process using the given `file`. 733 * @param {string} file 734 * @param {string[]} [args] 735 * @param {{ 736 * cwd?: string | URL; 737 * env?: Record<string, string>; 738 * argv0?: string; 739 * stdio?: Array | string; 740 * detached?: boolean; 741 * uid?: number; 742 * gid?: number; 743 * serialization?: string; 744 * shell?: boolean | string; 745 * windowsVerbatimArguments?: boolean; 746 * windowsHide?: boolean; 747 * signal?: AbortSignal; 748 * timeout?: number; 749 * killSignal?: string | number; 750 * }} [options] 751 * @returns {ChildProcess} 752 */ 753function spawn(file, args, options) { 754 options = normalizeSpawnArguments(file, args, options); 755 validateTimeout(options.timeout); 756 validateAbortSignal(options.signal, 'options.signal'); 757 const killSignal = sanitizeKillSignal(options.killSignal); 758 const child = new ChildProcess(); 759 760 debug('spawn', options); 761 child.spawn(options); 762 763 if (options.timeout > 0) { 764 let timeoutId = setTimeout(() => { 765 if (timeoutId) { 766 try { 767 child.kill(killSignal); 768 } catch (err) { 769 child.emit('error', err); 770 } 771 timeoutId = null; 772 } 773 }, options.timeout); 774 775 child.once('exit', () => { 776 if (timeoutId) { 777 clearTimeout(timeoutId); 778 timeoutId = null; 779 } 780 }); 781 } 782 783 if (options.signal) { 784 const signal = options.signal; 785 if (signal.aborted) { 786 process.nextTick(onAbortListener); 787 } else { 788 addAbortListener ??= require('events').addAbortListener; 789 const disposable = addAbortListener(signal, onAbortListener); 790 child.once('exit', disposable[SymbolDispose]); 791 } 792 793 function onAbortListener() { 794 abortChildProcess(child, killSignal, options.signal.reason); 795 } 796 } 797 798 return child; 799} 800 801/** 802 * Spawns a new process synchronously using the given `file`. 803 * @param {string} file 804 * @param {string[]} [args] 805 * @param {{ 806 * cwd?: string | URL; 807 * input?: string | Buffer | TypedArray | DataView; 808 * argv0?: string; 809 * stdio?: string | Array; 810 * env?: Record<string, string>; 811 * uid?: number; 812 * gid?: number; 813 * timeout?: number; 814 * killSignal?: string | number; 815 * maxBuffer?: number; 816 * encoding?: string; 817 * shell?: boolean | string; 818 * windowsVerbatimArguments?: boolean; 819 * windowsHide?: boolean; 820 * }} [options] 821 * @returns {{ 822 * pid: number; 823 * output: Array; 824 * stdout: Buffer | string; 825 * stderr: Buffer | string; 826 * status: number | null; 827 * signal: string | null; 828 * error: Error; 829 * }} 830 */ 831function spawnSync(file, args, options) { 832 options = { 833 __proto__: null, 834 maxBuffer: MAX_BUFFER, 835 ...normalizeSpawnArguments(file, args, options), 836 }; 837 838 debug('spawnSync', options); 839 840 // Validate the timeout, if present. 841 validateTimeout(options.timeout); 842 843 // Validate maxBuffer, if present. 844 validateMaxBuffer(options.maxBuffer); 845 846 // Validate and translate the kill signal, if present. 847 options.killSignal = sanitizeKillSignal(options.killSignal); 848 849 options.stdio = getValidStdio(options.stdio || 'pipe', true).stdio; 850 851 if (options.input) { 852 const stdin = options.stdio[0] = { ...options.stdio[0] }; 853 stdin.input = options.input; 854 } 855 856 // We may want to pass data in on any given fd, ensure it is a valid buffer 857 for (let i = 0; i < options.stdio.length; i++) { 858 const input = options.stdio[i] && options.stdio[i].input; 859 if (input != null) { 860 const pipe = options.stdio[i] = { ...options.stdio[i] }; 861 if (isArrayBufferView(input)) { 862 pipe.input = input; 863 } else if (typeof input === 'string') { 864 pipe.input = Buffer.from(input, options.encoding); 865 } else { 866 throw new ERR_INVALID_ARG_TYPE(`options.stdio[${i}]`, 867 ['Buffer', 868 'TypedArray', 869 'DataView', 870 'string'], 871 input); 872 } 873 } 874 } 875 876 return child_process.spawnSync(options); 877} 878 879 880function checkExecSyncError(ret, args, cmd) { 881 let err; 882 if (ret.error) { 883 err = ret.error; 884 ObjectAssign(err, ret); 885 } else if (ret.status !== 0) { 886 let msg = 'Command failed: '; 887 msg += cmd || ArrayPrototypeJoin(args, ' '); 888 if (ret.stderr && ret.stderr.length > 0) 889 msg += `\n${ret.stderr.toString()}`; 890 err = genericNodeError(msg, ret); 891 } 892 return err; 893} 894 895/** 896 * Spawns a file as a shell synchronously. 897 * @param {string} file 898 * @param {string[]} [args] 899 * @param {{ 900 * cwd?: string | URL; 901 * input?: string | Buffer | TypedArray | DataView; 902 * stdio?: string | Array; 903 * env?: Record<string, string>; 904 * uid?: number; 905 * gid?: number; 906 * timeout?: number; 907 * killSignal?: string | number; 908 * maxBuffer?: number; 909 * encoding?: string; 910 * windowsHide?: boolean; 911 * shell?: boolean | string; 912 * }} [options] 913 * @returns {Buffer | string} 914 */ 915function execFileSync(file, args, options) { 916 ({ file, args, options } = normalizeExecFileArgs(file, args, options)); 917 918 const inheritStderr = !options.stdio; 919 const ret = spawnSync(file, args, options); 920 921 if (inheritStderr && ret.stderr) 922 process.stderr.write(ret.stderr); 923 924 const errArgs = [options.argv0 || file]; 925 ArrayPrototypePushApply(errArgs, args); 926 const err = checkExecSyncError(ret, errArgs); 927 928 if (err) 929 throw err; 930 931 return ret.stdout; 932} 933 934/** 935 * Spawns a shell executing the given `command` synchronously. 936 * @param {string} command 937 * @param {{ 938 * cwd?: string | URL; 939 * input?: string | Buffer | TypedArray | DataView; 940 * stdio?: string | Array; 941 * env?: Record<string, string>; 942 * shell?: string; 943 * uid?: number; 944 * gid?: number; 945 * timeout?: number; 946 * killSignal?: string | number; 947 * maxBuffer?: number; 948 * encoding?: string; 949 * windowsHide?: boolean; 950 * }} [options] 951 * @returns {Buffer | string} 952 */ 953function execSync(command, options) { 954 const opts = normalizeExecArgs(command, options, null); 955 const inheritStderr = !opts.options.stdio; 956 957 const ret = spawnSync(opts.file, opts.options); 958 959 if (inheritStderr && ret.stderr) 960 process.stderr.write(ret.stderr); 961 962 const err = checkExecSyncError(ret, undefined, command); 963 964 if (err) 965 throw err; 966 967 return ret.stdout; 968} 969 970 971function validateArgumentNullCheck(arg, propName) { 972 if (typeof arg === 'string' && StringPrototypeIncludes(arg, '\u0000')) { 973 throw new ERR_INVALID_ARG_VALUE(propName, arg, 'must be a string without null bytes'); 974 } 975} 976 977 978function validateArgumentsNullCheck(args, propName) { 979 for (let i = 0; i < args.length; ++i) { 980 validateArgumentNullCheck(args[i], `${propName}[${i}]`); 981 } 982} 983 984 985function validateTimeout(timeout) { 986 if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) { 987 throw new ERR_OUT_OF_RANGE('timeout', 'an unsigned integer', timeout); 988 } 989} 990 991 992function validateMaxBuffer(maxBuffer) { 993 if (maxBuffer != null && !(typeof maxBuffer === 'number' && maxBuffer >= 0)) { 994 throw new ERR_OUT_OF_RANGE('options.maxBuffer', 995 'a positive number', 996 maxBuffer); 997 } 998} 999 1000 1001function sanitizeKillSignal(killSignal) { 1002 if (typeof killSignal === 'string' || typeof killSignal === 'number') { 1003 return convertToValidSignal(killSignal); 1004 } else if (killSignal != null) { 1005 throw new ERR_INVALID_ARG_TYPE('options.killSignal', 1006 ['string', 'number'], 1007 killSignal); 1008 } 1009} 1010 1011module.exports = { 1012 _forkChild, 1013 ChildProcess, 1014 exec, 1015 execFile, 1016 execFileSync, 1017 execSync, 1018 fork, 1019 spawn, 1020 spawnSync, 1021}; 1022