Skip to content

Commit 9491413

Browse files
piscisaureusry
authored andcommitted
New api for child_process.spawn; ability to set cwd for spawn()ed process
Tests for child_process.spawn() use new API Test for deprecated child_process.spawn() API
1 parent d408de8 commit 9491413

8 files changed

Lines changed: 168 additions & 22 deletions

doc/api.markdown

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -938,11 +938,22 @@ Example:
938938
grep.stdin.end();
939939

940940

941-
### child_process.spawn(command, args=[], env=process.env)
941+
### child_process.spawn(command, args=[], [options])
942942

943-
Launches a new process with the given `command`, command line arguments, and
944-
environment variables. If omitted, `args` defaults to an empty Array, and `env`
945-
defaults to `process.env`.
943+
Launches a new process with the given `command`, with command line arguments in `args`.
944+
If omitted, `args` defaults to an empty Array.
945+
946+
The third argument is used to specify additional options, which defaults to:
947+
948+
{ cwd: undefined
949+
, env: process.env,
950+
, customFds: [-1, -1, -1]
951+
}
952+
953+
`cwd` allows you to specify the working directory from which the process is spawned.
954+
Use `env` to specify environment variables that will be visible to the new process.
955+
With `customFds` it is possible to hook up the new process' [stdin, stout, stderr] to
956+
existing streams; `-1` means that a new stream should be created.
946957

947958
Example of running `ls -lh /usr`, capturing `stdout`, `stderr`, and the exit code:
948959

lib/child_process.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ var Stream = require('net').Stream;
44
var InternalChildProcess = process.binding('child_process').ChildProcess;
55

66

7-
var spawn = exports.spawn = function (path, args, env, customFds) {
7+
var spawn = exports.spawn = function (path, args /*, options OR env, customFds */) {
88
var child = new ChildProcess();
9-
child.spawn(path, args, env, customFds);
9+
child.spawn.apply(child, arguments);
1010
return child;
1111
};
1212

@@ -55,7 +55,7 @@ exports.execFile = function (file /* args, options, callback */) {
5555
}
5656
}
5757

58-
var child = spawn(file, args, options.env);
58+
var child = spawn(file, args, {env: options.env});
5959
var stdout = "";
6060
var stderr = "";
6161
var killed = false;
@@ -161,18 +161,32 @@ ChildProcess.prototype.kill = function (sig) {
161161
};
162162

163163

164-
ChildProcess.prototype.spawn = function (path, args, env, customFds) {
164+
ChildProcess.prototype.spawn = function (path, args, options, customFds) {
165165
args = args || [];
166-
env = env || process.env;
166+
options = options || {};
167+
168+
var cwd, env;
169+
if (options.cwd === undefined && options.env === undefined && options.customFds === undefined) {
170+
// Deprecated API: (path, args, options, env, customFds)
171+
cwd = "";
172+
env = options || process.env;
173+
customFds = customFds || [-1, -1, -1];
174+
}
175+
else {
176+
// Recommended API: (path, args, options)
177+
cwd = options.cwd || "";
178+
env = options.env || process.env;
179+
customFds = options.customFds || [-1, -1, -1];
180+
}
181+
167182
var envPairs = [];
168183
var keys = Object.keys(env);
169184
for (var index = 0, keysLength = keys.length; index < keysLength; index++) {
170185
var key = keys[index];
171186
envPairs.push(key + "=" + env[key]);
172187
}
173188

174-
customFds = customFds || [-1, -1, -1];
175-
var fds = this.fds = this._internal.spawn(path, args, envPairs, customFds);
189+
var fds = this.fds = this._internal.spawn(path, args, cwd, envPairs, customFds);
176190

177191
if (customFds[0] === -1 || customFds[0] === undefined) {
178192
this.stdin.open(fds[0]);

src/node_child_process.cc

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
7575
if (args.Length() < 3 ||
7676
!args[0]->IsString() ||
7777
!args[1]->IsArray() ||
78-
!args[2]->IsArray()) {
78+
!args[2]->IsString() ||
79+
!args[3]->IsArray()) {
7980
return ThrowException(Exception::Error(String::New("Bad argument.")));
8081
}
8182

@@ -99,8 +100,12 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
99100
argv[i+1] = strdup(*arg);
100101
}
101102

102-
// Copy third argument, args[2], into a c-string array called env.
103-
Local<Array> env_handle = Local<Array>::Cast(args[2]);
103+
// Copy third argument, args[2], into a c-string called cwd.
104+
String::Utf8Value arg(args[2]->ToString());
105+
char *cwd = strdup(*arg);
106+
107+
// Copy fourth argument, args[3], into a c-string array called env.
108+
Local<Array> env_handle = Local<Array>::Cast(args[3]);
104109
int envc = env_handle->Length();
105110
char **env = new char*[envc+1]; // heap allocated to detect errors
106111
env[envc] = NULL;
@@ -110,9 +115,9 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
110115
}
111116

112117
int custom_fds[3] = { -1, -1, -1 };
113-
if (args[3]->IsArray()) {
118+
if (args[4]->IsArray()) {
114119
// Set the custom file descriptor values (if any) for the child process
115-
Local<Array> custom_fds_handle = Local<Array>::Cast(args[3]);
120+
Local<Array> custom_fds_handle = Local<Array>::Cast(args[4]);
116121
int custom_fds_len = custom_fds_handle->Length();
117122
for (int i = 0; i < custom_fds_len; i++) {
118123
if (custom_fds_handle->Get(i)->IsUndefined()) continue;
@@ -123,7 +128,7 @@ Handle<Value> ChildProcess::Spawn(const Arguments& args) {
123128

124129
int fds[3];
125130

126-
int r = child->Spawn(argv[0], argv, env, fds, custom_fds);
131+
int r = child->Spawn(argv[0], argv, cwd, env, fds, custom_fds);
127132

128133
for (i = 0; i < argv_length; i++) free(argv[i]);
129134
delete [] argv;
@@ -200,6 +205,7 @@ void ChildProcess::Stop() {
200205
//
201206
int ChildProcess::Spawn(const char *file,
202207
char *const args[],
208+
const char *cwd,
203209
char **env,
204210
int stdio_fds[3],
205211
int custom_fds[3]) {
@@ -251,6 +257,11 @@ int ChildProcess::Spawn(const char *file,
251257
dup2(custom_fds[2], STDERR_FILENO);
252258
}
253259

260+
if (strlen(cwd) && chdir(cwd)) {
261+
perror("chdir()");
262+
_exit(127);
263+
}
264+
254265
environ = env;
255266

256267
execvp(file, args);

src/node_child_process.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ChildProcess : ObjectWrap {
4242
// are readable.
4343
// The user of this class has responsibility to close these pipes after
4444
// the child process exits.
45-
int Spawn(const char *file, char *const argv[], char **env, int stdio_fds[3], int custom_fds[3]);
45+
int Spawn(const char *file, char *const argv[], const char *cwd, char **env, int stdio_fds[3], int custom_fds[3]);
4646

4747
// Simple syscall wrapper. Does not disable the watcher. onexit will be
4848
// called still.

test/simple/test-child-process-custom-fds.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function test1(next) {
2121
console.log("Test 1...");
2222
fs.open(helloPath, 'w', 400, function (err, fd) {
2323
if (err) throw err;
24-
var child = spawn('/bin/echo', [expected], undefined, [-1, fd] );
24+
var child = spawn('/bin/echo', [expected], {customFds: [-1, fd]});
2525

2626
assert.notEqual(child.stdin, null);
2727
assert.equal(child.stdout, null);
@@ -50,7 +50,7 @@ function test2(next) {
5050
fs.open(helloPath, 'r', undefined, function (err, fd) {
5151
var child = spawn(process.argv[0]
5252
, [fixtPath('stdio-filter.js'), 'o', 'a']
53-
, undefined, [fd, -1, -1]);
53+
, {customFds: [fd, -1, -1]});
5454

5555
assert.equal(child.stdin, null);
5656
var actualData = '';
@@ -74,7 +74,7 @@ function test3(next) {
7474
console.log("Test 3...");
7575
var filter = spawn(process.argv[0]
7676
, [fixtPath('stdio-filter.js'), 'o', 'a']);
77-
var echo = spawn('/bin/echo', [expected], undefined, [-1, filter.fds[0]]);
77+
var echo = spawn('/bin/echo', [expected], {customFds: [-1, filter.fds[0]]});
7878
var actualData = '';
7979
filter.stdout.addListener('data', function(data) {
8080
console.log(" Got data --> " + data);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
common = require("../common");
2+
assert = common.assert
3+
spawn = require('child_process').spawn,
4+
path = require('path');
5+
6+
var returns = 0;
7+
8+
/*
9+
Spawns 'pwd' with given options, then test
10+
- whether the exit code equals forCode,
11+
- optionally whether the stdout result (after removing traling whitespace) matches forData
12+
*/
13+
function testCwd(options, forCode, forData) {
14+
var data = "";
15+
16+
var child = spawn('pwd', [], options);
17+
child.stdout.setEncoding('utf8');
18+
19+
child.stdout.addListener('data', function(chunk) {
20+
data += chunk;
21+
});
22+
23+
child.addListener('exit', function(code, signal) {
24+
forData && assert.strictEqual(forData, data.replace(/[\s\r\n]+$/, ''))
25+
assert.strictEqual(forCode, code);
26+
returns--;
27+
});
28+
29+
returns++;
30+
}
31+
32+
// Assume these exist, and 'pwd' gives us the right directory back
33+
testCwd( { cwd: '/bin' }, 0, '/bin' );
34+
testCwd( { cwd: '/dev' }, 0, '/dev' );
35+
testCwd( { cwd: '/' }, 0, '/' );
36+
37+
// Assume this doesn't exist, we expect exitcode=127
38+
testCwd( { cwd: 'does-not-exist' }, 127 );
39+
40+
// Spawn() shouldn't try to chdir() so this should just work
41+
testCwd( undefined, 0 );
42+
testCwd( { }, 0 );
43+
testCwd( { cwd: '' }, 0 );
44+
testCwd( { cwd: undefined }, 0 );
45+
testCwd( { cwd: null }, 0 );
46+
47+
// Check whether all tests actually returned
48+
assert.notEqual(0, returns);
49+
process.addListener('exit', function () {
50+
assert.equal(0, returns);
51+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
var common = require("../common");
2+
var assert = common.assert;
3+
var spawn = require('child_process').spawn;
4+
var path = require('path');
5+
var fs = require('fs');
6+
var exits = 0;
7+
8+
// Test `env` parameter for child_process.spawn(path, args, env, customFds) deprecated api
9+
(function() {
10+
var response = "";
11+
var child = spawn('/usr/bin/env', [], {'HELLO' : 'WORLD'});
12+
13+
child.stdout.setEncoding('utf8');
14+
15+
child.stdout.addListener("data", function (chunk) {
16+
response += chunk;
17+
});
18+
19+
process.addListener('exit', function () {
20+
assert.ok(response.indexOf('HELLO=WORLD') >= 0);
21+
exits++;
22+
});
23+
})();
24+
25+
// Test `customFds` parameter for child_process.spawn(path, args, env, customFds) deprecated api
26+
(function() {
27+
var expected = "hello world";
28+
var helloPath = path.join(common.fixturesDir, "hello.txt");
29+
30+
fs.open(helloPath, 'w', 400, function (err, fd) {
31+
if (err) throw err;
32+
33+
var child = spawn('/bin/echo', [expected], undefined, [-1, fd]);
34+
35+
assert.notEqual(child.stdin, null);
36+
assert.equal(child.stdout, null);
37+
assert.notEqual(child.stderr, null);
38+
39+
child.addListener('exit', function (err) {
40+
if (err) throw err;
41+
42+
fs.close(fd, function (error) {
43+
if (error) throw error;
44+
45+
fs.readFile(helloPath, function (err, data) {
46+
if (err) throw err;
47+
48+
assert.equal(data.toString(), expected + "\n");
49+
exits++;
50+
});
51+
});
52+
});
53+
});
54+
})();
55+
56+
// Check if all child processes exited
57+
process.addListener('exit', function () {
58+
assert.equal(2, exits);
59+
});

test/simple/test-child-process-env.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ common = require("../common");
22
assert = common.assert
33

44
var spawn = require('child_process').spawn;
5-
child = spawn('/usr/bin/env', [], {'HELLO' : 'WORLD'});
5+
child = spawn('/usr/bin/env', [], {env: {'HELLO' : 'WORLD'}});
66

77
response = "";
88

0 commit comments

Comments
 (0)