• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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