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 667998b..45c7701 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/lib/pty.js b/lib/pty.js index df707a5..06378ab 100644 --- a/lib/pty.js +++ b/lib/pty.js @@ -8,6 +8,8 @@ var net = require('net'); var tty = require('tty'); var extend = require('extend'); var pty = require('../build/Release/pty.node'); +var stream = require('stream'); +var util = require('util'); /** * Terminal @@ -23,10 +25,12 @@ var pty = require('../build/Release/pty.node'); // }); function Terminal(file, args, opt) { + Terminal.super_.call(this, opt); if (!(this instanceof Terminal)) { return new Terminal(file, args, opt); } + var self = this , env , cwd @@ -99,7 +103,7 @@ function Terminal(file, args, opt) { // setup this.socket.on('error', function(err) { // close - self._close(); + self.close(); // EIO, happens when someone closes our child // process: the only process in the terminal. @@ -131,12 +135,14 @@ function Terminal(file, args, opt) { Terminal.total++; this.socket.on('close', function() { Terminal.total--; - self._close(); + self.close(); self.emit('exit', null); }); env = null; } +util.inherits(Terminal, stream.Duplex); + Terminal.fork = Terminal.spawn = @@ -178,6 +184,7 @@ Terminal.open = function(opt) { self.pid = null; self.fd = term.master; self.pty = term.pty; + self.canPush = false; self.file = process.argv[0] || 'node'; self.name = process.env.TERM || ''; @@ -197,7 +204,12 @@ Terminal.open = function(opt) { Terminal.total++; self.socket.on('close', function() { Terminal.total--; - self._close(); + self.close(); + }); + + self.socket.on('readable', function() { + if(self._canPush) + self._canPush = self.push(self.socket.read()); }); return self; @@ -216,66 +228,13 @@ Terminal.total = 0; * Events */ -// Don't inherit from net.Socket in -// order to avoid collisions. - -Terminal.prototype.write = function(data) { - return this.socket.write(data); -}; - -Terminal.prototype.end = function(data) { - return this.socket.end(data); -}; - -Terminal.prototype.pipe = function(dest, options) { - return this.socket.pipe(dest, options); -}; - -Terminal.prototype.pause = function() { - this.socket.pause(); -}; - -Terminal.prototype.resume = function() { - this.socket.resume(); -}; - -Terminal.prototype.setEncoding = function(enc) { - if (this.socket._decoder) { - delete this.socket._decoder; - } - if (enc) { - this.socket.setEncoding(enc); - } -}; - -Terminal.prototype.addListener = -Terminal.prototype.on = function(type, func) { - this.socket.on(type, func); - return this; -}; - -Terminal.prototype.emit = function() { - return this.socket.emit.apply(this.socket, arguments); -}; - -Terminal.prototype.listeners = function(type) { - return this.socket.listeners(type); -}; - -Terminal.prototype.removeListener = function(type, func) { - this.socket.removeListener(type, func); - return this; -}; - -Terminal.prototype.removeAllListeners = function(type) { - this.socket.removeAllListeners(type); - return this; -}; +Terminal.prototype._read = function() { + this._canPush = this.push(this.socket.read()); +} -Terminal.prototype.once = function(type, func) { - this.socket.once(type, func); - return this; -}; +Terminal.prototype._write = function(chunk, encoding, callback) { + this.socket.write(chunk, encoding, callback); +} Terminal.prototype.__defineGetter__('stdin', function() { return this; @@ -342,10 +301,16 @@ Terminal.prototype.redraw = function() { }, 30); }; +Terminal.prototype.close = Terminal.prototype.end; + 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/lib/pty_win.js b/lib/pty_win.js index 4f8483a..37407da 100644 --- a/lib/pty_win.js +++ b/lib/pty_win.js @@ -157,12 +157,12 @@ function Terminal(file, args, opt) { self.dataPipe = socket; // These events needs to be forwarded. - ['connect', 'data', 'end', 'timeout', 'drain'].forEach(function(event) { - self.dataPipe.on(event, function(data) { + ['connect', 'readable', 'end', 'timeout', 'drain'].forEach(function(event) { + self.dataPipe.on(event, function() { // Wait until the first data event is fired // then we can run deferreds. - if(!self.isReady && event == 'data') { + if(!self.isReady && event == 'readable') { // Terminal is now ready and we can // avoid having to defer method calls. @@ -184,7 +184,7 @@ function Terminal(file, args, opt) { } // Emit to dummy socket - self.socket.emit(event, data); + self.socket.emit.apply(self.socket, arguments); }); }); @@ -263,9 +263,9 @@ Terminal.open = function () { * Events */ -Terminal.prototype.write = function(data) { +Terminal.prototype._write = function(chunk, encoding, callback) { defer(this, function() { - this.dataPipe.write(data); + this.dataPipe.write(chunk, encoding, callback); }); }; diff --git a/package.json b/package.json index 3797d52..cb9dbe3 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,10 @@ "terminal" ], "dependencies": { - "extend": "~1.2.1", - "nan": "~1.0.0" + "extend": "~2.0.0", + "nan": "~1.4.1" }, "devDependencies": { - "mocha": "~1.16.2" + "mocha": "~2.1.0" } } diff --git a/src/unix/pty.cc b/src/unix/pty.cc index c812e2c..e805cb9 100644 --- a/src/unix/pty.cc +++ b/src/unix/pty.cc @@ -14,7 +14,6 @@ */ #include "nan.h" - #include #include #include @@ -22,22 +21,16 @@ #include #include #include +#include #include +#include /* forkpty */ /* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */ #if defined(__GLIBC__) || defined(__CYGWIN__) #include #elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) -/** - * From node v0.10.28 (at least?) there is also a "util.h" in node/src, which would confuse - * the compiler when looking for "util.h". - */ -#if NODE_VERSION_AT_LEAST(0, 10, 28) #include -#else -#include -#endif #elif defined(__FreeBSD__) #include #elif defined(__sun) @@ -73,6 +66,7 @@ NAN_METHOD(PtyFork); NAN_METHOD(PtyOpen); NAN_METHOD(PtyResize); NAN_METHOD(PtyGetProc); +NAN_METHOD(PtyGetStatus); static int pty_execvpe(const char *, char **, char **); @@ -85,134 +79,172 @@ pty_getproc(int, char *); static int pty_openpty(int *, int *, char *, - const struct termios *, - const struct winsize *); + const struct termios *, + const struct winsize *); static pid_t pty_forkpty(int *, char *, - const struct termios *, - const struct winsize *); + 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) */ NAN_METHOD(PtyFork) { - NanScope(); - - if (args.Length() < 6 - || !args[0]->IsString() - || !args[1]->IsArray() - || !args[2]->IsArray() - || !args[3]->IsString() - || !args[4]->IsNumber() - || !args[5]->IsNumber() - || (args.Length() >= 8 && !args[6]->IsNumber()) - || (args.Length() >= 8 && !args[7]->IsNumber())) { - return NanThrowError( - "Usage: pty.fork(file, args, env, cwd, cols, rows[, uid, gid])"); - } - - // node/src/node_child_process.cc - - // file - String::Utf8Value file(args[0]->ToString()); - - // args - int i = 0; - Local argv_ = Local::Cast(args[1]); - int argc = argv_->Length(); - int argl = argc + 1 + 1; - char **argv = new char*[argl]; - argv[0] = strdup(*file); - argv[argl-1] = NULL; - for (; i < argc; i++) { - String::Utf8Value arg(argv_->Get(NanNew(i))->ToString()); - argv[i+1] = strdup(*arg); - } - - // env - i = 0; - Local env_ = Local::Cast(args[2]); - int envc = env_->Length(); - char **env = new char*[envc+1]; - env[envc] = NULL; - for (; i < envc; i++) { - String::Utf8Value pair(env_->Get(NanNew(i))->ToString()); - env[i] = strdup(*pair); - } - - // cwd - String::Utf8Value cwd_(args[3]->ToString()); - char *cwd = strdup(*cwd_); - - // size - struct winsize winp; - winp.ws_col = args[4]->IntegerValue(); - winp.ws_row = args[5]->IntegerValue(); - winp.ws_xpixel = 0; - winp.ws_ypixel = 0; - - // uid / gid - int uid = -1; - int gid = -1; - if (args.Length() >= 8) { - uid = args[6]->IntegerValue(); - gid = args[7]->IntegerValue(); - } - - // fork the pty - int master = -1; - char name[40]; - pid_t pid = pty_forkpty(&master, name, NULL, &winp); - - if (pid) { - for (i = 0; i < argl; i++) free(argv[i]); - delete[] argv; - for (i = 0; i < envc; i++) free(env[i]); - delete[] env; - free(cwd); - } - - switch (pid) { - case -1: - return NanThrowError("forkpty(3) failed."); - case 0: - if (strlen(cwd)) chdir(cwd); - - if (uid != -1 && gid != -1) { - if (setgid(gid) == -1) { - perror("setgid(2) failed."); - _exit(1); - } - if (setuid(uid) == -1) { - perror("setuid(2) failed."); - _exit(1); - } - } - - pty_execvpe(argv[0], argv, env); - - perror("execvp(3) failed."); - _exit(1); - default: - if (pty_nonblock(master) == -1) { - return NanThrowError("Could not set master fd to nonblocking."); - } - - Local obj = NanNew(); - obj->Set(NanNew("fd"), NanNew(master)); - obj->Set(NanNew("pid"), NanNew(pid)); - obj->Set(NanNew("pty"), NanNew(name)); - - NanReturnValue(obj); - } - - NanReturnUndefined(); + NanScope(); + + if (args.Length() < 6 + || !args[0]->IsString() + || !args[1]->IsArray() + || !args[2]->IsArray() + || !args[3]->IsString() + || !args[4]->IsNumber() + || !args[5]->IsNumber() + || (args.Length() >= 8 && !args[6]->IsNumber()) + || (args.Length() >= 8 && !args[7]->IsNumber())) { + return NanThrowError( + "Usage: pty.fork(file, args, env, cwd, cols, rows[, uid, gid])"); + } + + // node/src/node_child_process.cc + + // file + String::Utf8Value file(args[0]->ToString()); + + // args + int i = 0; + Local argv_ = Local::Cast(args[1]); + int argc = argv_->Length(); + int argl = argc + 1 + 1; + char **argv = new char*[argl]; + argv[0] = strdup(*file); + argv[argl-1] = NULL; + for (; i < argc; i++) { + String::Utf8Value arg(argv_->Get(NanNew(i))->ToString()); + argv[i+1] = strdup(*arg); + } + + // env + i = 0; + Local env_ = Local::Cast(args[2]); + int envc = env_->Length(); + char **env = new char*[envc+1]; + env[envc] = NULL; + for (; i < envc; i++) { + String::Utf8Value pair(env_->Get(NanNew(i))->ToString()); + env[i] = strdup(*pair); + } + + // cwd + String::Utf8Value cwd_(args[3]->ToString()); + char *cwd = strdup(*cwd_); + + // size + struct winsize winp; + winp.ws_col = args[4]->IntegerValue(); + winp.ws_row = args[5]->IntegerValue(); + winp.ws_xpixel = 0; + winp.ws_ypixel = 0; + + // uid / gid + int uid = -1; + int gid = -1; + if (args.Length() >= 8) { + uid = args[6]->IntegerValue(); + gid = args[7]->IntegerValue(); + } + + // fork the pty + int master = -1; + char name[40]; + pid_t pid = pty_forkpty(&master, name, NULL, &winp); + + if (pid) { + for (i = 0; i < argl; i++) free(argv[i]); + delete[] argv; + for (i = 0; i < envc; i++) free(env[i]); + delete[] env; + free(cwd); + } + + switch (pid) { + case -1: + return NanThrowError("forkpty(3) failed."); + case 0: + if (strlen(cwd)) chdir(cwd); + + if (uid != -1 && gid != -1) { + if (setgid(gid) == -1) { + perror("setgid(2) failed."); + _exit(1); + } + if (setuid(uid) == -1) { + perror("setuid(2) failed."); + _exit(1); + } + } + + pty_execvpe(argv[0], argv, env); + + perror("execvp(3) failed."); + _exit(1); + default: + if (pty_nonblock(master) == -1) { + return NanThrowError("Could not set master fd to nonblocking."); + } + + Local obj = NanNew(); + obj->Set(NanNew("fd"), NanNew(master)); + obj->Set(NanNew("pid"), NanNew(pid)); + obj->Set(NanNew("pty"), NanNew(name)); + pidMap[pid] = -302; + check_sigchld(); + + NanReturnValue(obj); + } + + NanReturnUndefined(); } /** @@ -221,44 +253,44 @@ NAN_METHOD(PtyFork) { */ NAN_METHOD(PtyOpen) { - NanScope(); - - if (args.Length() != 2 - || !args[0]->IsNumber() - || !args[1]->IsNumber()) { - return NanThrowError("Usage: pty.open(cols, rows)"); - } - - // size - struct winsize winp; - winp.ws_col = args[0]->IntegerValue(); - winp.ws_row = args[1]->IntegerValue(); - winp.ws_xpixel = 0; - winp.ws_ypixel = 0; - - // pty - int master, slave; - char name[40]; - int ret = pty_openpty(&master, &slave, name, NULL, &winp); - - if (ret == -1) { - return NanThrowError("openpty(3) failed."); - } - - if (pty_nonblock(master) == -1) { - return NanThrowError("Could not set master fd to nonblocking."); - } - - if (pty_nonblock(slave) == -1) { - return NanThrowError("Could not set slave fd to nonblocking."); - } - - Local obj = NanNew(); - obj->Set(NanNew("master"), NanNew(master)); - obj->Set(NanNew("slave"), NanNew(slave)); - obj->Set(NanNew("pty"), NanNew(name)); - - NanReturnValue(obj); + NanScope(); + + if (args.Length() != 2 + || !args[0]->IsNumber() + || !args[1]->IsNumber()) { + return NanThrowError("Usage: pty.open(cols, rows)"); + } + + // size + struct winsize winp; + winp.ws_col = args[0]->IntegerValue(); + winp.ws_row = args[1]->IntegerValue(); + winp.ws_xpixel = 0; + winp.ws_ypixel = 0; + + // pty + int master, slave; + char name[40]; + int ret = pty_openpty(&master, &slave, name, NULL, &winp); + + if (ret == -1) { + return NanThrowError("openpty(3) failed."); + } + + if (pty_nonblock(master) == -1) { + return NanThrowError("Could not set master fd to nonblocking."); + } + + if (pty_nonblock(slave) == -1) { + return NanThrowError("Could not set slave fd to nonblocking."); + } + + Local obj = NanNew(); + obj->Set(NanNew("master"), NanNew(master)); + obj->Set(NanNew("slave"), NanNew(slave)); + obj->Set(NanNew("pty"), NanNew(name)); + + NanReturnValue(obj); } /** @@ -267,28 +299,28 @@ NAN_METHOD(PtyOpen) { */ NAN_METHOD(PtyResize) { - NanScope(); + NanScope(); - if (args.Length() != 3 - || !args[0]->IsNumber() - || !args[1]->IsNumber() - || !args[2]->IsNumber()) { - return NanThrowError("Usage: pty.resize(fd, cols, rows)"); - } + if (args.Length() != 3 + || !args[0]->IsNumber() + || !args[1]->IsNumber() + || !args[2]->IsNumber()) { + return NanThrowError("Usage: pty.resize(fd, cols, rows)"); + } - int fd = args[0]->IntegerValue(); + int fd = args[0]->IntegerValue(); - struct winsize winp; - winp.ws_col = args[1]->IntegerValue(); - winp.ws_row = args[2]->IntegerValue(); - winp.ws_xpixel = 0; - winp.ws_ypixel = 0; + struct winsize winp; + winp.ws_col = args[1]->IntegerValue(); + winp.ws_row = args[2]->IntegerValue(); + winp.ws_xpixel = 0; + winp.ws_ypixel = 0; - if (ioctl(fd, TIOCSWINSZ, &winp) == -1) { - return NanThrowError("ioctl(2) failed."); - } + if (ioctl(fd, TIOCSWINSZ, &winp) == -1) { + return NanThrowError("ioctl(2) failed."); + } - NanReturnUndefined(); + NanReturnUndefined(); } /** @@ -298,28 +330,47 @@ NAN_METHOD(PtyResize) { */ NAN_METHOD(PtyGetProc) { - NanScope(); + NanScope(); - if (args.Length() != 2 - || !args[0]->IsNumber() - || !args[1]->IsString()) { - return NanThrowError("Usage: pty.process(fd, tty)"); - } + if (args.Length() != 2 + || !args[0]->IsNumber() + || !args[1]->IsString()) { + return NanThrowError("Usage: pty.process(fd, tty)"); + } - int fd = args[0]->IntegerValue(); + int fd = args[0]->IntegerValue(); - String::Utf8Value tty_(args[1]->ToString()); - char *tty = strdup(*tty_); - char *name = pty_getproc(fd, tty); - free(tty); + String::Utf8Value tty_(args[1]->ToString()); + char *tty = strdup(*tty_); + char *name = pty_getproc(fd, tty); + free(tty); - if (name == NULL) { - NanReturnUndefined(); - } + if (name == NULL) { + NanReturnUndefined(); + } - Local name_ = NanNew(name); - free(name); - NanReturnValue(name_); + Local name_ = NanNew(name); + free(name); + NanReturnValue(name_); +} + +/** + * PtyGetStatus + * Foreground Process Name + * pty.status(pid) + */ +NAN_METHOD(PtyGetStatus) { + NanScope(); + + if (args.Length() != 1 + || !args[0]->IsNumber()) { + return NanThrowError("Usage: pty.status(pid)"); + } + + int pid = args[0]->IntegerValue(); + + Local statusCode = NanNew(pidMap[pid]); + NanReturnValue(statusCode); } /** @@ -330,11 +381,11 @@ NAN_METHOD(PtyGetProc) { // http://www.gnu.org/software/gnulib/manual/html_node/execvpe.html static int pty_execvpe(const char *file, char **argv, char **envp) { - char **old = environ; - environ = envp; - int ret = execvp(file, argv); - environ = old; - return ret; + char **old = environ; + environ = envp; + int ret = execvp(file, argv); + environ = old; + return ret; } /** @@ -343,9 +394,9 @@ pty_execvpe(const char *file, char **argv, char **envp) { static int pty_nonblock(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) return -1; - return fcntl(fd, F_SETFL, flags | O_NONBLOCK); + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) return -1; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } /** @@ -374,73 +425,73 @@ pty_nonblock(int fd) { static char * pty_getproc(int fd, char *tty) { - FILE *f; - char *path, *buf; - size_t len; - int ch; - pid_t pgrp; - int r; - - if ((pgrp = tcgetpgrp(fd)) == -1) { - return NULL; - } - - r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp); - if (r == -1 || path == NULL) return NULL; - - if ((f = fopen(path, "r")) == NULL) { - free(path); - return NULL; - } - - free(path); - - len = 0; - buf = NULL; - while ((ch = fgetc(f)) != EOF) { - if (ch == '\0') break; - buf = (char *)realloc(buf, len + 2); - if (buf == NULL) return NULL; - buf[len++] = ch; - } - - if (buf != NULL) { - buf[len] = '\0'; - } - - fclose(f); - return buf; + FILE *f; + char *path, *buf; + size_t len; + int ch; + pid_t pgrp; + int r; + + if ((pgrp = tcgetpgrp(fd)) == -1) { + return NULL; + } + + r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp); + if (r == -1 || path == NULL) return NULL; + + if ((f = fopen(path, "r")) == NULL) { + free(path); + return NULL; + } + + free(path); + + len = 0; + buf = NULL; + while ((ch = fgetc(f)) != EOF) { + if (ch == '\0') break; + buf = (char *)realloc(buf, len + 2); + if (buf == NULL) return NULL; + buf[len++] = ch; + } + + if (buf != NULL) { + buf[len] = '\0'; + } + + fclose(f); + return buf; } #elif defined(__APPLE__) static char * pty_getproc(int fd, char *tty) { - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; - size_t size; - struct kinfo_proc kp; + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 }; + size_t size; + struct kinfo_proc kp; - if ((mib[3] = tcgetpgrp(fd)) == -1) { - return NULL; - } + if ((mib[3] = tcgetpgrp(fd)) == -1) { + return NULL; + } - size = sizeof kp; - if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) { - return NULL; - } + size = sizeof kp; + if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) { + return NULL; + } - if (*kp.kp_proc.p_comm == '\0') { - return NULL; - } + if (*kp.kp_proc.p_comm == '\0') { + return NULL; + } - return strdup(kp.kp_proc.p_comm); + return strdup(kp.kp_proc.p_comm); } #else static char * pty_getproc(int fd, char *tty) { - return NULL; + return NULL; } #endif @@ -451,100 +502,103 @@ pty_getproc(int fd, char *tty) { static int pty_openpty(int *amaster, int *aslave, char *name, - const struct termios *termp, - const struct winsize *winp) { + const struct termios *termp, + const struct winsize *winp) { #if defined(__sun) - char *slave_name; - int slave; - int master = open("/dev/ptmx", O_RDWR | O_NOCTTY); - if (master == -1) return -1; - if (amaster) *amaster = master; + char *slave_name; + int slave; + int master = open("/dev/ptmx", O_RDWR | O_NOCTTY); + if (master == -1) return -1; + if (amaster) *amaster = master; - if (grantpt(master) == -1) goto err; - if (unlockpt(master) == -1) goto err; + if (grantpt(master) == -1) goto err; + if (unlockpt(master) == -1) goto err; - slave_name = ptsname(master); - if (slave_name == NULL) goto err; - if (name) strcpy(name, slave_name); + slave_name = ptsname(master); + if (slave_name == NULL) goto err; + if (name) strcpy(name, slave_name); - slave = open(slave_name, O_RDWR | O_NOCTTY); - if (slave == -1) goto err; - if (aslave) *aslave = slave; + slave = open(slave_name, O_RDWR | O_NOCTTY); + if (slave == -1) goto err; + if (aslave) *aslave = slave; - ioctl(slave, I_PUSH, "ptem"); - ioctl(slave, I_PUSH, "ldterm"); - ioctl(slave, I_PUSH, "ttcompat"); + ioctl(slave, I_PUSH, "ptem"); + ioctl(slave, I_PUSH, "ldterm"); + ioctl(slave, I_PUSH, "ttcompat"); - if (termp) tcsetattr(slave, TCSAFLUSH, termp); - if (winp) ioctl(slave, TIOCSWINSZ, winp); + if (termp) tcsetattr(slave, TCSAFLUSH, termp); + if (winp) ioctl(slave, TIOCSWINSZ, winp); - return 0; + return 0; err: - close(master); - return -1; + close(master); + return -1; #else - return openpty(amaster, aslave, name, (termios *)termp, (winsize *)winp); + return openpty(amaster, aslave, name, (termios *)termp, (winsize *)winp); #endif } static pid_t pty_forkpty(int *amaster, char *name, - const struct termios *termp, - const struct winsize *winp) { + const struct termios *termp, + const struct winsize *winp) { #if defined(__sun) - int master, slave; + int master, slave; - int ret = pty_openpty(&master, &slave, name, termp, winp); - if (ret == -1) return -1; - if (amaster) *amaster = master; + int ret = pty_openpty(&master, &slave, name, termp, winp); + if (ret == -1) return -1; + if (amaster) *amaster = master; - pid_t pid = fork(); + pid_t pid = fork(); - switch (pid) { - case -1: - close(master); - close(slave); - return -1; - case 0: - close(master); + switch (pid) { + case -1: + close(master); + close(slave); + return -1; + case 0: + close(master); - setsid(); + setsid(); #if defined(TIOCSCTTY) - // glibc does this - if (ioctl(slave, TIOCSCTTY, NULL) == -1) _exit(1); + // glibc does this + if (ioctl(slave, TIOCSCTTY, NULL) == -1) _exit(1); #endif - dup2(slave, 0); - dup2(slave, 1); - dup2(slave, 2); + dup2(slave, 0); + dup2(slave, 1); + dup2(slave, 2); - if (slave > 2) close(slave); + if (slave > 2) close(slave); - return 0; - default: - close(slave); - return pid; - } + return 0; + default: + close(slave); + return pid; + } - return -1; + return -1; #else - return forkpty(amaster, name, (termios *)termp, (winsize *)winp); + return forkpty(amaster, name, (termios *)termp, (winsize *)winp); #endif } + /** * Init */ extern "C" void init(Handle target) { - NanScope(); - NODE_SET_METHOD(target, "fork", PtyFork); - NODE_SET_METHOD(target, "open", PtyOpen); - NODE_SET_METHOD(target, "resize", PtyResize); - NODE_SET_METHOD(target, "process", PtyGetProc); + NanScope(); + 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 b668071..5cb164c 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(); + }); + }); +});