| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | #include <nan.h> |
| | #include <errno.h> |
| | #include <string.h> |
| | #include <stdlib.h> |
| | #include <unistd.h> |
| |
|
| | #include <sys/types.h> |
| | #include <sys/stat.h> |
| | #include <sys/ioctl.h> |
| | #include <sys/wait.h> |
| | #include <fcntl.h> |
| |
|
| | |
| | |
| | #if defined(__GLIBC__) || defined(__CYGWIN__) |
| | #include <pty.h> |
| | #elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) |
| | #include <util.h> |
| | #elif defined(__FreeBSD__) |
| | #include <libutil.h> |
| | #elif defined(__sun) |
| | #include <stropts.h> |
| | #else |
| | #include <pty.h> |
| | #endif |
| |
|
| | #include <termios.h> |
| |
|
| | |
| | #if !defined(VWERASE) && defined(VWERSE) |
| | #define VWERASE VWERSE |
| | #endif |
| | #if !defined(VDISCARD) && defined(VDISCRD) |
| | #define VDISCARD VDISCRD |
| | #endif |
| |
|
| | |
| | |
| | #if defined(__APPLE__) && !TARGET_OS_IPHONE |
| | #include <crt_externs.h> |
| | #define environ (*_NSGetEnviron()) |
| | #else |
| | extern char **environ; |
| | #endif |
| |
|
| | |
| | #if defined(__linux__) |
| | #include <stdio.h> |
| | #include <stdint.h> |
| | #elif defined(__APPLE__) |
| | #include <sys/sysctl.h> |
| | #include <libproc.h> |
| | #endif |
| |
|
| | |
| | |
| | |
| |
|
| | struct pty_baton { |
| | Nan::Persistent<v8::Function> cb; |
| | int exit_code; |
| | int signal_code; |
| | pid_t pid; |
| | uv_async_t async; |
| | uv_thread_t tid; |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | NAN_METHOD(PtyFork); |
| | NAN_METHOD(PtyOpen); |
| | NAN_METHOD(PtyResize); |
| | NAN_METHOD(PtyGetProc); |
| |
|
| | |
| | |
| | |
| |
|
| | static int |
| | pty_execvpe(const char *, char **, char **); |
| |
|
| | static int |
| | pty_nonblock(int); |
| |
|
| | static char * |
| | pty_getproc(int, char *); |
| |
|
| | static int |
| | pty_openpty(int *, int *, char *, |
| | const struct termios *, |
| | const struct winsize *); |
| |
|
| | static pid_t |
| | pty_forkpty(int *, char *, |
| | const struct termios *, |
| | const struct winsize *); |
| |
|
| | static void |
| | pty_waitpid(void *); |
| |
|
| | static void |
| | pty_after_waitpid(uv_async_t *); |
| |
|
| | static void |
| | pty_after_close(uv_handle_t *); |
| |
|
| | NAN_METHOD(PtyFork) { |
| | Nan::HandleScope scope; |
| |
|
| | if (info.Length() != 10 || |
| | !info[0]->IsString() || |
| | !info[1]->IsArray() || |
| | !info[2]->IsArray() || |
| | !info[3]->IsString() || |
| | !info[4]->IsNumber() || |
| | !info[5]->IsNumber() || |
| | !info[6]->IsNumber() || |
| | !info[7]->IsNumber() || |
| | !info[8]->IsBoolean() || |
| | !info[9]->IsFunction()) { |
| | return Nan::ThrowError( |
| | "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, onexit)"); |
| | } |
| |
|
| | |
| | signal(SIGINT, SIG_DFL); |
| |
|
| | |
| | Nan::Utf8String file(info[0]); |
| |
|
| | |
| | int i = 0; |
| | v8::Local<v8::Array> argv_ = v8::Local<v8::Array>::Cast(info[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++) { |
| | Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked()); |
| | argv[i+1] = strdup(*arg); |
| | } |
| |
|
| | |
| | i = 0; |
| | v8::Local<v8::Array> env_ = v8::Local<v8::Array>::Cast(info[2]); |
| | int envc = env_->Length(); |
| | char **env = new char*[envc+1]; |
| | env[envc] = NULL; |
| | for (; i < envc; i++) { |
| | Nan::Utf8String pair(Nan::Get(env_, i).ToLocalChecked()); |
| | env[i] = strdup(*pair); |
| | } |
| |
|
| | |
| | Nan::Utf8String cwd_(info[3]); |
| | char *cwd = strdup(*cwd_); |
| |
|
| | |
| | struct winsize winp; |
| | winp.ws_col = info[4]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_row = info[5]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_xpixel = 0; |
| | winp.ws_ypixel = 0; |
| |
|
| | |
| | struct termios t = termios(); |
| | struct termios *term = &t; |
| | term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT; |
| | if (Nan::To<bool>(info[8]).FromJust()) { |
| | #if defined(IUTF8) |
| | term->c_iflag |= IUTF8; |
| | #endif |
| | } |
| | term->c_oflag = OPOST | ONLCR; |
| | term->c_cflag = CREAD | CS8 | HUPCL; |
| | term->c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL; |
| |
|
| | term->c_cc[VEOF] = 4; |
| | term->c_cc[VEOL] = -1; |
| | term->c_cc[VEOL2] = -1; |
| | term->c_cc[VERASE] = 0x7f; |
| | term->c_cc[VWERASE] = 23; |
| | term->c_cc[VKILL] = 21; |
| | term->c_cc[VREPRINT] = 18; |
| | term->c_cc[VINTR] = 3; |
| | term->c_cc[VQUIT] = 0x1c; |
| | term->c_cc[VSUSP] = 26; |
| | term->c_cc[VSTART] = 17; |
| | term->c_cc[VSTOP] = 19; |
| | term->c_cc[VLNEXT] = 22; |
| | term->c_cc[VDISCARD] = 15; |
| | term->c_cc[VMIN] = 1; |
| | term->c_cc[VTIME] = 0; |
| |
|
| | #if (__APPLE__) |
| | term->c_cc[VDSUSP] = 25; |
| | term->c_cc[VSTATUS] = 20; |
| | #endif |
| |
|
| | cfsetispeed(term, B38400); |
| | cfsetospeed(term, B38400); |
| |
|
| | |
| | int uid = info[6]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | int gid = info[7]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| |
|
| | |
| | int master = -1; |
| | pid_t pid = pty_forkpty(&master, nullptr, term, &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 Nan::ThrowError("forkpty(3) failed."); |
| | case 0: |
| | if (strlen(cwd)) { |
| | if (chdir(cwd) == -1) { |
| | perror("chdir(2) failed."); |
| | _exit(1); |
| | } |
| | } |
| |
|
| | 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 Nan::ThrowError("Could not set master fd to nonblocking."); |
| | } |
| |
|
| | v8::Local<v8::Object> obj = Nan::New<v8::Object>(); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("fd").ToLocalChecked(), |
| | Nan::New<v8::Number>(master)); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("pid").ToLocalChecked(), |
| | Nan::New<v8::Number>(pid)); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("pty").ToLocalChecked(), |
| | Nan::New<v8::String>(ptsname(master)).ToLocalChecked()); |
| |
|
| | pty_baton *baton = new pty_baton(); |
| | baton->exit_code = 0; |
| | baton->signal_code = 0; |
| | baton->cb.Reset(v8::Local<v8::Function>::Cast(info[9])); |
| | baton->pid = pid; |
| | baton->async.data = baton; |
| |
|
| | uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid); |
| |
|
| | uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton)); |
| |
|
| | return info.GetReturnValue().Set(obj); |
| | } |
| |
|
| | return info.GetReturnValue().SetUndefined(); |
| | } |
| |
|
| | NAN_METHOD(PtyOpen) { |
| | Nan::HandleScope scope; |
| |
|
| | if (info.Length() != 2 || |
| | !info[0]->IsNumber() || |
| | !info[1]->IsNumber()) { |
| | return Nan::ThrowError("Usage: pty.open(cols, rows)"); |
| | } |
| |
|
| | |
| | struct winsize winp; |
| | winp.ws_col = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_row = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_xpixel = 0; |
| | winp.ws_ypixel = 0; |
| |
|
| | |
| | int master, slave; |
| | int ret = pty_openpty(&master, &slave, nullptr, NULL, &winp); |
| |
|
| | if (ret == -1) { |
| | return Nan::ThrowError("openpty(3) failed."); |
| | } |
| |
|
| | if (pty_nonblock(master) == -1) { |
| | return Nan::ThrowError("Could not set master fd to nonblocking."); |
| | } |
| |
|
| | if (pty_nonblock(slave) == -1) { |
| | return Nan::ThrowError("Could not set slave fd to nonblocking."); |
| | } |
| |
|
| | v8::Local<v8::Object> obj = Nan::New<v8::Object>(); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("master").ToLocalChecked(), |
| | Nan::New<v8::Number>(master)); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("slave").ToLocalChecked(), |
| | Nan::New<v8::Number>(slave)); |
| | Nan::Set(obj, |
| | Nan::New<v8::String>("pty").ToLocalChecked(), |
| | Nan::New<v8::String>(ptsname(master)).ToLocalChecked()); |
| |
|
| | return info.GetReturnValue().Set(obj); |
| | } |
| |
|
| | NAN_METHOD(PtyResize) { |
| | Nan::HandleScope scope; |
| |
|
| | if (info.Length() != 3 || |
| | !info[0]->IsNumber() || |
| | !info[1]->IsNumber() || |
| | !info[2]->IsNumber()) { |
| | return Nan::ThrowError("Usage: pty.resize(fd, cols, rows)"); |
| | } |
| |
|
| | int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| |
|
| | struct winsize winp; |
| | winp.ws_col = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_row = info[2]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| | winp.ws_xpixel = 0; |
| | winp.ws_ypixel = 0; |
| |
|
| | if (ioctl(fd, TIOCSWINSZ, &winp) == -1) { |
| | switch (errno) { |
| | case EBADF: return Nan::ThrowError("ioctl(2) failed, EBADF"); |
| | case EFAULT: return Nan::ThrowError("ioctl(2) failed, EFAULT"); |
| | case EINVAL: return Nan::ThrowError("ioctl(2) failed, EINVAL"); |
| | case ENOTTY: return Nan::ThrowError("ioctl(2) failed, ENOTTY"); |
| | } |
| | return Nan::ThrowError("ioctl(2) failed"); |
| | } |
| |
|
| | return info.GetReturnValue().SetUndefined(); |
| | } |
| |
|
| | |
| | |
| | |
| | NAN_METHOD(PtyGetProc) { |
| | Nan::HandleScope scope; |
| |
|
| | if (info.Length() != 2 || |
| | !info[0]->IsNumber() || |
| | !info[1]->IsString()) { |
| | return Nan::ThrowError("Usage: pty.process(fd, tty)"); |
| | } |
| |
|
| | int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(); |
| |
|
| | Nan::Utf8String tty_(info[1]); |
| | char *tty = strdup(*tty_); |
| | char *name = pty_getproc(fd, tty); |
| | free(tty); |
| |
|
| | if (name == NULL) { |
| | return info.GetReturnValue().SetUndefined(); |
| | } |
| |
|
| | v8::Local<v8::String> name_ = Nan::New<v8::String>(name).ToLocalChecked(); |
| | free(name); |
| | return info.GetReturnValue().Set(name_); |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| | 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; |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | 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); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static void |
| | pty_waitpid(void *data) { |
| | int ret; |
| | int stat_loc; |
| |
|
| | pty_baton *baton = static_cast<pty_baton*>(data); |
| |
|
| | errno = 0; |
| |
|
| | if ((ret = waitpid(baton->pid, &stat_loc, 0)) != baton->pid) { |
| | if (ret == -1 && errno == EINTR) { |
| | return pty_waitpid(baton); |
| | } |
| | if (ret == -1 && errno == ECHILD) { |
| | |
| | |
| | ; |
| | } else { |
| | assert(false); |
| | } |
| | } |
| |
|
| | if (WIFEXITED(stat_loc)) { |
| | baton->exit_code = WEXITSTATUS(stat_loc); |
| | } |
| |
|
| | if (WIFSIGNALED(stat_loc)) { |
| | baton->signal_code = WTERMSIG(stat_loc); |
| | } |
| |
|
| | uv_async_send(&baton->async); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static void |
| | pty_after_waitpid(uv_async_t *async) { |
| | Nan::HandleScope scope; |
| | pty_baton *baton = static_cast<pty_baton*>(async->data); |
| |
|
| | v8::Local<v8::Value> argv[] = { |
| | Nan::New<v8::Integer>(baton->exit_code), |
| | Nan::New<v8::Integer>(baton->signal_code), |
| | }; |
| |
|
| | v8::Local<v8::Function> cb = Nan::New<v8::Function>(baton->cb); |
| | baton->cb.Reset(); |
| | memset(&baton->cb, -1, sizeof(baton->cb)); |
| | Nan::AsyncResource resource("pty_after_waitpid"); |
| | resource.runInAsyncScope(Nan::GetCurrentContext()->Global(), cb, 2, argv); |
| |
|
| | uv_close((uv_handle_t *)async, pty_after_close); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static void |
| | pty_after_close(uv_handle_t *handle) { |
| | uv_async_t *async = (uv_async_t *)handle; |
| | pty_baton *baton = static_cast<pty_baton*>(async->data); |
| | delete baton; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #if defined(__linux__) |
| |
|
| | 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; |
| | } |
| |
|
| | #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; |
| |
|
| | if ((mib[3] = tcgetpgrp(fd)) == -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; |
| | } |
| |
|
| | return strdup(kp.kp_proc.p_comm); |
| | } |
| |
|
| | #else |
| |
|
| | static char * |
| | pty_getproc(int fd, char *tty) { |
| | return NULL; |
| | } |
| |
|
| | #endif |
| |
|
| | |
| | |
| | |
| |
|
| | static int |
| | pty_openpty(int *amaster, |
| | int *aslave, |
| | char *name, |
| | 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; |
| |
|
| | 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 = 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"); |
| |
|
| | if (termp) tcsetattr(slave, TCSAFLUSH, termp); |
| | if (winp) ioctl(slave, TIOCSWINSZ, winp); |
| |
|
| | return 0; |
| |
|
| | err: |
| | close(master); |
| | return -1; |
| | #else |
| | 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) { |
| | #if defined(__sun) |
| | int master, slave; |
| |
|
| | int ret = pty_openpty(&master, &slave, name, termp, winp); |
| | if (ret == -1) return -1; |
| | if (amaster) *amaster = master; |
| |
|
| | pid_t pid = fork(); |
| |
|
| | switch (pid) { |
| | case -1: |
| | close(master); |
| | close(slave); |
| | return -1; |
| | case 0: |
| | close(master); |
| |
|
| | setsid(); |
| |
|
| | #if defined(TIOCSCTTY) |
| | |
| | if (ioctl(slave, TIOCSCTTY, NULL) == -1) { |
| | _exit(1); |
| | } |
| | #endif |
| |
|
| | dup2(slave, 0); |
| | dup2(slave, 1); |
| | dup2(slave, 2); |
| |
|
| | if (slave > 2) close(slave); |
| |
|
| | return 0; |
| | default: |
| | close(slave); |
| | return pid; |
| | } |
| |
|
| | return -1; |
| | #else |
| | return forkpty(amaster, name, (termios *)termp, (winsize *)winp); |
| | #endif |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | NAN_MODULE_INIT(init) { |
| | Nan::HandleScope scope; |
| | Nan::Export(target, "fork", PtyFork); |
| | Nan::Export(target, "open", PtyOpen); |
| | Nan::Export(target, "resize", PtyResize); |
| | Nan::Export(target, "process", PtyGetProc); |
| | } |
| |
|
| | NODE_MODULE(pty, init) |
| |
|