dgram: send() can accept strings
[platform/upstream/nodejs.git] / lib / child_process.js
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 var StringDecoder = require('string_decoder').StringDecoder;
23 var EventEmitter = require('events').EventEmitter;
24 var net = require('net');
25 var dgram = require('dgram');
26 var assert = require('assert');
27 var util = require('util');
28
29 var Process = process.binding('process_wrap').Process;
30 var uv = process.binding('uv');
31
32 var constants;  // Lazy-loaded process.binding('constants')
33
34 var errnoException = util._errnoException;
35 var handleWraps = {};
36
37 function handleWrapGetter(name, callback) {
38   var cons;
39
40   Object.defineProperty(handleWraps, name, {
41     get: function() {
42       if (!util.isUndefined(cons)) return cons;
43       return cons = callback();
44     }
45   });
46 }
47
48 handleWrapGetter('Pipe', function() {
49   return process.binding('pipe_wrap').Pipe;
50 });
51
52 handleWrapGetter('TTY', function() {
53   return process.binding('tty_wrap').TTY;
54 });
55
56 handleWrapGetter('TCP', function() {
57   return process.binding('tcp_wrap').TCP;
58 });
59
60 handleWrapGetter('UDP', function() {
61   return process.binding('udp_wrap').UDP;
62 });
63
64 // constructors for lazy loading
65 function createPipe(ipc) {
66   return new handleWraps.Pipe(ipc);
67 }
68
69 function createSocket(pipe, readable) {
70   var s = new net.Socket({ handle: pipe });
71
72   if (readable) {
73     s.writable = false;
74     s.readable = true;
75   } else {
76     s.writable = true;
77     s.readable = false;
78   }
79
80   return s;
81 }
82
83
84 // this object contain function to convert TCP objects to native handle objects
85 // and back again.
86 var handleConversion = {
87   'net.Native': {
88     simultaneousAccepts: true,
89
90     send: function(message, handle) {
91       return handle;
92     },
93
94     got: function(message, handle, emit) {
95       emit(handle);
96     }
97   },
98
99   'net.Server': {
100     simultaneousAccepts: true,
101
102     send: function(message, server) {
103       return server._handle;
104     },
105
106     got: function(message, handle, emit) {
107       var server = new net.Server();
108       server.listen(handle, function() {
109         emit(server);
110       });
111     }
112   },
113
114   'net.Socket': {
115     send: function(message, socket) {
116       // if the socket was created by net.Server
117       if (socket.server) {
118         // the slave should keep track of the socket
119         message.key = socket.server._connectionKey;
120
121         var firstTime = !this._channel.sockets.send[message.key];
122         var socketList = getSocketList('send', this, message.key);
123
124         // the server should no longer expose a .connection property
125         // and when asked to close it should query the socket status from
126         // the slaves
127         if (firstTime) socket.server._setupSlave(socketList);
128
129         // Act like socket is detached
130         socket.server._connections--;
131       }
132
133       // remove handle from socket object, it will be closed when the socket
134       // will be sent
135       var handle = socket._handle;
136       handle.onread = function() {};
137       socket._handle = null;
138
139       return handle;
140     },
141
142     postSend: function(handle) {
143       // Close the Socket handle after sending it
144       handle.close();
145     },
146
147     got: function(message, handle, emit) {
148       var socket = new net.Socket({handle: handle});
149       socket.readable = socket.writable = true;
150
151       // if the socket was created by net.Server we will track the socket
152       if (message.key) {
153
154         // add socket to connections list
155         var socketList = getSocketList('got', this, message.key);
156         socketList.add({
157           socket: socket
158         });
159       }
160
161       emit(socket);
162     }
163   },
164
165   'dgram.Native': {
166     simultaneousAccepts: false,
167
168     send: function(message, handle) {
169       return handle;
170     },
171
172     got: function(message, handle, emit) {
173       emit(handle);
174     }
175   },
176
177   'dgram.Socket': {
178     simultaneousAccepts: false,
179
180     send: function(message, socket) {
181       message.dgramType = socket.type;
182
183       return socket._handle;
184     },
185
186     got: function(message, handle, emit) {
187       var socket = new dgram.Socket(message.dgramType);
188
189       socket.bind(handle, function() {
190         emit(socket);
191       });
192     }
193   }
194 };
195
196 // This object keep track of the socket there are sended
197 function SocketListSend(slave, key) {
198   EventEmitter.call(this);
199
200   this.key = key;
201   this.slave = slave;
202 }
203 util.inherits(SocketListSend, EventEmitter);
204
205 SocketListSend.prototype._request = function(msg, cmd, callback) {
206   var self = this;
207
208   if (!this.slave.connected) return onclose();
209   this.slave.send(msg);
210
211   function onclose() {
212     self.slave.removeListener('internalMessage', onreply);
213     callback(new Error('Slave closed before reply'));
214   };
215
216   function onreply(msg) {
217     if (!(msg.cmd === cmd && msg.key === self.key)) return;
218     self.slave.removeListener('disconnect', onclose);
219     self.slave.removeListener('internalMessage', onreply);
220
221     callback(null, msg);
222   };
223
224   this.slave.once('disconnect', onclose);
225   this.slave.on('internalMessage', onreply);
226 };
227
228 SocketListSend.prototype.close = function close(callback) {
229   this._request({
230     cmd: 'NODE_SOCKET_NOTIFY_CLOSE',
231     key: this.key
232   }, 'NODE_SOCKET_ALL_CLOSED', callback);
233 };
234
235 SocketListSend.prototype.getConnections = function getConnections(callback) {
236   this._request({
237     cmd: 'NODE_SOCKET_GET_COUNT',
238     key: this.key
239   }, 'NODE_SOCKET_COUNT', function(err, msg) {
240     if (err) return callback(err);
241     callback(null, msg.count);
242   });
243 };
244
245 // This object keep track of the socket there are received
246 function SocketListReceive(slave, key) {
247   EventEmitter.call(this);
248
249   var self = this;
250
251   this.connections = 0;
252   this.key = key;
253   this.slave = slave;
254
255   function onempty() {
256     if (!self.slave.connected) return;
257
258     self.slave.send({
259       cmd: 'NODE_SOCKET_ALL_CLOSED',
260       key: self.key
261     });
262   }
263
264   this.slave.on('internalMessage', function(msg) {
265     if (msg.key !== self.key) return;
266
267     if (msg.cmd === 'NODE_SOCKET_NOTIFY_CLOSE') {
268       // Already empty
269       if (self.connections === 0) return onempty();
270
271       // Wait for sockets to get closed
272       self.once('empty', onempty);
273     } else if (msg.cmd === 'NODE_SOCKET_GET_COUNT') {
274       if (!self.slave.connected) return;
275       self.slave.send({
276         cmd: 'NODE_SOCKET_COUNT',
277         key: self.key,
278         count: self.connections
279       });
280     }
281   });
282 }
283 util.inherits(SocketListReceive, EventEmitter);
284
285 SocketListReceive.prototype.add = function(obj) {
286   var self = this;
287
288   this.connections++;
289
290   // Notify previous owner of socket about its state change
291   obj.socket.once('close', function() {
292     self.connections--;
293
294     if (self.connections === 0) self.emit('empty');
295   });
296 };
297
298 function getSocketList(type, slave, key) {
299   var sockets = slave._channel.sockets[type];
300   var socketList = sockets[key];
301   if (!socketList) {
302     var Construct = type === 'send' ? SocketListSend : SocketListReceive;
303     socketList = sockets[key] = new Construct(slave, key);
304   }
305   return socketList;
306 }
307
308 var INTERNAL_PREFIX = 'NODE_';
309 function handleMessage(target, message, handle) {
310   var eventName = 'message';
311   if (!util.isNull(message) &&
312       util.isObject(message) &&
313       util.isString(message.cmd) &&
314       message.cmd.length > INTERNAL_PREFIX.length &&
315       message.cmd.slice(0, INTERNAL_PREFIX.length) === INTERNAL_PREFIX) {
316     eventName = 'internalMessage';
317   }
318   target.emit(eventName, message, handle);
319 }
320
321 function setupChannel(target, channel) {
322   target._channel = channel;
323   target._handleQueue = null;
324
325   var decoder = new StringDecoder('utf8');
326   var jsonBuffer = '';
327   channel.buffering = false;
328   channel.onread = function(nread, pool, recvHandle) {
329     // TODO(bnoordhuis) Check that nread > 0.
330     if (pool) {
331       jsonBuffer += decoder.write(pool);
332
333       var i, start = 0;
334
335       //Linebreak is used as a message end sign
336       while ((i = jsonBuffer.indexOf('\n', start)) >= 0) {
337         var json = jsonBuffer.slice(start, i);
338         var message = JSON.parse(json);
339
340         // There will be at most one NODE_HANDLE message in every chunk we
341         // read because SCM_RIGHTS messages don't get coalesced. Make sure
342         // that we deliver the handle with the right message however.
343         if (message && message.cmd === 'NODE_HANDLE')
344           handleMessage(target, message, recvHandle);
345         else
346           handleMessage(target, message, undefined);
347
348         start = i + 1;
349       }
350       jsonBuffer = jsonBuffer.slice(start);
351       this.buffering = jsonBuffer.length !== 0;
352
353     } else {
354       this.buffering = false;
355       target.disconnect();
356       channel.onread = nop;
357       channel.close();
358       maybeClose(target);
359     }
360   };
361
362   // object where socket lists will live
363   channel.sockets = { got: {}, send: {} };
364
365   // handlers will go through this
366   target.on('internalMessage', function(message, handle) {
367     // Once acknowledged - continue sending handles.
368     if (message.cmd === 'NODE_HANDLE_ACK') {
369       assert(util.isArray(target._handleQueue));
370       var queue = target._handleQueue;
371       target._handleQueue = null;
372       queue.forEach(function(args) {
373         target.send(args.message, args.handle);
374       });
375       return;
376     }
377
378     if (message.cmd !== 'NODE_HANDLE') return;
379
380     // Acknowledge handle receival.
381     target.send({ cmd: 'NODE_HANDLE_ACK' });
382
383     var obj = handleConversion[message.type];
384
385     // Update simultaneous accepts on Windows
386     if (process.platform === 'win32') {
387       handle._simultaneousAccepts = false;
388       net._setSimultaneousAccepts(handle);
389     }
390
391     // Convert handle object
392     obj.got.call(this, message, handle, function(handle) {
393       handleMessage(target, message.msg, handle);
394     });
395   });
396
397   target.send = function(message, handle) {
398     if (util.isUndefined(message)) {
399       throw new TypeError('message cannot be undefined');
400     }
401
402     if (!this.connected) {
403       this.emit('error', new Error('channel closed'));
404       return;
405     }
406
407     // package messages with a handle object
408     if (handle) {
409       // this message will be handled by an internalMessage event handler
410       message = {
411         cmd: 'NODE_HANDLE',
412         type: null,
413         msg: message
414       };
415
416       if (handle instanceof net.Socket) {
417         message.type = 'net.Socket';
418       } else if (handle instanceof net.Server) {
419         message.type = 'net.Server';
420       } else if (handle instanceof process.binding('tcp_wrap').TCP ||
421                  handle instanceof process.binding('pipe_wrap').Pipe) {
422         message.type = 'net.Native';
423       } else if (handle instanceof dgram.Socket) {
424         message.type = 'dgram.Socket';
425       } else if (handle instanceof process.binding('udp_wrap').UDP) {
426         message.type = 'dgram.Native';
427       } else {
428         throw new TypeError("This handle type can't be sent");
429       }
430
431       // Queue-up message and handle if we haven't received ACK yet.
432       if (this._handleQueue) {
433         this._handleQueue.push({ message: message.msg, handle: handle });
434         return;
435       }
436
437       var obj = handleConversion[message.type];
438
439       // convert TCP object to native handle object
440       handle = handleConversion[message.type].send.apply(target, arguments);
441
442       // Update simultaneous accepts on Windows
443       if (obj.simultaneousAccepts) {
444         net._setSimultaneousAccepts(handle);
445       }
446     } else if (this._handleQueue) {
447       // Queue request anyway to avoid out-of-order messages.
448       this._handleQueue.push({ message: message, handle: null });
449       return;
450     }
451
452     var req = { oncomplete: nop };
453     var string = JSON.stringify(message) + '\n';
454     var err = channel.writeUtf8String(req, string, handle);
455
456     if (err) {
457       this.emit('error', errnoException(err, 'write'));
458     } else if (handle && !this._handleQueue) {
459       this._handleQueue = [];
460     }
461
462     if (obj && obj.postSend) {
463       req.oncomplete = obj.postSend.bind(null, handle);
464     }
465
466     /* If the master is > 2 read() calls behind, please stop sending. */
467     return channel.writeQueueSize < (65536 * 2);
468   };
469
470   target.connected = true;
471   target.disconnect = function() {
472     if (!this.connected) {
473       this.emit('error', new Error('IPC channel is already disconnected'));
474       return;
475     }
476
477     // do not allow messages to be written
478     this.connected = false;
479     this._channel = null;
480
481     var fired = false;
482     function finish() {
483       if (fired) return;
484       fired = true;
485
486       channel.close();
487       target.emit('disconnect');
488     }
489
490     // If a message is being read, then wait for it to complete.
491     if (channel.buffering) {
492       this.once('message', finish);
493       this.once('internalMessage', finish);
494
495       return;
496     }
497
498     process.nextTick(finish);
499   };
500
501   channel.readStart();
502 }
503
504
505 function nop() { }
506
507 exports.fork = function(modulePath /*, args, options*/) {
508
509   // Get options and args arguments.
510   var options, args, execArgv;
511   if (util.isArray(arguments[1])) {
512     args = arguments[1];
513     options = util._extend({}, arguments[2]);
514   } else {
515     args = [];
516     options = util._extend({}, arguments[1]);
517   }
518
519   // Prepare arguments for fork:
520   execArgv = options.execArgv || process.execArgv;
521   args = execArgv.concat([modulePath], args);
522
523   // Leave stdin open for the IPC channel. stdout and stderr should be the
524   // same as the parent's if silent isn't set.
525   options.stdio = options.silent ? ['pipe', 'pipe', 'pipe', 'ipc'] :
526       [0, 1, 2, 'ipc'];
527
528   options.execPath = options.execPath || process.execPath;
529
530   return spawn(options.execPath, args, options);
531 };
532
533
534 exports._forkChild = function(fd) {
535   // set process.send()
536   var p = createPipe(true);
537   p.open(fd);
538   p.unref();
539   setupChannel(process, p);
540
541   var refs = 0;
542   process.on('newListener', function(name) {
543     if (name !== 'message' && name !== 'disconnect') return;
544     if (++refs === 1) p.ref();
545   });
546   process.on('removeListener', function(name) {
547     if (name !== 'message' && name !== 'disconnect') return;
548     if (--refs === 0) p.unref();
549   });
550 };
551
552
553 exports.exec = function(command /*, options, callback */) {
554   var file, args, options, callback;
555
556   if (util.isFunction(arguments[1])) {
557     options = undefined;
558     callback = arguments[1];
559   } else {
560     options = arguments[1];
561     callback = arguments[2];
562   }
563
564   if (process.platform === 'win32') {
565     file = 'cmd.exe';
566     args = ['/s', '/c', '"' + command + '"'];
567     // Make a shallow copy before patching so we don't clobber the user's
568     // options object.
569     options = util._extend({}, options);
570     options.windowsVerbatimArguments = true;
571   } else {
572     file = '/bin/sh';
573     args = ['-c', command];
574   }
575
576   if (options && options.shell)
577     file = options.shell;
578
579   return exports.execFile(file, args, options, callback);
580 };
581
582
583 exports.execFile = function(file /* args, options, callback */) {
584   var args, callback;
585   var options = {
586     encoding: 'utf8',
587     timeout: 0,
588     maxBuffer: 200 * 1024,
589     killSignal: 'SIGTERM',
590     cwd: null,
591     env: null
592   };
593
594   // Parse the parameters.
595
596   if (util.isFunction(arguments[arguments.length - 1])) {
597     callback = arguments[arguments.length - 1];
598   }
599
600   if (util.isArray(arguments[1])) {
601     args = arguments[1];
602     options = util._extend(options, arguments[2]);
603   } else {
604     args = [];
605     options = util._extend(options, arguments[1]);
606   }
607
608   var child = spawn(file, args, {
609     cwd: options.cwd,
610     env: options.env,
611     windowsVerbatimArguments: !!options.windowsVerbatimArguments
612   });
613
614   var encoding;
615   var _stdout;
616   var _stderr;
617   if (options.encoding !== 'buffer' && Buffer.isEncoding(options.encoding)) {
618     encoding = options.encoding;
619     _stdout = '';
620     _stderr = '';
621   } else {
622     _stdout = [];
623     _stderr = [];
624     encoding = null;
625   }
626   var stdoutLen = 0;
627   var stderrLen = 0;
628   var killed = false;
629   var exited = false;
630   var timeoutId;
631
632   var ex;
633
634   function exithandler(code, signal) {
635     if (exited) return;
636     exited = true;
637
638     if (timeoutId) {
639       clearTimeout(timeoutId);
640       timeoutId = null;
641     }
642
643     if (!callback) return;
644
645     // merge chunks
646     var stdout;
647     var stderr;
648     if (!encoding) {
649       stdout = Buffer.concat(_stdout);
650       stderr = Buffer.concat(_stderr);
651     } else {
652       stdout = _stdout;
653       stderr = _stderr;
654     }
655
656     if (ex) {
657       callback(ex, stdout, stderr);
658     } else if (code === 0 && signal === null) {
659       callback(null, stdout, stderr);
660     } else {
661       ex = new Error('Command failed: ' + stderr);
662       ex.killed = child.killed || killed;
663       ex.code = code < 0 ? uv.errname(code) : code;
664       ex.signal = signal;
665       callback(ex, stdout, stderr);
666     }
667   }
668
669   function errorhandler(e) {
670     ex = e;
671     child.stdout.destroy();
672     child.stderr.destroy();
673     exithandler();
674   }
675
676   function kill() {
677     child.stdout.destroy();
678     child.stderr.destroy();
679
680     killed = true;
681     try {
682       child.kill(options.killSignal);
683     } catch (e) {
684       ex = e;
685       exithandler();
686     }
687   }
688
689   if (options.timeout > 0) {
690     timeoutId = setTimeout(function() {
691       kill();
692       timeoutId = null;
693     }, options.timeout);
694   }
695
696   child.stdout.addListener('data', function(chunk) {
697     stdoutLen += chunk.length;
698
699     if (stdoutLen > options.maxBuffer) {
700       ex = new Error('stdout maxBuffer exceeded.');
701       kill();
702     } else {
703       if (!encoding)
704         _stdout.push(chunk);
705       else
706         _stdout += chunk;
707     }
708   });
709
710   child.stderr.addListener('data', function(chunk) {
711     stderrLen += chunk.length;
712
713     if (stderrLen > options.maxBuffer) {
714       ex = new Error('stderr maxBuffer exceeded.');
715       kill();
716     } else {
717       if (!encoding)
718         _stderr.push(chunk);
719       else
720         _stderr += chunk;
721     }
722   });
723
724   if (encoding) {
725     child.stderr.setEncoding(encoding);
726     child.stdout.setEncoding(encoding);
727   }
728
729   child.addListener('close', exithandler);
730   child.addListener('error', errorhandler);
731
732   return child;
733 };
734
735
736 var spawn = exports.spawn = function(file, args, options) {
737   args = args ? args.slice(0) : [];
738   args.unshift(file);
739
740   var env = (options ? options.env : null) || process.env;
741   var envPairs = [];
742   for (var key in env) {
743     envPairs.push(key + '=' + env[key]);
744   }
745
746   var child = new ChildProcess();
747   if (options && options.customFds && !options.stdio) {
748     options.stdio = options.customFds.map(function(fd) {
749       return fd === -1 ? 'pipe' : fd;
750     });
751   }
752
753   child.spawn({
754     file: file,
755     args: args,
756     cwd: options ? options.cwd : null,
757     windowsVerbatimArguments: !!(options && options.windowsVerbatimArguments),
758     detached: !!(options && options.detached),
759     envPairs: envPairs,
760     stdio: options ? options.stdio : null,
761     uid: options ? options.uid : null,
762     gid: options ? options.gid : null
763   });
764
765   return child;
766 };
767
768
769 function maybeClose(subprocess) {
770   subprocess._closesGot++;
771
772   if (subprocess._closesGot == subprocess._closesNeeded) {
773     subprocess.emit('close', subprocess.exitCode, subprocess.signalCode);
774   }
775 }
776
777
778 function ChildProcess() {
779   EventEmitter.call(this);
780
781   // Initialize TCPWrap and PipeWrap
782   process.binding('tcp_wrap');
783   process.binding('pipe_wrap');
784
785   var self = this;
786
787   this._closesNeeded = 1;
788   this._closesGot = 0;
789   this.connected = false;
790
791   this.signalCode = null;
792   this.exitCode = null;
793   this.killed = false;
794
795   this._handle = new Process();
796   this._handle.owner = this;
797
798   this._handle.onexit = function(exitCode, signalCode) {
799     //
800     // follow 0.4.x behaviour:
801     //
802     // - normally terminated processes don't touch this.signalCode
803     // - signaled processes don't touch this.exitCode
804     //
805     // new in 0.9.x:
806     //
807     // - spawn failures are reported with exitCode < 0
808     //
809     var err = (exitCode < 0) ? errnoException(exitCode, 'spawn') : null;
810
811     if (signalCode) {
812       self.signalCode = signalCode;
813     } else {
814       self.exitCode = exitCode;
815     }
816
817     if (self.stdin) {
818       self.stdin.destroy();
819     }
820
821     self._handle.close();
822     self._handle = null;
823
824     if (exitCode < 0) {
825       self.emit('error', err);
826     } else {
827       self.emit('exit', self.exitCode, self.signalCode);
828     }
829
830     // if any of the stdio streams have not been touched,
831     // then pull all the data through so that it can get the
832     // eof and emit a 'close' event.
833     // Do it on nextTick so that the user has one last chance
834     // to consume the output, if for example they only want to
835     // start reading the data once the process exits.
836     process.nextTick(function() {
837       flushStdio(self);
838     });
839
840     maybeClose(self);
841   };
842 }
843 util.inherits(ChildProcess, EventEmitter);
844
845
846 function flushStdio(subprocess) {
847   subprocess.stdio.forEach(function(stream, fd, stdio) {
848     if (!stream || !stream.readable || stream._consuming ||
849         stream._readableState.flowing)
850       return;
851     stream.resume();
852   });
853 }
854
855
856
857 function getHandleWrapType(stream) {
858   if (stream instanceof handleWraps.Pipe) return 'pipe';
859   if (stream instanceof handleWraps.TTY) return 'tty';
860   if (stream instanceof handleWraps.TCP) return 'tcp';
861   if (stream instanceof handleWraps.UDP) return 'udp';
862
863   return false;
864 }
865
866
867 ChildProcess.prototype.spawn = function(options) {
868   var self = this,
869       ipc,
870       ipcFd,
871       // If no `stdio` option was given - use default
872       stdio = options.stdio || 'pipe';
873
874   // Replace shortcut with an array
875   if (util.isString(stdio)) {
876     switch (stdio) {
877       case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break;
878       case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break;
879       case 'inherit': stdio = [0, 1, 2]; break;
880       default: throw new TypeError('Incorrect value of stdio option: ' + stdio);
881     }
882   } else if (!util.isArray(stdio)) {
883     throw new TypeError('Incorrect value of stdio option: ' + stdio);
884   }
885
886   // At least 3 stdio will be created
887   // Don't concat() a new Array() because it would be sparse, and
888   // stdio.reduce() would skip the sparse elements of stdio.
889   // See http://stackoverflow.com/a/5501711/3561
890   while (stdio.length < 3) stdio.push(undefined);
891
892   // Translate stdio into C++-readable form
893   // (i.e. PipeWraps or fds)
894   stdio = stdio.reduce(function(acc, stdio, i) {
895     function cleanup() {
896       acc.filter(function(stdio) {
897         return stdio.type === 'pipe' || stdio.type === 'ipc';
898       }).forEach(function(stdio) {
899         stdio.handle.close();
900       });
901     }
902
903     // Defaults
904     if (util.isNullOrUndefined(stdio)) {
905       stdio = i < 3 ? 'pipe' : 'ignore';
906     }
907
908     if (stdio === 'ignore') {
909       acc.push({type: 'ignore'});
910     } else if (stdio === 'pipe' || util.isNumber(stdio) && stdio < 0) {
911       acc.push({type: 'pipe', handle: createPipe()});
912     } else if (stdio === 'ipc') {
913       if (!util.isUndefined(ipc)) {
914         // Cleanup previously created pipes
915         cleanup();
916         throw Error('Child process can have only one IPC pipe');
917       }
918
919       ipc = createPipe(true);
920       ipcFd = i;
921
922       acc.push({ type: 'pipe', handle: ipc, ipc: true });
923     } else if (util.isNumber(stdio) || util.isNumber(stdio.fd)) {
924       acc.push({ type: 'fd', fd: stdio.fd || stdio });
925     } else if (getHandleWrapType(stdio) || getHandleWrapType(stdio.handle) ||
926                getHandleWrapType(stdio._handle)) {
927       var handle = getHandleWrapType(stdio) ?
928           stdio :
929           getHandleWrapType(stdio.handle) ? stdio.handle : stdio._handle;
930
931       acc.push({
932         type: 'wrap',
933         wrapType: getHandleWrapType(handle),
934         handle: handle
935       });
936     } else {
937       // Cleanup
938       cleanup();
939       throw new TypeError('Incorrect value for stdio stream: ' + stdio);
940     }
941
942     return acc;
943   }, []);
944
945   options.stdio = stdio;
946
947   if (!util.isUndefined(ipc)) {
948     // Let child process know about opened IPC channel
949     options.envPairs = options.envPairs || [];
950     options.envPairs.push('NODE_CHANNEL_FD=' + ipcFd);
951   }
952
953   var err = this._handle.spawn(options);
954
955   if (err) {
956     // Close all opened fds on error
957     stdio.forEach(function(stdio) {
958       if (stdio.type === 'pipe') {
959         stdio.handle.close();
960       }
961     });
962
963     this._handle.close();
964     this._handle = null;
965     throw errnoException(err, 'spawn');
966   }
967
968   this.pid = this._handle.pid;
969
970   stdio.forEach(function(stdio, i) {
971     if (stdio.type === 'ignore') return;
972
973     if (stdio.ipc) {
974       self._closesNeeded++;
975       return;
976     }
977
978     if (stdio.handle) {
979       // when i === 0 - we're dealing with stdin
980       // (which is the only one writable pipe)
981       stdio.socket = createSocket(self.pid !== 0 ? stdio.handle : null, i > 0);
982
983       if (i > 0 && self.pid !== 0) {
984         self._closesNeeded++;
985         stdio.socket.on('close', function() {
986           maybeClose(self);
987         });
988       }
989     }
990   });
991
992   this.stdin = stdio.length >= 1 && !util.isUndefined(stdio[0].socket) ?
993       stdio[0].socket : null;
994   this.stdout = stdio.length >= 2 && !util.isUndefined(stdio[1].socket) ?
995       stdio[1].socket : null;
996   this.stderr = stdio.length >= 3 && !util.isUndefined(stdio[2].socket) ?
997       stdio[2].socket : null;
998
999   this.stdio = stdio.map(function(stdio) {
1000     return util.isUndefined(stdio.socket) ? null : stdio.socket;
1001   });
1002
1003   // Add .send() method and start listening for IPC data
1004   if (!util.isUndefined(ipc)) setupChannel(this, ipc);
1005
1006   return err;
1007 };
1008
1009
1010 ChildProcess.prototype.kill = function(sig) {
1011   var signal;
1012
1013   if (!constants) {
1014     constants = process.binding('constants');
1015   }
1016
1017   if (sig === 0) {
1018     signal = 0;
1019   } else if (!sig) {
1020     signal = constants['SIGTERM'];
1021   } else {
1022     signal = constants[sig];
1023   }
1024
1025   if (util.isUndefined(signal)) {
1026     throw new Error('Unknown signal: ' + sig);
1027   }
1028
1029   if (this._handle) {
1030     var err = this._handle.kill(signal);
1031     if (err === 0) {
1032       /* Success. */
1033       this.killed = true;
1034       return true;
1035     }
1036     if (err === uv.UV_ESRCH) {
1037       /* Already dead. */
1038     } else if (err === uv.UV_EINVAL || err === uv.UV_ENOSYS) {
1039       /* The underlying platform doesn't support this signal. */
1040       throw errnoException(err, 'kill');
1041     } else {
1042       /* Other error, almost certainly EPERM. */
1043       this.emit('error', errnoException(err, 'kill'));
1044     }
1045   }
1046
1047   /* Kill didn't succeed. */
1048   return false;
1049 };
1050
1051
1052 ChildProcess.prototype.ref = function() {
1053   if (this._handle) this._handle.ref();
1054 };
1055
1056
1057 ChildProcess.prototype.unref = function() {
1058   if (this._handle) this._handle.unref();
1059 };