doc: improvements to debugger.markdown copy
[platform/upstream/nodejs.git] / lib / child_process.js
1 'use strict';
2
3 const util = require('util');
4 const internalUtil = require('internal/util');
5 const debug = util.debuglog('child_process');
6 const constants = require('constants');
7
8 const uv = process.binding('uv');
9 const spawn_sync = process.binding('spawn_sync');
10 const Buffer = require('buffer').Buffer;
11 const Pipe = process.binding('pipe_wrap').Pipe;
12 const child_process = require('internal/child_process');
13
14 const errnoException = util._errnoException;
15 const _validateStdio = child_process._validateStdio;
16 const setupChannel = child_process.setupChannel;
17 const ChildProcess = exports.ChildProcess = child_process.ChildProcess;
18
19 exports.fork = function(modulePath /*, args, options*/) {
20
21   // Get options and args arguments.
22   var options, args, execArgv;
23   if (Array.isArray(arguments[1])) {
24     args = arguments[1];
25     options = util._extend({}, arguments[2]);
26   } else if (arguments[1] && typeof arguments[1] !== 'object') {
27     throw new TypeError('Incorrect value of args option');
28   } else {
29     args = [];
30     options = util._extend({}, arguments[1]);
31   }
32
33   // Prepare arguments for fork:
34   execArgv = options.execArgv || process.execArgv;
35
36   if (execArgv === process.execArgv && process._eval != null) {
37     const index = execArgv.lastIndexOf(process._eval);
38     if (index > 0) {
39       // Remove the -e switch to avoid fork bombing ourselves.
40       execArgv = execArgv.slice();
41       execArgv.splice(index - 1, 2);
42     }
43   }
44
45   args = execArgv.concat([modulePath], args);
46
47   // Leave stdin open for the IPC channel. stdout and stderr should be the
48   // same as the parent's if silent isn't set.
49   options.stdio = options.silent ? ['pipe', 'pipe', 'pipe', 'ipc'] :
50       [0, 1, 2, 'ipc'];
51
52   options.execPath = options.execPath || process.execPath;
53
54   return spawn(options.execPath, args, options);
55 };
56
57
58 exports._forkChild = function(fd) {
59   // set process.send()
60   var p = new Pipe(true);
61   p.open(fd);
62   p.unref();
63   const control = setupChannel(process, p);
64   process.on('newListener', function(name) {
65     if (name === 'message' || name === 'disconnect') control.ref();
66   });
67   process.on('removeListener', function(name) {
68     if (name === 'message' || name === 'disconnect') control.unref();
69   });
70 };
71
72
73 function normalizeExecArgs(command /*, options, callback*/) {
74   var file, args, options, callback;
75
76   if (typeof arguments[1] === 'function') {
77     options = undefined;
78     callback = arguments[1];
79   } else {
80     options = arguments[1];
81     callback = arguments[2];
82   }
83
84   if (process.platform === 'win32') {
85     file = process.env.comspec || 'cmd.exe';
86     args = ['/s', '/c', '"' + command + '"'];
87     // Make a shallow copy before patching so we don't clobber the user's
88     // options object.
89     options = util._extend({}, options);
90     options.windowsVerbatimArguments = true;
91   } else {
92     file = '/bin/sh';
93     args = ['-c', command];
94   }
95
96   if (options && options.shell)
97     file = options.shell;
98
99   return {
100     cmd: command,
101     file: file,
102     args: args,
103     options: options,
104     callback: callback
105   };
106 }
107
108
109 exports.exec = function(command /*, options, callback*/) {
110   var opts = normalizeExecArgs.apply(null, arguments);
111   return exports.execFile(opts.file,
112                           opts.args,
113                           opts.options,
114                           opts.callback);
115 };
116
117
118 exports.execFile = function(file /*, args, options, callback*/) {
119   var args = [], callback;
120   var options = {
121     encoding: 'utf8',
122     timeout: 0,
123     maxBuffer: 200 * 1024,
124     killSignal: 'SIGTERM',
125     cwd: null,
126     env: null
127   };
128
129   // Parse the optional positional parameters.
130   var pos = 1;
131   if (pos < arguments.length && Array.isArray(arguments[pos])) {
132     args = arguments[pos++];
133   } else if (pos < arguments.length && arguments[pos] == null) {
134     pos++;
135   }
136
137   if (pos < arguments.length && typeof arguments[pos] === 'object') {
138     options = util._extend(options, arguments[pos++]);
139   } else if (pos < arguments.length && arguments[pos] == null) {
140     pos++;
141   }
142
143   if (pos < arguments.length && typeof arguments[pos] === 'function') {
144     callback = arguments[pos++];
145   }
146
147   if (pos === 1 && arguments.length > 1) {
148     throw new TypeError('Incorrect value of args option');
149   }
150
151   var child = spawn(file, args, {
152     cwd: options.cwd,
153     env: options.env,
154     gid: options.gid,
155     uid: options.uid,
156     windowsVerbatimArguments: !!options.windowsVerbatimArguments
157   });
158
159   var encoding;
160   var _stdout;
161   var _stderr;
162   if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
163     encoding = options.encoding;
164     _stdout = '';
165     _stderr = '';
166   } else {
167     _stdout = [];
168     _stderr = [];
169     encoding = null;
170   }
171   var stdoutLen = 0;
172   var stderrLen = 0;
173   var killed = false;
174   var exited = false;
175   var timeoutId;
176
177   var ex = null;
178
179   function exithandler(code, signal) {
180     if (exited) return;
181     exited = true;
182
183     if (timeoutId) {
184       clearTimeout(timeoutId);
185       timeoutId = null;
186     }
187
188     if (!callback) return;
189
190     // merge chunks
191     var stdout;
192     var stderr;
193     if (!encoding) {
194       stdout = Buffer.concat(_stdout);
195       stderr = Buffer.concat(_stderr);
196     } else {
197       stdout = _stdout;
198       stderr = _stderr;
199     }
200
201     if (ex) {
202       // Will be handled later
203     } else if (code === 0 && signal === null) {
204       callback(null, stdout, stderr);
205       return;
206     }
207
208     var cmd = file;
209     if (args.length !== 0)
210       cmd += ' ' + args.join(' ');
211
212     if (!ex) {
213       ex = new Error('Command failed: ' + cmd + '\n' + stderr);
214       ex.killed = child.killed || killed;
215       ex.code = code < 0 ? uv.errname(code) : code;
216       ex.signal = signal;
217     }
218
219     ex.cmd = cmd;
220     callback(ex, stdout, stderr);
221   }
222
223   function errorhandler(e) {
224     ex = e;
225
226     if (child.stdout)
227       child.stdout.destroy();
228
229     if (child.stderr)
230       child.stderr.destroy();
231
232     exithandler();
233   }
234
235   function kill() {
236     if (child.stdout)
237       child.stdout.destroy();
238
239     if (child.stderr)
240       child.stderr.destroy();
241
242     killed = true;
243     try {
244       child.kill(options.killSignal);
245     } catch (e) {
246       ex = e;
247       exithandler();
248     }
249   }
250
251   if (options.timeout > 0) {
252     timeoutId = setTimeout(function() {
253       kill();
254       timeoutId = null;
255     }, options.timeout);
256   }
257
258   if (child.stdout) {
259     if (encoding)
260       child.stdout.setEncoding(encoding);
261
262     child.stdout.addListener('data', function(chunk) {
263       stdoutLen += chunk.length;
264
265       if (stdoutLen > options.maxBuffer) {
266         ex = new Error('stdout maxBuffer exceeded');
267         kill();
268       } else {
269         if (!encoding)
270           _stdout.push(chunk);
271         else
272           _stdout += chunk;
273       }
274     });
275   }
276
277   if (child.stderr) {
278     if (encoding)
279       child.stderr.setEncoding(encoding);
280
281     child.stderr.addListener('data', function(chunk) {
282       stderrLen += chunk.length;
283
284       if (stderrLen > options.maxBuffer) {
285         ex = new Error('stderr maxBuffer exceeded');
286         kill();
287       } else {
288         if (!encoding)
289           _stderr.push(chunk);
290         else
291           _stderr += chunk;
292       }
293     });
294   }
295
296   child.addListener('close', exithandler);
297   child.addListener('error', errorhandler);
298
299   return child;
300 };
301
302 var _deprecatedCustomFds = internalUtil.deprecate(function(options) {
303   options.stdio = options.customFds.map(function(fd) {
304     return fd === -1 ? 'pipe' : fd;
305   });
306 }, 'child_process: options.customFds option is deprecated. ' +
307    'Use options.stdio instead.');
308
309 function _convertCustomFds(options) {
310   if (options && options.customFds && !options.stdio) {
311     _deprecatedCustomFds(options);
312   }
313 }
314
315 function normalizeSpawnArguments(file /*, args, options*/) {
316   var args, options;
317
318   if (Array.isArray(arguments[1])) {
319     args = arguments[1].slice(0);
320     options = arguments[2];
321   } else if (arguments[1] !== undefined &&
322              (arguments[1] === null || typeof arguments[1] !== 'object')) {
323     throw new TypeError('Incorrect value of args option');
324   } else {
325     args = [];
326     options = arguments[1];
327   }
328
329   if (options === undefined)
330     options = {};
331   else if (options === null || typeof options !== 'object')
332     throw new TypeError('options argument must be an object');
333
334   options = util._extend({}, options);
335   args.unshift(file);
336
337   var env = options.env || process.env;
338   var envPairs = [];
339
340   for (var key in env) {
341     envPairs.push(key + '=' + env[key]);
342   }
343
344   _convertCustomFds(options);
345
346   return {
347     file: file,
348     args: args,
349     options: options,
350     envPairs: envPairs
351   };
352 }
353
354
355 var spawn = exports.spawn = function(/*file, args, options*/) {
356   var opts = normalizeSpawnArguments.apply(null, arguments);
357   var options = opts.options;
358   var child = new ChildProcess();
359
360   debug('spawn', opts.args, options);
361
362   child.spawn({
363     file: opts.file,
364     args: opts.args,
365     cwd: options.cwd,
366     windowsVerbatimArguments: !!options.windowsVerbatimArguments,
367     detached: !!options.detached,
368     envPairs: opts.envPairs,
369     stdio: options.stdio,
370     uid: options.uid,
371     gid: options.gid
372   });
373
374   return child;
375 };
376
377
378 function lookupSignal(signal) {
379   if (typeof signal === 'number')
380     return signal;
381
382   if (!(signal in constants))
383     throw new Error('Unknown signal: ' + signal);
384
385   return constants[signal];
386 }
387
388
389 function spawnSync(/*file, args, options*/) {
390   var opts = normalizeSpawnArguments.apply(null, arguments);
391
392   var options = opts.options;
393
394   var i;
395
396   debug('spawnSync', opts.args, options);
397
398   options.file = opts.file;
399   options.args = opts.args;
400   options.envPairs = opts.envPairs;
401
402   if (options.killSignal)
403     options.killSignal = lookupSignal(options.killSignal);
404
405   options.stdio = _validateStdio(options.stdio || 'pipe', true).stdio;
406
407   if (options.input) {
408     var stdin = options.stdio[0] = util._extend({}, options.stdio[0]);
409     stdin.input = options.input;
410   }
411
412   // We may want to pass data in on any given fd, ensure it is a valid buffer
413   for (i = 0; i < options.stdio.length; i++) {
414     var input = options.stdio[i] && options.stdio[i].input;
415     if (input != null) {
416       var pipe = options.stdio[i] = util._extend({}, options.stdio[i]);
417       if (Buffer.isBuffer(input))
418         pipe.input = input;
419       else if (typeof input === 'string')
420         pipe.input = new Buffer(input, options.encoding);
421       else
422         throw new TypeError(util.format(
423             'stdio[%d] should be Buffer or string not %s',
424             i,
425             typeof input));
426     }
427   }
428
429   var result = spawn_sync.spawn(options);
430
431   if (result.output && options.encoding) {
432     for (i = 0; i < result.output.length; i++) {
433       if (!result.output[i])
434         continue;
435       result.output[i] = result.output[i].toString(options.encoding);
436     }
437   }
438
439   result.stdout = result.output && result.output[1];
440   result.stderr = result.output && result.output[2];
441
442   if (result.error) {
443     result.error = errnoException(result.error, 'spawnSync ' + opts.file);
444     result.error.path = opts.file;
445     result.error.spawnargs = opts.args.slice(1);
446   }
447
448   util._extend(result, opts);
449
450   return result;
451 }
452 exports.spawnSync = spawnSync;
453
454
455 function checkExecSyncError(ret) {
456   if (ret.error || ret.status !== 0) {
457     var err = ret.error;
458     ret.error = null;
459
460     if (!err) {
461       var msg = 'Command failed: ' +
462                 (ret.cmd ? ret.cmd : ret.args.join(' ')) +
463                 (ret.stderr ? '\n' + ret.stderr.toString() : '');
464       err = new Error(msg);
465     }
466
467     util._extend(err, ret);
468     return err;
469   }
470
471   return false;
472 }
473
474
475 function execFileSync(/*command, args, options*/) {
476   var opts = normalizeSpawnArguments.apply(null, arguments);
477   var inheritStderr = !opts.options.stdio;
478
479   var ret = spawnSync(opts.file, opts.args.slice(1), opts.options);
480
481   if (inheritStderr)
482     process.stderr.write(ret.stderr);
483
484   var err = checkExecSyncError(ret);
485
486   if (err)
487     throw err;
488   else
489     return ret.stdout;
490 }
491 exports.execFileSync = execFileSync;
492
493
494 function execSync(/*command, options*/) {
495   var opts = normalizeExecArgs.apply(null, arguments);
496   var inheritStderr = opts.options ? !opts.options.stdio : true;
497
498   var ret = spawnSync(opts.file, opts.args, opts.options);
499   ret.cmd = opts.cmd;
500
501   if (inheritStderr)
502     process.stderr.write(ret.stderr);
503
504   var err = checkExecSyncError(ret);
505
506   if (err)
507     throw err;
508   else
509     return ret.stdout;
510 }
511 exports.execSync = execSync;