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