diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..df63076 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - "0.10" + - "0.8" diff --git a/README.md b/README.md index 1fbb78d..6fcbd5c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ term.on('data', function(data) { console.log(data); }); +term.on('close', function() { + console.log('exit status: %d', term.status); +}); + term.write('ls\r'); term.resize(100, 40); term.write('ls /\r'); diff --git a/binding.gyp b/binding.gyp index 43c3693..a6ca63b 100644 --- a/binding.gyp +++ b/binding.gyp @@ -18,7 +18,9 @@ 'src/unix/pty.cc' ], 'libraries': [ - '-lutil' + '-lutil', + '-L/usr/lib', + '-L/usr/local/lib' ], }], # http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html diff --git a/lib/pty.js b/lib/pty.js index 85a084c..cf2b05c 100644 --- a/lib/pty.js +++ b/lib/pty.js @@ -303,6 +303,10 @@ Terminal.prototype.__defineGetter__('process', function() { return pty.process(this.fd, this.pty) || this.file; }); +Terminal.prototype.__defineGetter__('status', function() { + return pty.status(this.pid); +}); + Terminal.prototype._close = function() { this.socket.writable = false; this.socket.readable = false; diff --git a/src/unix/pty.cc b/src/unix/pty.cc index ba09422..acfd5bb 100644 --- a/src/unix/pty.cc +++ b/src/unix/pty.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -22,7 +23,9 @@ #include #include #include +#include #include +#include /* forkpty */ /* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */ @@ -73,6 +76,9 @@ PtyResize(const Arguments&); static Handle PtyGetProc(const Arguments&); +static Handle +PtyGetStatus(const Arguments&); + static int pty_execvpe(const char *, char **, char **); @@ -92,9 +98,45 @@ pty_forkpty(int *, char *, const struct termios *, const struct winsize *); +std::map pidMap; + extern "C" void init(Handle); +static void (*node_sighandler)(int) = NULL; + +void +sigChldHandler(int sig, siginfo_t *sip, void *ctx) { + int status = 0; + pid_t res; + if (pidMap[sip->si_pid] == -302) { // this is one of ours + res = waitpid(sip->si_pid, &status, 0); + pidMap[sip->si_pid] = WEXITSTATUS(status); + } else if (node_sighandler) { + // Can only have one SIGCHLD handler at a time, so we need to call node/libuv's handler. + node_sighandler(sig); + } +} + +void +check_sigchld() { + // retrieve node/libuv's SIGCHLD handler. + struct sigaction node_action; + node_action.sa_flags = 0; + sigaction(SIGCHLD, NULL, &node_action); + if (node_action.sa_sigaction == sigChldHandler) { + return; // it's already installed + } + node_sighandler = node_action.sa_handler; + struct sigaction action; + memset (&action, '\0', sizeof(action)); + action.sa_sigaction = sigChldHandler; + action.sa_flags = SA_SIGINFO; + action.sa_flags |= SA_NOCLDSTOP; + // set new SIGCHLD handler. this will call node/libuv's handler at the end. + sigaction(SIGCHLD, &action, NULL); +} + /** * PtyFork * pty.fork(file, args, env, cwd, cols, rows) @@ -210,6 +252,8 @@ PtyFork(const Arguments& args) { obj->Set(String::New("fd"), Number::New(master)); obj->Set(String::New("pid"), Number::New(pid)); obj->Set(String::New("pty"), String::New(name)); + pidMap[pid] = -302; + check_sigchld(); return scope.Close(obj); } @@ -334,6 +378,28 @@ PtyGetProc(const Arguments& args) { return scope.Close(name_); } +/** + * PtyGetStatus + * Foreground Process Name + * pty.status(pid) + */ + +static Handle +PtyGetStatus(const Arguments& args) { + HandleScope scope; + + if (args.Length() != 1 + || !args[0]->IsNumber()) { + return ThrowException(Exception::Error( + String::New("Usage: pty.status(pid)"))); + } + + int pid = args[0]->IntegerValue(); + + Local statusCode = Number::New(pidMap[pid]); + return scope.Close(statusCode); +} + /** * execvpe */ @@ -546,6 +612,7 @@ pty_forkpty(int *amaster, char *name, #endif } + /** * Init */ @@ -553,10 +620,12 @@ pty_forkpty(int *amaster, char *name, extern "C" void init(Handle target) { HandleScope scope; + check_sigchld(); NODE_SET_METHOD(target, "fork", PtyFork); NODE_SET_METHOD(target, "open", PtyOpen); NODE_SET_METHOD(target, "resize", PtyResize); NODE_SET_METHOD(target, "process", PtyGetProc); + NODE_SET_METHOD(target, "status", PtyGetStatus); } NODE_MODULE(pty, init) diff --git a/test/children/exitCode.js b/test/children/exitCode.js new file mode 100644 index 0000000..bd95aba --- /dev/null +++ b/test/children/exitCode.js @@ -0,0 +1 @@ +process.exit(5); diff --git a/test/index.js b/test/index.js index 7915cb7..517ba5d 100644 --- a/test/index.js +++ b/test/index.js @@ -7,6 +7,7 @@ var tests = [ name: 'should be correctly setup', command: [ 'children/void.js' ], options: { cwd: __dirname }, + exitCode: 0, test: function () { assert.equal(this.file, process.execPath); } @@ -14,6 +15,7 @@ var tests = [ name: 'should support stdin', command: [ 'children/stdin.js' ], options: { cwd: __dirname }, + exitCode: 0, test: function () { this.write('☃'); } @@ -21,6 +23,7 @@ var tests = [ name: 'should support resize', command: [ 'children/resize.js' ], options: { cwd: __dirname }, + exitCode: 0, test: function () { this.resize(100, 100); } @@ -28,7 +31,14 @@ var tests = [ name: 'should change uid/gid', command: [ 'children/uidgid.js' ], options: { cwd: __dirname, uid: 777, gid: 777 }, + exitCode: 0, test: function () {} + }, { + name: 'should report exitCode', + command: [ 'children/exitCode.js' ], + options: { cwd: __dirname }, + test: function () {}, + exitCode: 5 } ]; @@ -42,14 +52,8 @@ describe('Pty', function() { var term = pty.fork(process.execPath, testCase.command, testCase.options); term.pipe(process.stderr); - // any output is considered failure. this is only a workaround - // until the actual error code is passed through - var count = 0; - term.on('data', function (data) { - count++; - }); - term.on('exit', function () { - assert.equal(count, 0); + term.on('close', function () { + assert.equal(term.status, testCase.exitCode); done(); }); @@ -58,3 +62,15 @@ describe('Pty', function() { }); }); }); + +describe('The SIGCHLD handler', function () { + it('should not interfere with child_process', function (done) { + this.timeout(500); + var spawn = require('child_process').spawn; + var proc = spawn('false') + proc.on('close', function (code) { + assert.equal(code, 1); + done(); + }); + }); +});