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 Error, 27 NumberIsInteger, 28 ObjectAssign, 29 ObjectDefineProperty, 30 ObjectPrototypeHasOwnProperty, 31} = primordials; 32 33const { 34 promisify, 35 convertToValidSignal, 36 createDeferredPromise, 37 getSystemErrorName 38} = require('internal/util'); 39const { isArrayBufferView } = require('internal/util/types'); 40let debug = require('internal/util/debuglog').debuglog( 41 'child_process', 42 (fn) => { 43 debug = fn; 44 } 45); 46const { Buffer } = require('buffer'); 47const { Pipe, constants: PipeConstants } = internalBinding('pipe_wrap'); 48 49const { 50 AbortError, 51 codes: errorCodes, 52} = require('internal/errors'); 53const { 54 ERR_INVALID_ARG_VALUE, 55 ERR_CHILD_PROCESS_IPC_REQUIRED, 56 ERR_CHILD_PROCESS_STDIO_MAXBUFFER, 57 ERR_INVALID_ARG_TYPE, 58 ERR_OUT_OF_RANGE, 59} = errorCodes; 60const { clearTimeout, setTimeout } = require('timers'); 61const { getValidatedPath } = require('internal/fs/utils'); 62const { 63 validateString, 64 isInt32, 65 validateAbortSignal, 66 validateBoolean, 67} = require('internal/validators'); 68const child_process = require('internal/child_process'); 69const { 70 getValidStdio, 71 setupChannel, 72 ChildProcess, 73 stdioStringToArray 74} = child_process; 75 76const MAX_BUFFER = 1024 * 1024; 77 78/** 79 * Spawns a new Node.js process + fork. 80 * @param {string} modulePath 81 * @param {string[]} [args] 82 * @param {{ 83 * cwd?: string; 84 * detached?: boolean; 85 * env?: Object; 86 * execPath?: string; 87 * execArgv?: string[]; 88 * gid?: number; 89 * serialization?: string; 90 * signal?: AbortSignal; 91 * killSignal?: string | number; 92 * silent?: boolean; 93 * stdio?: Array | string; 94 * uid?: number; 95 * windowsVerbatimArguments?: boolean; 96 * timeout?: number; 97 * }} [options] 98 * @returns {ChildProcess} 99 */ 100function fork(modulePath /* , args, options */) { 101 validateString(modulePath, 'modulePath'); 102 103 // Get options and args arguments. 104 let execArgv; 105 let options = {}; 106 let args = []; 107 let pos = 1; 108 if (pos < arguments.length && ArrayIsArray(arguments[pos])) { 109 args = arguments[pos++]; 110 } 111 112 if (pos < arguments.length && arguments[pos] == null) { 113 pos++; 114 } 115 116 if (pos < arguments.length && arguments[pos] != null) { 117 if (typeof arguments[pos] !== 'object') { 118 throw new ERR_INVALID_ARG_VALUE(`arguments[${pos}]`, arguments[pos]); 119 } 120 121 options = { ...arguments[pos++] }; 122 } 123 124 // Prepare arguments for fork: 125 execArgv = options.execArgv || process.execArgv; 126 127 if (execArgv === process.execArgv && process._eval != null) { 128 const index = execArgv.lastIndexOf(process._eval); 129 if (index > 0) { 130 // Remove the -e switch to avoid fork bombing ourselves. 131 execArgv = execArgv.slice(); 132 execArgv.splice(index - 1, 2); 133 } 134 } 135 136 args = execArgv.concat([modulePath], args); 137 138 if (typeof options.stdio === 'string') { 139 options.stdio = stdioStringToArray(options.stdio, 'ipc'); 140 } else if (!ArrayIsArray(options.stdio)) { 141 // Use a separate fd=3 for the IPC channel. Inherit stdin, stdout, 142 // and stderr from the parent if silent isn't set. 143 options.stdio = stdioStringToArray( 144 options.silent ? 'pipe' : 'inherit', 145 'ipc'); 146 } else if (!options.stdio.includes('ipc')) { 147 throw new ERR_CHILD_PROCESS_IPC_REQUIRED('options.stdio'); 148 } 149 150 options.execPath = options.execPath || process.execPath; 151 options.shell = false; 152 153 return spawn(options.execPath, args, options); 154} 155 156function _forkChild(fd, serializationMode) { 157 // set process.send() 158 const p = new Pipe(PipeConstants.IPC); 159 p.open(fd); 160 p.unref(); 161 const control = setupChannel(process, p, serializationMode); 162 process.on('newListener', function onNewListener(name) { 163 if (name === 'message' || name === 'disconnect') control.refCounted(); 164 }); 165 process.on('removeListener', function onRemoveListener(name) { 166 if (name === 'message' || name === 'disconnect') control.unrefCounted(); 167 }); 168} 169 170function normalizeExecArgs(command, options, callback) { 171 if (typeof options === 'function') { 172 callback = options; 173 options = undefined; 174 } 175 176 // Make a shallow copy so we don't clobber the user's options object. 177 options = { ...options }; 178 options.shell = typeof options.shell === 'string' ? options.shell : true; 179 180 return { 181 file: command, 182 options: options, 183 callback: callback 184 }; 185} 186 187/** 188 * Spawns a shell executing the given command. 189 * @param {string} command 190 * @param {{ 191 * cmd?: string; 192 * env?: Object; 193 * encoding?: string; 194 * shell?: string; 195 * signal?: AbortSignal; 196 * timeout?: number; 197 * maxBuffer?: number; 198 * killSignal?: string | number; 199 * uid?: number; 200 * gid?: number; 201 * windowsHide?: boolean; 202 * }} [options] 203 * @param {( 204 * error?: Error, 205 * stdout?: string | Buffer, 206 * stderr?: string | Buffer 207 * ) => any} [callback] 208 * @returns {ChildProcess} 209 */ 210function exec(command, options, callback) { 211 const opts = normalizeExecArgs(command, options, callback); 212 return module.exports.execFile(opts.file, 213 opts.options, 214 opts.callback); 215} 216 217const customPromiseExecFunction = (orig) => { 218 return (...args) => { 219 const { promise, resolve, reject } = createDeferredPromise(); 220 221 promise.child = orig(...args, (err, stdout, stderr) => { 222 if (err !== null) { 223 err.stdout = stdout; 224 err.stderr = stderr; 225 reject(err); 226 } else { 227 resolve({ stdout, stderr }); 228 } 229 }); 230 231 return promise; 232 }; 233}; 234 235ObjectDefineProperty(exec, promisify.custom, { 236 enumerable: false, 237 value: customPromiseExecFunction(exec) 238}); 239 240/** 241 * Spawns the specified file as a shell. 242 * @param {string} file 243 * @param {string[]} [args] 244 * @param {{ 245 * cwd?: string; 246 * env?: Object; 247 * encoding?: string; 248 * timeout?: number; 249 * maxBuffer?: number; 250 * killSignal?: string | number; 251 * uid?: number; 252 * gid?: number; 253 * windowsHide?: boolean; 254 * windowsVerbatimArguments?: boolean; 255 * shell?: boolean | string; 256 * signal?: AbortSignal; 257 * }} [options] 258 * @param {( 259 * error?: Error, 260 * stdout?: string | Buffer, 261 * stderr?: string | Buffer 262 * ) => any} [callback] 263 * @returns {ChildProcess} 264 */ 265function execFile(file /* , args, options, callback */) { 266 let args = []; 267 let callback; 268 let options; 269 270 // Parse the optional positional parameters. 271 let pos = 1; 272 if (pos < arguments.length && ArrayIsArray(arguments[pos])) { 273 args = arguments[pos++]; 274 } else if (pos < arguments.length && arguments[pos] == null) { 275 pos++; 276 } 277 278 if (pos < arguments.length && typeof arguments[pos] === 'object') { 279 options = arguments[pos++]; 280 } else if (pos < arguments.length && arguments[pos] == null) { 281 pos++; 282 } 283 284 if (pos < arguments.length && typeof arguments[pos] === 'function') { 285 callback = arguments[pos++]; 286 } 287 288 if (!callback && pos < arguments.length && arguments[pos] != null) { 289 throw new ERR_INVALID_ARG_VALUE('args', arguments[pos]); 290 } 291 292 options = { 293 encoding: 'utf8', 294 timeout: 0, 295 maxBuffer: MAX_BUFFER, 296 killSignal: 'SIGTERM', 297 cwd: null, 298 env: null, 299 shell: false, 300 ...options 301 }; 302 303 // Validate the timeout, if present. 304 validateTimeout(options.timeout); 305 306 // Validate maxBuffer, if present. 307 validateMaxBuffer(options.maxBuffer); 308 309 options.killSignal = sanitizeKillSignal(options.killSignal); 310 311 const child = spawn(file, args, { 312 cwd: options.cwd, 313 env: options.env, 314 gid: options.gid, 315 shell: options.shell, 316 signal: options.signal, 317 uid: options.uid, 318 windowsHide: !!options.windowsHide, 319 windowsVerbatimArguments: !!options.windowsVerbatimArguments 320 }); 321 322 let encoding; 323 const _stdout = []; 324 const _stderr = []; 325 if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) { 326 encoding = options.encoding; 327 } else { 328 encoding = null; 329 } 330 let stdoutLen = 0; 331 let stderrLen = 0; 332 let killed = false; 333 let exited = false; 334 let timeoutId; 335 336 let ex = null; 337 338 let cmd = file; 339 340 function exithandler(code, signal) { 341 if (exited) return; 342 exited = true; 343 344 if (timeoutId) { 345 clearTimeout(timeoutId); 346 timeoutId = null; 347 } 348 349 if (!callback) return; 350 351 // merge chunks 352 let stdout; 353 let stderr; 354 if (encoding || 355 ( 356 child.stdout && 357 child.stdout.readableEncoding 358 )) { 359 stdout = _stdout.join(''); 360 } else { 361 stdout = Buffer.concat(_stdout); 362 } 363 if (encoding || 364 ( 365 child.stderr && 366 child.stderr.readableEncoding 367 )) { 368 stderr = _stderr.join(''); 369 } else { 370 stderr = Buffer.concat(_stderr); 371 } 372 373 if (!ex && code === 0 && signal === null) { 374 callback(null, stdout, stderr); 375 return; 376 } 377 378 if (args.length !== 0) 379 cmd += ` ${args.join(' ')}`; 380 381 if (!ex) { 382 // eslint-disable-next-line no-restricted-syntax 383 ex = new Error('Command failed: ' + cmd + '\n' + stderr); 384 ex.killed = child.killed || killed; 385 ex.code = code < 0 ? getSystemErrorName(code) : code; 386 ex.signal = signal; 387 } 388 389 ex.cmd = cmd; 390 callback(ex, stdout, stderr); 391 } 392 393 function errorhandler(e) { 394 ex = e; 395 396 if (child.stdout) 397 child.stdout.destroy(); 398 399 if (child.stderr) 400 child.stderr.destroy(); 401 402 exithandler(); 403 } 404 405 function kill() { 406 if (child.stdout) 407 child.stdout.destroy(); 408 409 if (child.stderr) 410 child.stderr.destroy(); 411 412 killed = true; 413 try { 414 child.kill(options.killSignal); 415 } catch (e) { 416 ex = e; 417 exithandler(); 418 } 419 } 420 421 if (options.timeout > 0) { 422 timeoutId = setTimeout(function delayedKill() { 423 kill(); 424 timeoutId = null; 425 }, options.timeout); 426 } 427 428 if (child.stdout) { 429 if (encoding) 430 child.stdout.setEncoding(encoding); 431 432 child.stdout.on('data', function onChildStdout(chunk) { 433 const encoding = child.stdout.readableEncoding; 434 const length = encoding ? 435 Buffer.byteLength(chunk, encoding) : 436 chunk.length; 437 stdoutLen += length; 438 439 if (stdoutLen > options.maxBuffer) { 440 const truncatedLen = options.maxBuffer - (stdoutLen - length); 441 _stdout.push(chunk.slice(0, truncatedLen)); 442 443 ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stdout'); 444 kill(); 445 } else { 446 _stdout.push(chunk); 447 } 448 }); 449 } 450 451 if (child.stderr) { 452 if (encoding) 453 child.stderr.setEncoding(encoding); 454 455 child.stderr.on('data', function onChildStderr(chunk) { 456 const encoding = child.stderr.readableEncoding; 457 const length = encoding ? 458 Buffer.byteLength(chunk, encoding) : 459 chunk.length; 460 stderrLen += length; 461 462 if (stderrLen > options.maxBuffer) { 463 const truncatedLen = options.maxBuffer - (stderrLen - length); 464 _stderr.push(chunk.slice(0, truncatedLen)); 465 466 ex = new ERR_CHILD_PROCESS_STDIO_MAXBUFFER('stderr'); 467 kill(); 468 } else { 469 _stderr.push(chunk); 470 } 471 }); 472 } 473 474 child.addListener('close', exithandler); 475 child.addListener('error', errorhandler); 476 477 return child; 478} 479 480ObjectDefineProperty(execFile, promisify.custom, { 481 enumerable: false, 482 value: customPromiseExecFunction(execFile) 483}); 484 485function normalizeSpawnArguments(file, args, options) { 486 validateString(file, 'file'); 487 488 if (file.length === 0) 489 throw new ERR_INVALID_ARG_VALUE('file', file, 'cannot be empty'); 490 491 if (ArrayIsArray(args)) { 492 args = args.slice(0); 493 } else if (args == null) { 494 args = []; 495 } else if (typeof args !== 'object') { 496 throw new ERR_INVALID_ARG_TYPE('args', 'object', args); 497 } else { 498 options = args; 499 args = []; 500 } 501 502 if (options === undefined) 503 options = {}; 504 else if (options === null || typeof options !== 'object') 505 throw new ERR_INVALID_ARG_TYPE('options', 'object', options); 506 507 let cwd = options.cwd; 508 509 // Validate the cwd, if present. 510 if (cwd != null) { 511 cwd = getValidatedPath(cwd, 'options.cwd'); 512 } 513 514 // Validate detached, if present. 515 if (options.detached != null) { 516 validateBoolean(options.detached, 'options.detached'); 517 } 518 519 // Validate the uid, if present. 520 if (options.uid != null && !isInt32(options.uid)) { 521 throw new ERR_INVALID_ARG_TYPE('options.uid', 'int32', options.uid); 522 } 523 524 // Validate the gid, if present. 525 if (options.gid != null && !isInt32(options.gid)) { 526 throw new ERR_INVALID_ARG_TYPE('options.gid', 'int32', options.gid); 527 } 528 529 // Validate the shell, if present. 530 if (options.shell != null && 531 typeof options.shell !== 'boolean' && 532 typeof options.shell !== 'string') { 533 throw new ERR_INVALID_ARG_TYPE('options.shell', 534 ['boolean', 'string'], options.shell); 535 } 536 537 // Validate argv0, if present. 538 if (options.argv0 != null) { 539 validateString(options.argv0, 'options.argv0'); 540 } 541 542 // Validate windowsHide, if present. 543 if (options.windowsHide != null) { 544 validateBoolean(options.windowsHide, 'options.windowsHide'); 545 } 546 547 // Validate windowsVerbatimArguments, if present. 548 let { windowsVerbatimArguments } = options; 549 if (windowsVerbatimArguments != null) { 550 validateBoolean(windowsVerbatimArguments, 551 'options.windowsVerbatimArguments'); 552 } 553 554 if (options.shell) { 555 const command = [file].concat(args).join(' '); 556 // Set the shell, switches, and commands. 557 if (process.platform === 'win32') { 558 if (typeof options.shell === 'string') 559 file = options.shell; 560 else 561 file = process.env.comspec || 'cmd.exe'; 562 // '/d /s /c' is used only for cmd.exe. 563 if (/^(?:.*\\)?cmd(?:\.exe)?$/i.test(file)) { 564 args = ['/d', '/s', '/c', `"${command}"`]; 565 windowsVerbatimArguments = true; 566 } else { 567 args = ['-c', command]; 568 } 569 } else { 570 if (typeof options.shell === 'string') 571 file = options.shell; 572 else if (process.platform === 'android') 573 file = '/system/bin/sh'; 574 else 575 file = '/bin/sh'; 576 args = ['-c', command]; 577 } 578 } 579 580 if (typeof options.argv0 === 'string') { 581 args.unshift(options.argv0); 582 } else { 583 args.unshift(file); 584 } 585 586 const env = options.env || process.env; 587 const envPairs = []; 588 589 // process.env.NODE_V8_COVERAGE always propagates, making it possible to 590 // collect coverage for programs that spawn with white-listed environment. 591 if (process.env.NODE_V8_COVERAGE && 592 !ObjectPrototypeHasOwnProperty(options.env || {}, 'NODE_V8_COVERAGE')) { 593 env.NODE_V8_COVERAGE = process.env.NODE_V8_COVERAGE; 594 } 595 596 // Prototype values are intentionally included. 597 for (const key in env) { 598 const value = env[key]; 599 if (value !== undefined) { 600 envPairs.push(`${key}=${value}`); 601 } 602 } 603 604 return { 605 // Make a shallow copy so we don't clobber the user's options object. 606 ...options, 607 args, 608 cwd, 609 detached: !!options.detached, 610 envPairs, 611 file, 612 windowsHide: !!options.windowsHide, 613 windowsVerbatimArguments: !!windowsVerbatimArguments, 614 }; 615} 616 617function abortChildProcess(child, killSignal) { 618 if (!child) 619 return; 620 try { 621 if (child.kill(killSignal)) { 622 child.emit('error', new AbortError()); 623 } 624 } catch (err) { 625 child.emit('error', err); 626 } 627} 628 629 630/** 631 * Spawns a new process using the given `file`. 632 * @param {string} file 633 * @param {string[]} [args] 634 * @param {{ 635 * cwd?: string; 636 * env?: Object; 637 * argv0?: string; 638 * stdio?: Array | string; 639 * detached?: boolean; 640 * uid?: number; 641 * gid?: number; 642 * serialization?: string; 643 * shell?: boolean | string; 644 * windowsVerbatimArguments?: boolean; 645 * windowsHide?: boolean; 646 * signal?: AbortSignal; 647 * timeout?: number; 648 * killSignal?: string | number; 649 * }} [options] 650 * @returns {ChildProcess} 651 */ 652function spawn(file, args, options) { 653 options = normalizeSpawnArguments(file, args, options); 654 validateTimeout(options.timeout, 'options.timeout'); 655 validateAbortSignal(options.signal, 'options.signal'); 656 const killSignal = sanitizeKillSignal(options.killSignal); 657 const child = new ChildProcess(); 658 659 if (options.signal) { 660 const signal = options.signal; 661 if (signal.aborted) { 662 onAbortListener(); 663 } else { 664 signal.addEventListener('abort', onAbortListener, { once: true }); 665 child.once('exit', 666 () => signal.removeEventListener('abort', onAbortListener)); 667 } 668 669 function onAbortListener() { 670 process.nextTick(() => { 671 abortChildProcess(child, killSignal); 672 }); 673 } 674 } 675 676 debug('spawn', options); 677 child.spawn(options); 678 679 if (options.timeout > 0) { 680 let timeoutId = setTimeout(() => { 681 if (timeoutId) { 682 try { 683 child.kill(killSignal); 684 } catch (err) { 685 child.emit('error', err); 686 } 687 timeoutId = null; 688 } 689 }, options.timeout); 690 691 child.once('exit', () => { 692 if (timeoutId) { 693 clearTimeout(timeoutId); 694 timeoutId = null; 695 } 696 }); 697 } 698 699 return child; 700} 701 702/** 703 * Spawns a new process synchronously using the given `file`. 704 * @param {string} file 705 * @param {string[]} [args] 706 * @param {{ 707 * cwd?: string; 708 * input?: string | Buffer | TypedArray | DataView; 709 * argv0?: string; 710 * stdio?: string | Array; 711 * env?: Object; 712 * uid?: number; 713 * gid?: number; 714 * timeout?: number; 715 * killSignal?: string | number; 716 * maxBuffer?: number; 717 * encoding?: string; 718 * shell?: boolean | string; 719 * windowsVerbatimArguments?: boolean; 720 * windowsHide?: boolean; 721 * }} [options] 722 * @returns {{ 723 * pid: number; 724 * output: Array; 725 * stdout: Buffer | string; 726 * stderr: Buffer | string; 727 * status: number | null; 728 * signal: string | null; 729 * error: Error; 730 * }} 731 */ 732function spawnSync(file, args, options) { 733 options = { 734 maxBuffer: MAX_BUFFER, 735 ...normalizeSpawnArguments(file, args, options) 736 }; 737 738 debug('spawnSync', options); 739 740 // Validate the timeout, if present. 741 validateTimeout(options.timeout); 742 743 // Validate maxBuffer, if present. 744 validateMaxBuffer(options.maxBuffer); 745 746 // Validate and translate the kill signal, if present. 747 options.killSignal = sanitizeKillSignal(options.killSignal); 748 749 options.stdio = getValidStdio(options.stdio || 'pipe', true).stdio; 750 751 if (options.input) { 752 const stdin = options.stdio[0] = { ...options.stdio[0] }; 753 stdin.input = options.input; 754 } 755 756 // We may want to pass data in on any given fd, ensure it is a valid buffer 757 for (let i = 0; i < options.stdio.length; i++) { 758 const input = options.stdio[i] && options.stdio[i].input; 759 if (input != null) { 760 const pipe = options.stdio[i] = { ...options.stdio[i] }; 761 if (isArrayBufferView(input)) { 762 pipe.input = input; 763 } else if (typeof input === 'string') { 764 pipe.input = Buffer.from(input, options.encoding); 765 } else { 766 throw new ERR_INVALID_ARG_TYPE(`options.stdio[${i}]`, 767 ['Buffer', 768 'TypedArray', 769 'DataView', 770 'string'], 771 input); 772 } 773 } 774 } 775 776 return child_process.spawnSync(options); 777} 778 779 780function checkExecSyncError(ret, args, cmd) { 781 let err; 782 if (ret.error) { 783 err = ret.error; 784 } else if (ret.status !== 0) { 785 let msg = 'Command failed: '; 786 msg += cmd || args.join(' '); 787 if (ret.stderr && ret.stderr.length > 0) 788 msg += `\n${ret.stderr.toString()}`; 789 // eslint-disable-next-line no-restricted-syntax 790 err = new Error(msg); 791 } 792 if (err) { 793 ObjectAssign(err, ret); 794 } 795 return err; 796} 797 798/** 799 * Spawns a file as a shell synchronously. 800 * @param {string} command 801 * @param {string[]} [args] 802 * @param {{ 803 * cwd?: string; 804 * input?: string | Buffer | TypedArray | DataView; 805 * stdio?: string | Array; 806 * env?: Object; 807 * uid?: number; 808 * gid?: number; 809 * timeout?: number; 810 * killSignal?: string | number; 811 * maxBuffer?: number; 812 * encoding?: string; 813 * windowsHide?: boolean; 814 * shell?: boolean | string; 815 * }} [options] 816 * @returns {Buffer | string} 817 */ 818function execFileSync(command, args, options) { 819 options = normalizeSpawnArguments(command, args, options); 820 821 const inheritStderr = !options.stdio; 822 const ret = spawnSync(options.file, options.args.slice(1), options); 823 824 if (inheritStderr && ret.stderr) 825 process.stderr.write(ret.stderr); 826 827 const err = checkExecSyncError(ret, options.args, undefined); 828 829 if (err) 830 throw err; 831 832 return ret.stdout; 833} 834 835/** 836 * Spawns a shell executing the given `command` synchronously. 837 * @param {string} command 838 * @param {{ 839 * cwd?: string; 840 * input?: string | Buffer | TypedArray | DataView; 841 * stdio?: string | Array; 842 * env?: Object; 843 * shell?: string; 844 * uid?: number; 845 * gid?: number; 846 * timeout?: number; 847 * killSignal?: string | number; 848 * maxBuffer?: number; 849 * encoding?: string; 850 * windowsHide?: boolean; 851 * }} [options] 852 * @returns {Buffer | string} 853 */ 854function execSync(command, options) { 855 const opts = normalizeExecArgs(command, options, null); 856 const inheritStderr = !opts.options.stdio; 857 858 const ret = spawnSync(opts.file, opts.options); 859 860 if (inheritStderr && ret.stderr) 861 process.stderr.write(ret.stderr); 862 863 const err = checkExecSyncError(ret, opts.args, command); 864 865 if (err) 866 throw err; 867 868 return ret.stdout; 869} 870 871 872function validateTimeout(timeout) { 873 if (timeout != null && !(NumberIsInteger(timeout) && timeout >= 0)) { 874 throw new ERR_OUT_OF_RANGE('timeout', 'an unsigned integer', timeout); 875 } 876} 877 878 879function validateMaxBuffer(maxBuffer) { 880 if (maxBuffer != null && !(typeof maxBuffer === 'number' && maxBuffer >= 0)) { 881 throw new ERR_OUT_OF_RANGE('options.maxBuffer', 882 'a positive number', 883 maxBuffer); 884 } 885} 886 887 888function sanitizeKillSignal(killSignal) { 889 if (typeof killSignal === 'string' || typeof killSignal === 'number') { 890 return convertToValidSignal(killSignal); 891 } else if (killSignal != null) { 892 throw new ERR_INVALID_ARG_TYPE('options.killSignal', 893 ['string', 'number'], 894 killSignal); 895 } 896} 897 898// This level of indirection is here because the other child_process methods 899// call spawn internally but should use different cancellation logic. 900function spawnWithSignal(file, args, options) { 901 // Remove signal from options to spawn 902 // to avoid double emitting of AbortError 903 const opts = options && typeof options === 'object' && ('signal' in options) ? 904 { ...options, signal: undefined } : 905 options; 906 907 if (options?.signal) { 908 // Validate signal, if present 909 validateAbortSignal(options.signal, 'options.signal'); 910 } 911 const child = spawn(file, args, opts); 912 913 if (options?.signal) { 914 const killSignal = sanitizeKillSignal(options.killSignal); 915 916 function kill() { 917 abortChildProcess(child, killSignal); 918 } 919 if (options.signal.aborted) { 920 process.nextTick(kill); 921 } else { 922 options.signal.addEventListener('abort', kill, { once: true }); 923 const remove = () => options.signal.removeEventListener('abort', kill); 924 child.once('exit', remove); 925 } 926 } 927 return child; 928} 929module.exports = { 930 _forkChild, 931 ChildProcess, 932 exec, 933 execFile, 934 execFileSync, 935 execSync, 936 fork, 937 spawn: spawnWithSignal, 938 spawnSync 939}; 940