Add a parameter to spawn() that sets the child's stdio file descriptors.
After the child is forked, these file descriptors will get dup2()'d to STDIN, STDIO, and STDERR. (API may be changed.)
This commit is contained in:
parent
501136b999
commit
92da636b97
@ -4,9 +4,9 @@ var Stream = require('net').Stream;
|
|||||||
var InternalChildProcess = process.binding('child_process').ChildProcess;
|
var InternalChildProcess = process.binding('child_process').ChildProcess;
|
||||||
|
|
||||||
|
|
||||||
var spawn = exports.spawn = function (path, args, env) {
|
var spawn = exports.spawn = function (path, args, env, customFds) {
|
||||||
var child = new ChildProcess();
|
var child = new ChildProcess();
|
||||||
child.spawn(path, args, env);
|
child.spawn(path, args, env, customFds);
|
||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -104,14 +104,14 @@ function ChildProcess () {
|
|||||||
|
|
||||||
stderr.addListener('close', function () {
|
stderr.addListener('close', function () {
|
||||||
stderrClosed = true;
|
stderrClosed = true;
|
||||||
if (gotCHLD && stdoutClosed) {
|
if (gotCHLD && (!self.stdout || stdoutClosed)) {
|
||||||
self.emit('exit', exitCode, termSignal);
|
self.emit('exit', exitCode, termSignal);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
stdout.addListener('close', function () {
|
stdout.addListener('close', function () {
|
||||||
stdoutClosed = true;
|
stdoutClosed = true;
|
||||||
if (gotCHLD && stderrClosed) {
|
if (gotCHLD && (!self.stderr || stderrClosed)) {
|
||||||
self.emit('exit', exitCode, termSignal);
|
self.emit('exit', exitCode, termSignal);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -120,8 +120,11 @@ function ChildProcess () {
|
|||||||
gotCHLD = true;
|
gotCHLD = true;
|
||||||
exitCode = code;
|
exitCode = code;
|
||||||
termSignal = signal;
|
termSignal = signal;
|
||||||
stdin.end();
|
if (self.stdin) {
|
||||||
if (!stdout.readable && !stderr.readable) {
|
self.stdin.end();
|
||||||
|
}
|
||||||
|
if ( (!self.stdout || !self.stdout.readable)
|
||||||
|
&& (!self.stderr || !self.stderr.readable)) {
|
||||||
self.emit('exit', exitCode, termSignal);
|
self.emit('exit', exitCode, termSignal);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -136,7 +139,7 @@ ChildProcess.prototype.kill = function (sig) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
ChildProcess.prototype.spawn = function (path, args, env) {
|
ChildProcess.prototype.spawn = function (path, args, env, customFds) {
|
||||||
args = args || [];
|
args = args || [];
|
||||||
env = env || process.env;
|
env = env || process.env;
|
||||||
var envPairs = [];
|
var envPairs = [];
|
||||||
@ -146,20 +149,36 @@ ChildProcess.prototype.spawn = function (path, args, env) {
|
|||||||
envPairs.push(key + "=" + env[key]);
|
envPairs.push(key + "=" + env[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fds = this._internal.spawn(path, args, envPairs);
|
customFds = customFds || [-1, -1, -1];
|
||||||
|
var fds = this.fds = this._internal.spawn(path, args, envPairs, customFds);
|
||||||
|
|
||||||
this.stdin.open(fds[0]);
|
if (customFds[0] === -1 || customFds[0] === undefined) {
|
||||||
this.stdin.writable = true;
|
this.stdin.open(fds[0]);
|
||||||
this.stdin.readable = false;
|
this.stdin.writable = true;
|
||||||
|
this.stdin.readable = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stdin = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.stdout.open(fds[1]);
|
if (customFds[1] === -1 || customFds[1] === undefined) {
|
||||||
this.stdout.writable = false;
|
this.stdout.open(fds[1]);
|
||||||
this.stdout.readable = true;
|
this.stdout.writable = false;
|
||||||
this.stdout.resume();
|
this.stdout.readable = true;
|
||||||
|
this.stdout.resume();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stdout = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.stderr.open(fds[2]);
|
if (customFds[2] === -1 || customFds[2] === undefined) {
|
||||||
this.stderr.writable = false;
|
this.stderr.open(fds[2]);
|
||||||
this.stderr.readable = true;
|
this.stderr.writable = false;
|
||||||
this.stderr.resume();
|
this.stderr.readable = true;
|
||||||
|
this.stderr.resume();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.stderr = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ Handle<Value> ChildProcess::New(const Arguments& args) {
|
|||||||
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
|
|
||||||
if (args.Length() != 3 ||
|
if (args.Length() < 3 ||
|
||||||
!args[0]->IsString() ||
|
!args[0]->IsString() ||
|
||||||
!args[1]->IsArray() ||
|
!args[1]->IsArray() ||
|
||||||
!args[2]->IsArray()) {
|
!args[2]->IsArray()) {
|
||||||
@ -101,9 +101,21 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
|
|||||||
env[i] = strdup(*pair);
|
env[i] = strdup(*pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int custom_fds[3] = { -1, -1, -1 };
|
||||||
|
if (args[3]->IsArray()) {
|
||||||
|
// Set the custom file descriptor values (if any) for the child process
|
||||||
|
Local<Array> custom_fds_handle = Local<Array>::Cast(args[3]);
|
||||||
|
int custom_fds_len = custom_fds_handle->Length();
|
||||||
|
for (int i = 0; i < custom_fds_len; i++) {
|
||||||
|
if (custom_fds_handle->Get(i)->IsUndefined()) continue;
|
||||||
|
Local<Integer> fd = custom_fds_handle->Get(i)->ToInteger();
|
||||||
|
custom_fds[i] = fd->Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int fds[3];
|
int fds[3];
|
||||||
|
|
||||||
int r = child->Spawn(argv[0], argv, env, fds);
|
int r = child->Spawn(argv[0], argv, env, fds, custom_fds);
|
||||||
|
|
||||||
for (i = 0; i < argv_length; i++) free(argv[i]);
|
for (i = 0; i < argv_length; i++) free(argv[i]);
|
||||||
delete [] argv;
|
delete [] argv;
|
||||||
@ -181,7 +193,8 @@ void ChildProcess::Stop() {
|
|||||||
int ChildProcess::Spawn(const char *file,
|
int ChildProcess::Spawn(const char *file,
|
||||||
char *const args[],
|
char *const args[],
|
||||||
char **env,
|
char **env,
|
||||||
int stdio_fds[3]) {
|
int stdio_fds[3],
|
||||||
|
int custom_fds[3]) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
assert(pid_ == -1);
|
assert(pid_ == -1);
|
||||||
assert(!ev_is_active(&child_watcher_));
|
assert(!ev_is_active(&child_watcher_));
|
||||||
@ -189,9 +202,9 @@ int ChildProcess::Spawn(const char *file,
|
|||||||
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
|
||||||
|
|
||||||
/* An implementation of popen(), basically */
|
/* An implementation of popen(), basically */
|
||||||
if (pipe(stdin_pipe) < 0 ||
|
if (custom_fds[0] == -1 && pipe(stdin_pipe) < 0 ||
|
||||||
pipe(stdout_pipe) < 0 ||
|
custom_fds[1] == -1 && pipe(stdout_pipe) < 0 ||
|
||||||
pipe(stderr_pipe) < 0) {
|
custom_fds[2] == -1 && pipe(stderr_pipe) < 0) {
|
||||||
perror("pipe()");
|
perror("pipe()");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -206,14 +219,29 @@ int ChildProcess::Spawn(const char *file,
|
|||||||
return -4;
|
return -4;
|
||||||
|
|
||||||
case 0: // Child.
|
case 0: // Child.
|
||||||
close(stdin_pipe[1]); // close write end
|
if (custom_fds[0] == -1) {
|
||||||
dup2(stdin_pipe[0], STDIN_FILENO);
|
close(stdin_pipe[1]); // close write end
|
||||||
|
dup2(stdin_pipe[0], STDIN_FILENO);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dup2(custom_fds[0], STDIN_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
close(stdout_pipe[0]); // close read end
|
if (custom_fds[1] == -1) {
|
||||||
dup2(stdout_pipe[1], STDOUT_FILENO);
|
close(stdout_pipe[0]); // close read end
|
||||||
|
dup2(stdout_pipe[1], STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dup2(custom_fds[1], STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
close(stderr_pipe[0]); // close read end
|
if (custom_fds[2] == -1) {
|
||||||
dup2(stderr_pipe[1], STDERR_FILENO);
|
close(stderr_pipe[0]); // close read end
|
||||||
|
dup2(stderr_pipe[1], STDERR_FILENO);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dup2(custom_fds[2], STDERR_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
environ = env;
|
environ = env;
|
||||||
|
|
||||||
@ -232,17 +260,32 @@ int ChildProcess::Spawn(const char *file,
|
|||||||
Ref();
|
Ref();
|
||||||
handle_->Set(pid_symbol, Integer::New(pid_));
|
handle_->Set(pid_symbol, Integer::New(pid_));
|
||||||
|
|
||||||
close(stdin_pipe[0]);
|
if (custom_fds[0] == -1) {
|
||||||
stdio_fds[0] = stdin_pipe[1];
|
close(stdin_pipe[0]);
|
||||||
SetNonBlocking(stdin_pipe[1]);
|
stdio_fds[0] = stdin_pipe[1];
|
||||||
|
SetNonBlocking(stdin_pipe[1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stdio_fds[0] = custom_fds[0];
|
||||||
|
}
|
||||||
|
|
||||||
close(stdout_pipe[1]);
|
if (custom_fds[1] == -1) {
|
||||||
stdio_fds[1] = stdout_pipe[0];
|
close(stdout_pipe[1]);
|
||||||
SetNonBlocking(stdout_pipe[0]);
|
stdio_fds[1] = stdout_pipe[0];
|
||||||
|
SetNonBlocking(stdout_pipe[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stdio_fds[1] = custom_fds[1];
|
||||||
|
}
|
||||||
|
|
||||||
close(stderr_pipe[1]);
|
if (custom_fds[2] == -1) {
|
||||||
stdio_fds[2] = stderr_pipe[0];
|
close(stderr_pipe[1]);
|
||||||
SetNonBlocking(stderr_pipe[0]);
|
stdio_fds[2] = stderr_pipe[0];
|
||||||
|
SetNonBlocking(stderr_pipe[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stdio_fds[2] = custom_fds[2];
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class ChildProcess : ObjectWrap {
|
|||||||
// are readable.
|
// are readable.
|
||||||
// The user of this class has responsibility to close these pipes after
|
// The user of this class has responsibility to close these pipes after
|
||||||
// the child process exits.
|
// the child process exits.
|
||||||
int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3]);
|
int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3], int custom_fds[3]);
|
||||||
|
|
||||||
// Simple syscall wrapper. Does not disable the watcher. onexit will be
|
// Simple syscall wrapper. Does not disable the watcher. onexit will be
|
||||||
// called still.
|
// called still.
|
||||||
|
11
test/fixtures/stdio-filter.js
vendored
Normal file
11
test/fixtures/stdio-filter.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
sys = require('sys');
|
||||||
|
|
||||||
|
var regexIn = process.argv[2];
|
||||||
|
var replacement = process.argv[3];
|
||||||
|
var re = new RegExp(regexIn, 'g');
|
||||||
|
var stdin = process.openStdin();
|
||||||
|
|
||||||
|
stdin.addListener("data", function (data) {
|
||||||
|
data = data.toString();
|
||||||
|
process.stdout.write(data.replace(re, replacement));
|
||||||
|
});
|
93
test/simple/test-child-process-custom-fds.js
Normal file
93
test/simple/test-child-process-custom-fds.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
require("../common");
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var spawn = require('child_process').spawn;
|
||||||
|
var path = require('path');
|
||||||
|
var fs = require('fs');
|
||||||
|
var sys = require('sys');
|
||||||
|
|
||||||
|
function fixtPath(p) {
|
||||||
|
return path.join(fixturesDir, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected = "hello world";
|
||||||
|
|
||||||
|
// Test the equivalent of:
|
||||||
|
// $ /bin/echo "hello world" > hello.txt
|
||||||
|
var helloPath = fixtPath("hello.txt");
|
||||||
|
|
||||||
|
function test1(next) {
|
||||||
|
puts("Test 1...");
|
||||||
|
fs.open(helloPath, 'w', 400, function (err, fd) {
|
||||||
|
if (err) throw err;
|
||||||
|
var child = spawn('/bin/echo', [expected], undefined, [-1, fd] );
|
||||||
|
|
||||||
|
assert.notEqual(child.stdin, null);
|
||||||
|
assert.equal(child.stdout, null);
|
||||||
|
assert.notEqual(child.stderr, null);
|
||||||
|
|
||||||
|
child.addListener('exit', function (err) {
|
||||||
|
if (err) throw err;
|
||||||
|
fs.close(fd, function (error) {
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
fs.readFile(helloPath, function (err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.equal(data.toString(), expected + "\n");
|
||||||
|
puts(' File was written.');
|
||||||
|
next(test3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the equivalent of:
|
||||||
|
// $ node ../fixture/stdio-filter.js < hello.txt
|
||||||
|
function test2(next) {
|
||||||
|
puts("Test 2...");
|
||||||
|
fs.open(helloPath, 'r', undefined, function (err, fd) {
|
||||||
|
var child = spawn(process.argv[0]
|
||||||
|
, [fixtPath('stdio-filter.js'), 'o', 'a']
|
||||||
|
, undefined, [fd, -1, -1]);
|
||||||
|
|
||||||
|
assert.equal(child.stdin, null);
|
||||||
|
var actualData = '';
|
||||||
|
child.stdout.addListener('data', function (data) {
|
||||||
|
actualData += data.toString();
|
||||||
|
});
|
||||||
|
child.addListener('exit', function (code) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.equal(actualData, "hella warld\n");
|
||||||
|
puts(" File was filtered successfully");
|
||||||
|
fs.close(fd, function () {
|
||||||
|
next(test3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the equivalent of:
|
||||||
|
// $ /bin/echo "hello world" | ../stdio-filter.js a o
|
||||||
|
function test3(next) {
|
||||||
|
puts("Test 3...");
|
||||||
|
var filter = spawn(process.argv[0]
|
||||||
|
, [fixtPath('stdio-filter.js'), 'o', 'a']);
|
||||||
|
var echo = spawn('/bin/echo', [expected], undefined, [-1, filter.fds[0]]);
|
||||||
|
var actualData = '';
|
||||||
|
filter.stdout.addListener('data', function(data) {
|
||||||
|
puts(" Got data --> " + data);
|
||||||
|
actualData += data;
|
||||||
|
});
|
||||||
|
filter.addListener('exit', function(code) {
|
||||||
|
if (code) throw "Return code was " + code;
|
||||||
|
assert.equal(actualData, "hella warld\n");
|
||||||
|
puts(" Talked to another process successfully");
|
||||||
|
});
|
||||||
|
echo.addListener('exit', function(code) {
|
||||||
|
if (code) throw "Return code was " + code;
|
||||||
|
filter.stdin.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test1(test2);
|
Loading…
x
Reference in New Issue
Block a user