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