c++ - After forking bash with forkpty and execvp, bash does not respond to SIGINT -
background
i'm writing terminal emulator text editor written in node.js (javascript). application uses c++ fork shell , communicate in backend. code forks shell written developer not seem maintaining project anymore. left me no other choice try , fix problem myself. i've gone through code line line point understand of doing.
problem
i can't figure out why bash won't send sigint signal it's sub processes. else works perfectly. can communicate shell instance in node.js , run commands. if write signal interrupt escape code ('\x03') fd of pty, sigint signal isn't sent. see ^c
in terminal display. commands ping
interpret , stop. commands cat
, python
, or java
not stop , show ^c
.
example:
user:example user$ cat ^c^c^c^c^c^c^c^c
code
first forkpty used pty instance. then, pty instance calls execvp replace process shell process. in instance, execvp launches execvp('/bin/bash', {'/bin/bash', '--login'})
.
nan_method(ptyfork) { nan::handlescope scope; if (info.length() != 9 || !info[0]->isstring() // file || !info[1]->isarray() // args || !info[2]->isarray() // env || !info[3]->isstring() // cwd || !info[4]->isnumber() // cols || !info[5]->isnumber() // rows || !info[6]->isnumber() // uid || !info[7]->isnumber() // gid || !info[8]->isfunction() // onexit ) { return nan::throwerror( "usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, onexit)"); } // file string::utf8value file(info[0]->tostring()); // args int = 0; local<array> argv_ = local<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; (; < argc; i++) { string::utf8value arg(argv_->get(nan::new<integer>(i))->tostring()); argv[i+1] = strdup(*arg); } // env = 0; local<array> env_ = local<array>::cast(info[2]); int envc = env_->length(); char **env = new char*[envc+1]; env[envc] = null; (; < envc; i++) { string::utf8value pair(env_->get(nan::new<integer>(i))->tostring()); env[i] = strdup(*pair); } // cwd string::utf8value cwd_(info[3]->tostring()); char *cwd = strdup(*cwd_); // size struct winsize winp; winp.ws_col = info[4]->integervalue(); winp.ws_row = info[5]->integervalue(); winp.ws_xpixel = 0; winp.ws_ypixel = 0; // uid / gid int uid = info[6]->integervalue(); int gid = info[7]->integervalue(); // fork pty int master = -1; char name[40]; pid_t pid = pty_forkpty(&master, name, null, &winp); if (pid) { (i = 0; < argl; i++) free(argv[i]); delete[] argv; (i = 0; < envc; i++) free(env[i]); delete[] env; free(cwd); } switch (pid) { case -1: return nan::throwerror("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 nan::throwerror("could not set master fd nonblocking."); } local<object> obj = nan::new<object>(); nan::set(obj, nan::new<string>("fd").tolocalchecked(), nan::new<number>(master)); nan::set(obj, nan::new<string>("pid").tolocalchecked(), nan::new<number>(pid)); nan::set(obj, nan::new<string>("pty").tolocalchecked(), nan::new<string>(name).tolocalchecked()); pty_baton *baton = new pty_baton(); baton->exit_code = 0; baton->signal_code = 0; baton->cb.reset(local<function>::cast(info[8])); 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(); } /** * execvpe */ // execvpe(3) not portable. // 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; } /** * nonblocking fd */ 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); } /** * pty_waitpid * wait sigchld read exit status. */ 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) { // xxx node v0.8.x seems have problem. // waitpid handled elsewhere. ; } else { assert(false); } } if (wifexited(stat_loc)) { baton->exit_code = wexitstatus(stat_loc); // errno? } if (wifsignaled(stat_loc)) { baton->signal_code = wtermsig(stat_loc); } uv_async_send(&baton->async); } /** * pty_after_waitpid * callback after exit status has been read. */ static void #if node_version_at_least(0, 11, 0) pty_after_waitpid(uv_async_t *async) { #else pty_after_waitpid(uv_async_t *async, int unhelpful) { #endif nan::handlescope scope; pty_baton *baton = static_cast<pty_baton*>(async->data); local<value> argv[] = { nan::new<integer>(baton->exit_code), nan::new<integer>(baton->signal_code), }; local<function> cb = nan::new<function>(baton->cb); baton->cb.reset(); memset(&baton->cb, -1, sizeof(baton->cb)); nan::callback(cb).call(nan::getcurrentcontext()->global(), 2, argv); uv_close((uv_handle_t *)async, pty_after_close); } /** * pty_after_close * uv_close() callback - free handle data */ 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; } static pid_t pty_forkpty(int *amaster, char *name, const struct termios *termp, const struct winsize *winp) { return forkpty(amaster, name, (termios *)termp, (winsize *)winp); }
what i've tried
i have tried using execvp call exec -l bash
instead. launches bash not solve issue.
have tried writing fd directly within c++ incase node.js. same issue of seeing ^c
not registering sigint
.
the thing work use shell such zsh. when using zsh, sigint
sent , subprocesses stop. else odd bash shells launched zsh don't experience issue. in other words, if fork zsh , call command exec bash
(or exec -l bash
), new bash instance send sigint
signal supposed to.
i know not me because application public , multiple users have reported issue. on linux, on os x. major issue, appreciated if point out going on here.
additional information
print out of stty -a
:
user:example user$ stty -a speed 9600 baud; 15 rows; 159 columns; lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl -echoprt -altwerase -noflsh -tostop -flusho pendin -nokerninfo -extproc iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -iutf8 -ignbrk brkint -inpck -ignpar -parmrk oflags: opost onlcr -oxtabs -onocr -onlret cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow -dtrflow -mdmbuf cchars: discard = ^o; dsusp = ^y; eof = ^d; eol = <undef>; eol2 = <undef>; erase = ^?; intr = ^c; kill = ^u; lnext = ^v; min = 1; quit = ^\; reprint = ^r; start = ^q; status = ^t; stop = ^s; susp = ^z; time = 0; werase = ^w;
short answer: call setsid
before exec
signals sent active process group of session associate slave pty. calling setsid
, session created associate pty, otherwise no session created, no signal emitted.
Comments
Post a Comment