diff --git a/Makefile b/Makefile index 5dbcdd1a00..5d7248a52a 100644 --- a/Makefile +++ b/Makefile @@ -234,8 +234,9 @@ UVTEST += simple/test-tls-request-timeout UVTEST += simple/test-tls-set-encoding # child_process -UVTEST += simple/test-child-process-exit-code.js -UVTEST += simple/test-child-process-buffering.js +UVTEST += simple/test-child-process-exit-code +UVTEST += simple/test-child-process-buffering +UVTEST += simple/test-child-process-exec-cwd test-uv: all diff --git a/lib/child_process_uv.js b/lib/child_process_uv.js index 342578c76f..6352c93828 100644 --- a/lib/child_process_uv.js +++ b/lib/child_process_uv.js @@ -47,6 +47,125 @@ function createSocket(pipe, readable) { } +exports.exec = function(command /*, options, callback */) { + var rest = Array.prototype.slice.call(arguments, 1); + var args = ['/bin/sh', ['-c', command]].concat(rest); + return exports.execFile.apply(this, args); +}; + + +exports.execFile = function(file /* args, options, callback */) { + var args, optionArg, callback; + var options = { + encoding: 'utf8', + timeout: 0, + maxBuffer: 200 * 1024, + killSignal: 'SIGTERM', + setsid: false, + cwd: null, + env: null + }; + + // Parse the parameters. + + if (typeof arguments[arguments.length - 1] === 'function') { + callback = arguments[arguments.length - 1]; + } + + if (Array.isArray(arguments[1])) { + args = arguments[1]; + if (typeof arguments[2] === 'object') optionArg = arguments[2]; + } else { + args = []; + if (typeof arguments[1] === 'object') optionArg = arguments[1]; + } + + // Merge optionArg into options + if (optionArg) { + var keys = Object.keys(options); + for (var i = 0, len = keys.length; i < len; i++) { + var k = keys[i]; + if (optionArg[k] !== undefined) options[k] = optionArg[k]; + } + } + + var child = spawn(file, args, { + cwd: options.cwd, + env: options.env + }); + + var stdout = ''; + var stderr = ''; + var killed = false; + var exited = false; + var timeoutId; + + var err; + + function exithandler(code, signal) { + if (exited) return; + exited = true; + + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + + if (!callback) return; + + if (err) { + callback(err, stdout, stderr); + } else if (code === 0 && signal === null) { + callback(null, stdout, stderr); + } else { + var e = new Error('Command failed: ' + stderr); + e.killed = child.killed || killed; + e.code = code; + e.signal = signal; + callback(e, stdout, stderr); + } + } + + function kill() { + killed = true; + child.kill(options.killSignal); + process.nextTick(function() { + exithandler(null, options.killSignal); + }); + } + + if (options.timeout > 0) { + timeoutId = setTimeout(function() { + kill(); + timeoutId = null; + }, options.timeout); + } + + child.stdout.setEncoding(options.encoding); + child.stderr.setEncoding(options.encoding); + + child.stdout.addListener('data', function(chunk) { + stdout += chunk; + if (stdout.length > options.maxBuffer) { + err = new Error('maxBuffer exceeded.'); + kill(); + } + }); + + child.stderr.addListener('data', function(chunk) { + stderr += chunk; + if (stderr.length > options.maxBuffer) { + err = new Error('maxBuffer exceeded.'); + kill(); + } + }); + + child.addListener('exit', exithandler); + + return child; +}; + + var spawn = exports.spawn = function(file, args, options) { var child = new ChildProcess(); diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 6d9a230a9c..9c56b01583 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -106,7 +106,7 @@ class ProcessWrap : public HandleWrap { // options.cwd Local cwd_v = js_options->Get(String::New("cwd")); if (!cwd_v.IsEmpty() && cwd_v->IsString()) { - String::Utf8Value cwd(js_options->ToString()); + String::Utf8Value cwd(cwd_v->ToString()); options.cwd = strdup(*cwd); } diff --git a/test/simple/test-child-process-exec-env.js b/test/simple/test-child-process-exec-env.js index 64ec327550..8ab8de4f46 100644 --- a/test/simple/test-child-process-exec-env.js +++ b/test/simple/test-child-process-exec-env.js @@ -26,27 +26,28 @@ var success_count = 0; var error_count = 0; var response = ''; -var child = exec('/usr/bin/env', {env: {'HELLO': 'WORLD'}}, - function(err, stdout, stderr) { - if (err) { - error_count++; - console.log('error!: ' + err.code); - console.log('stdout: ' + JSON.stringify(stdout)); - console.log('stderr: ' + JSON.stringify(stderr)); - assert.equal(false, err.killed); - } else { - success_count++; - assert.equal(true, stdout != ''); - } - }); +function after(err, stdout, stderr) { + if (err) { + error_count++; + console.log('error!: ' + err.code); + console.log('stdout: ' + JSON.stringify(stdout)); + console.log('stderr: ' + JSON.stringify(stderr)); + assert.equal(false, err.killed); + } else { + success_count++; + assert.equal(true, stdout != ''); + } +} -child.stdout.setEncoding('utf8'); +var child = exec('/usr/bin/env', { env: { 'HELLO': 'WORLD' } }, after); +child.stdout.setEncoding('utf8'); child.stdout.addListener('data', function(chunk) { response += chunk; }); process.addListener('exit', function() { + console.log("response: ", response); assert.equal(1, success_count); assert.equal(0, error_count); assert.ok(response.indexOf('HELLO=WORLD') >= 0);