2 * Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
3 * Copyright (c) 2017, Daniel Imms (MIT License)
6 * This file is responsible for starting processes
7 * with pseudo-terminal file descriptors.
26 #include <sys/types.h>
28 #include <sys/ioctl.h>
33 /* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */
34 #if defined(__GLIBC__) || defined(__CYGWIN__)
36 #elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)
38 #elif defined(__FreeBSD__)
41 #include <stropts.h> /* for I_PUSH */
46 #include <termios.h> /* tcgetattr, tty_ioctl */
48 /* Some platforms name VWERASE and VDISCARD differently */
49 #if !defined(VWERASE) && defined(VWERSE)
50 #define VWERASE VWERSE
52 #if !defined(VDISCARD) && defined(VDISCRD)
53 #define VDISCARD VDISCRD
56 /* environ for execvpe */
57 /* node/src/node_child_process.cc */
58 #if defined(__APPLE__) && !TARGET_OS_IPHONE
59 #include <crt_externs.h>
60 #define environ (*_NSGetEnviron())
62 extern char **environ;
66 #if defined(__linux__)
69 #elif defined(__APPLE__)
70 #include <sys/sysctl.h>
79 Nan::Persistent<v8::Function> cb;
93 NAN_METHOD(PtyResize);
94 NAN_METHOD(PtyGetProc);
101 pty_execvpe(const char *, char **, char **);
107 pty_getproc(int, char *);
110 pty_openpty(int *, int *, char *,
111 const struct termios *,
112 const struct winsize *);
115 pty_forkpty(int *, char *,
116 const struct termios *,
117 const struct winsize *);
123 pty_after_waitpid(uv_async_t *);
126 pty_after_close(uv_handle_t *);
128 NAN_METHOD(PtyFork) {
129 Nan::HandleScope scope;
131 if (info.Length() != 10 ||
132 !info[0]->IsString() ||
133 !info[1]->IsArray() ||
134 !info[2]->IsArray() ||
135 !info[3]->IsString() ||
136 !info[4]->IsNumber() ||
137 !info[5]->IsNumber() ||
138 !info[6]->IsNumber() ||
139 !info[7]->IsNumber() ||
140 !info[8]->IsBoolean() ||
141 !info[9]->IsFunction()) {
142 return Nan::ThrowError(
143 "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, onexit)");
146 // Make sure the process still listens to SIGINT
147 signal(SIGINT, SIG_DFL);
150 Nan::Utf8String file(info[0]);
154 v8::Local<v8::Array> argv_ = v8::Local<v8::Array>::Cast(info[1]);
155 int argc = argv_->Length();
156 int argl = argc + 1 + 1;
157 char **argv = new char*[argl];
158 argv[0] = strdup(*file);
160 for (; i < argc; i++) {
161 Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked());
162 argv[i+1] = strdup(*arg);
167 v8::Local<v8::Array> env_ = v8::Local<v8::Array>::Cast(info[2]);
168 int envc = env_->Length();
169 char **env = new char*[envc+1];
171 for (; i < envc; i++) {
172 Nan::Utf8String pair(Nan::Get(env_, i).ToLocalChecked());
173 env[i] = strdup(*pair);
177 Nan::Utf8String cwd_(info[3]);
178 char *cwd = strdup(*cwd_);
182 winp.ws_col = info[4]->IntegerValue(Nan::GetCurrentContext()).FromJust();
183 winp.ws_row = info[5]->IntegerValue(Nan::GetCurrentContext()).FromJust();
188 struct termios t = termios();
189 struct termios *term = &t;
190 term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
191 if (Nan::To<bool>(info[8]).FromJust()) {
193 term->c_iflag |= IUTF8;
196 term->c_oflag = OPOST | ONLCR;
197 term->c_cflag = CREAD | CS8 | HUPCL;
198 term->c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
200 term->c_cc[VEOF] = 4;
201 term->c_cc[VEOL] = -1;
202 term->c_cc[VEOL2] = -1;
203 term->c_cc[VERASE] = 0x7f;
204 term->c_cc[VWERASE] = 23;
205 term->c_cc[VKILL] = 21;
206 term->c_cc[VREPRINT] = 18;
207 term->c_cc[VINTR] = 3;
208 term->c_cc[VQUIT] = 0x1c;
209 term->c_cc[VSUSP] = 26;
210 term->c_cc[VSTART] = 17;
211 term->c_cc[VSTOP] = 19;
212 term->c_cc[VLNEXT] = 22;
213 term->c_cc[VDISCARD] = 15;
214 term->c_cc[VMIN] = 1;
215 term->c_cc[VTIME] = 0;
218 term->c_cc[VDSUSP] = 25;
219 term->c_cc[VSTATUS] = 20;
222 cfsetispeed(term, B38400);
223 cfsetospeed(term, B38400);
226 int uid = info[6]->IntegerValue(Nan::GetCurrentContext()).FromJust();
227 int gid = info[7]->IntegerValue(Nan::GetCurrentContext()).FromJust();
231 pid_t pid = pty_forkpty(&master, nullptr, term, &winp);
234 for (i = 0; i < argl; i++) free(argv[i]);
236 for (i = 0; i < envc; i++) free(env[i]);
243 return Nan::ThrowError("forkpty(3) failed.");
246 if (chdir(cwd) == -1) {
247 perror("chdir(2) failed.");
252 if (uid != -1 && gid != -1) {
253 if (setgid(gid) == -1) {
254 perror("setgid(2) failed.");
257 if (setuid(uid) == -1) {
258 perror("setuid(2) failed.");
263 pty_execvpe(argv[0], argv, env);
265 perror("execvp(3) failed.");
268 if (pty_nonblock(master) == -1) {
269 return Nan::ThrowError("Could not set master fd to nonblocking.");
272 v8::Local<v8::Object> obj = Nan::New<v8::Object>();
274 Nan::New<v8::String>("fd").ToLocalChecked(),
275 Nan::New<v8::Number>(master));
277 Nan::New<v8::String>("pid").ToLocalChecked(),
278 Nan::New<v8::Number>(pid));
280 Nan::New<v8::String>("pty").ToLocalChecked(),
281 Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
283 pty_baton *baton = new pty_baton();
284 baton->exit_code = 0;
285 baton->signal_code = 0;
286 baton->cb.Reset(v8::Local<v8::Function>::Cast(info[9]));
288 baton->async.data = baton;
290 uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);
292 uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));
294 return info.GetReturnValue().Set(obj);
297 return info.GetReturnValue().SetUndefined();
300 NAN_METHOD(PtyOpen) {
301 Nan::HandleScope scope;
303 if (info.Length() != 2 ||
304 !info[0]->IsNumber() ||
305 !info[1]->IsNumber()) {
306 return Nan::ThrowError("Usage: pty.open(cols, rows)");
311 winp.ws_col = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
312 winp.ws_row = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust();
318 int ret = pty_openpty(&master, &slave, nullptr, NULL, &winp);
321 return Nan::ThrowError("openpty(3) failed.");
324 if (pty_nonblock(master) == -1) {
325 return Nan::ThrowError("Could not set master fd to nonblocking.");
328 if (pty_nonblock(slave) == -1) {
329 return Nan::ThrowError("Could not set slave fd to nonblocking.");
332 v8::Local<v8::Object> obj = Nan::New<v8::Object>();
334 Nan::New<v8::String>("master").ToLocalChecked(),
335 Nan::New<v8::Number>(master));
337 Nan::New<v8::String>("slave").ToLocalChecked(),
338 Nan::New<v8::Number>(slave));
340 Nan::New<v8::String>("pty").ToLocalChecked(),
341 Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
343 return info.GetReturnValue().Set(obj);
346 NAN_METHOD(PtyResize) {
347 Nan::HandleScope scope;
349 if (info.Length() != 3 ||
350 !info[0]->IsNumber() ||
351 !info[1]->IsNumber() ||
352 !info[2]->IsNumber()) {
353 return Nan::ThrowError("Usage: pty.resize(fd, cols, rows)");
356 int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
359 winp.ws_col = info[1]->IntegerValue(Nan::GetCurrentContext()).FromJust();
360 winp.ws_row = info[2]->IntegerValue(Nan::GetCurrentContext()).FromJust();
364 if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
366 case EBADF: return Nan::ThrowError("ioctl(2) failed, EBADF");
367 case EFAULT: return Nan::ThrowError("ioctl(2) failed, EFAULT");
368 case EINVAL: return Nan::ThrowError("ioctl(2) failed, EINVAL");
369 case ENOTTY: return Nan::ThrowError("ioctl(2) failed, ENOTTY");
371 return Nan::ThrowError("ioctl(2) failed");
374 return info.GetReturnValue().SetUndefined();
378 * Foreground Process Name
380 NAN_METHOD(PtyGetProc) {
381 Nan::HandleScope scope;
383 if (info.Length() != 2 ||
384 !info[0]->IsNumber() ||
385 !info[1]->IsString()) {
386 return Nan::ThrowError("Usage: pty.process(fd, tty)");
389 int fd = info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust();
391 Nan::Utf8String tty_(info[1]);
392 char *tty = strdup(*tty_);
393 char *name = pty_getproc(fd, tty);
397 return info.GetReturnValue().SetUndefined();
400 v8::Local<v8::String> name_ = Nan::New<v8::String>(name).ToLocalChecked();
402 return info.GetReturnValue().Set(name_);
409 // execvpe(3) is not portable.
410 // http://www.gnu.org/software/gnulib/manual/html_node/execvpe.html
412 pty_execvpe(const char *file, char **argv, char **envp) {
413 char **old = environ;
415 int ret = execvp(file, argv);
425 pty_nonblock(int fd) {
426 int flags = fcntl(fd, F_GETFL, 0);
427 if (flags == -1) return -1;
428 return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
433 * Wait for SIGCHLD to read exit status.
437 pty_waitpid(void *data) {
441 pty_baton *baton = static_cast<pty_baton*>(data);
445 if ((ret = waitpid(baton->pid, &stat_loc, 0)) != baton->pid) {
446 if (ret == -1 && errno == EINTR) {
447 return pty_waitpid(baton);
449 if (ret == -1 && errno == ECHILD) {
450 // XXX node v0.8.x seems to have this problem.
451 // waitpid is already handled elsewhere.
458 if (WIFEXITED(stat_loc)) {
459 baton->exit_code = WEXITSTATUS(stat_loc); // errno?
462 if (WIFSIGNALED(stat_loc)) {
463 baton->signal_code = WTERMSIG(stat_loc);
466 uv_async_send(&baton->async);
471 * Callback after exit status has been read.
475 pty_after_waitpid(uv_async_t *async) {
476 Nan::HandleScope scope;
477 pty_baton *baton = static_cast<pty_baton*>(async->data);
479 v8::Local<v8::Value> argv[] = {
480 Nan::New<v8::Integer>(baton->exit_code),
481 Nan::New<v8::Integer>(baton->signal_code),
484 v8::Local<v8::Function> cb = Nan::New<v8::Function>(baton->cb);
486 memset(&baton->cb, -1, sizeof(baton->cb));
487 Nan::AsyncResource resource("pty_after_waitpid");
488 resource.runInAsyncScope(Nan::GetCurrentContext()->Global(), cb, 2, argv);
490 uv_close((uv_handle_t *)async, pty_after_close);
495 * uv_close() callback - free handle data
499 pty_after_close(uv_handle_t *handle) {
500 uv_async_t *async = (uv_async_t *)handle;
501 pty_baton *baton = static_cast<pty_baton*>(async->data);
510 // Taken from: tmux (http://tmux.sourceforge.net/)
511 // Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
512 // Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
513 // Copyright (c) 2009 Todd Carson <toc@daybefore.net>
515 // Permission to use, copy, modify, and distribute this software for any
516 // purpose with or without fee is hereby granted, provided that the above
517 // copyright notice and this permission notice appear in all copies.
519 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
520 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
521 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
522 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
523 // WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
524 // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
525 // OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
527 #if defined(__linux__)
530 pty_getproc(int fd, char *tty) {
538 if ((pgrp = tcgetpgrp(fd)) == -1) {
542 r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp);
543 if (r == -1 || path == NULL) return NULL;
545 if ((f = fopen(path, "r")) == NULL) {
554 while ((ch = fgetc(f)) != EOF) {
555 if (ch == '\0') break;
556 buf = (char *)realloc(buf, len + 2);
557 if (buf == NULL) return NULL;
569 #elif defined(__APPLE__)
572 pty_getproc(int fd, char *tty) {
573 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
575 struct kinfo_proc kp;
577 if ((mib[3] = tcgetpgrp(fd)) == -1) {
582 if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
586 if (*kp.kp_proc.p_comm == '\0') {
590 return strdup(kp.kp_proc.p_comm);
596 pty_getproc(int fd, char *tty) {
603 * openpty(3) / forkpty(3)
607 pty_openpty(int *amaster,
610 const struct termios *termp,
611 const struct winsize *winp) {
615 int master = open("/dev/ptmx", O_RDWR | O_NOCTTY);
616 if (master == -1) return -1;
617 if (amaster) *amaster = master;
619 if (grantpt(master) == -1) goto err;
620 if (unlockpt(master) == -1) goto err;
622 slave_name = ptsname(master);
623 if (slave_name == NULL) goto err;
624 if (name) strcpy(name, slave_name);
626 slave = open(slave_name, O_RDWR | O_NOCTTY);
627 if (slave == -1) goto err;
628 if (aslave) *aslave = slave;
630 ioctl(slave, I_PUSH, "ptem");
631 ioctl(slave, I_PUSH, "ldterm");
632 ioctl(slave, I_PUSH, "ttcompat");
634 if (termp) tcsetattr(slave, TCSAFLUSH, termp);
635 if (winp) ioctl(slave, TIOCSWINSZ, winp);
643 return openpty(amaster, aslave, name, (termios *)termp, (winsize *)winp);
648 pty_forkpty(int *amaster,
650 const struct termios *termp,
651 const struct winsize *winp) {
655 int ret = pty_openpty(&master, &slave, name, termp, winp);
656 if (ret == -1) return -1;
657 if (amaster) *amaster = master;
671 #if defined(TIOCSCTTY)
673 if (ioctl(slave, TIOCSCTTY, NULL) == -1) {
682 if (slave > 2) close(slave);
692 return forkpty(amaster, name, (termios *)termp, (winsize *)winp);
700 NAN_MODULE_INIT(init) {
701 Nan::HandleScope scope;
702 Nan::Export(target, "fork", PtyFork);
703 Nan::Export(target, "open", PtyOpen);
704 Nan::Export(target, "resize", PtyResize);
705 Nan::Export(target, "process", PtyGetProc);
708 NODE_MODULE(pty, init)