#define _XOPEN_SOURCE 600 /* for posix_openpt() */ #include #include #include #include #include #include #include #include #include #include static struct { bool interactive; struct termios termios_stdin; int fd_ptym; } g; int ptym_open(char *pts_name, int pts_namesz) { int fdm; fdm = posix_openpt(O_RDWR); grantpt(fdm); unlockpt(fdm); snprintf(pts_name, pts_namesz, "%s", ptsname(fdm) ); return (fdm); } int ptys_open(char *pts_name) { return open(pts_name, O_RDWR); } void fatal_sys(char *s) { perror(s); exit(1); } pid_t pty_fork(int *ptrfdm, char *slave_name, int slave_namesz, const struct termios *slave_termios, const struct winsize *slave_winsize) { int fdm, fds; pid_t pid; char pts_name[32]; fdm = ptym_open(pts_name, sizeof(pts_name) ); snprintf(slave_name, slave_namesz, "%s", pts_name); if ((pid = fork()) < 0) { return (-1); } else if (pid == 0) { if (setsid() < 0) fatal_sys("setsid error"); /* System V acquires controlling terminal on open() */ if ((fds = ptys_open(pts_name)) < 0) fatal_sys("can't open slave pty"); close(fdm); #if defined(TIOCSCTTY) /* TIOCSCTTY is the BSD way to acquire a controlling terminal */ if (ioctl(fds, TIOCSCTTY, (char *)0) < 0) fatal_sys("TIOCSCTTY error"); #endif if (slave_termios != NULL) { if (tcsetattr(fds, TCSANOW, slave_termios) < 0) fatal_sys("tcsetattr error on slave pty"); } if (slave_winsize != NULL) { if (ioctl(fds, TIOCSWINSZ, slave_winsize) < 0) fatal_sys("TIOCSWINSZ error on slave pty"); } if (dup2(fds, STDIN_FILENO) != STDIN_FILENO) fatal_sys("dup2 error to stdin"); if (dup2(fds, STDOUT_FILENO) != STDOUT_FILENO) fatal_sys("dup2 error to stdout"); if (dup2(fds, STDERR_FILENO) != STDERR_FILENO) fatal_sys("dup2 error to stderr"); if (fds != STDIN_FILENO && fds != STDOUT_FILENO && fds != STDERR_FILENO) { close(fds); } return (0); } else { *ptrfdm = fdm; return (pid); } } int tty_raw(int fd, struct termios *save_termios) { int err; struct termios buf; if (tcgetattr(fd, &buf) < 0) return (-1); if (save_termios) { *save_termios = buf; } /* * Echo off, canonical mode off, extended input * processing off, signal chars off. */ buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* * No SIGINT on BREAK, CR-to-NL off, input parity * check off, don't strip 8th bit on input, output * flow control off. */ buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* * Clear size bits, parity checking off. */ buf.c_cflag &= ~(CSIZE | PARENB); /* * Set 8 bits/char. */ buf.c_cflag |= CS8; /* * Output processing off. */ buf.c_oflag &= ~(OPOST); /* * Case B: 1 byte at a time, no timer. */ buf.c_cc[VMIN] = 1; buf.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSAFLUSH, &buf) < 0) return (-1); /* * Verify that the changes stuck. tcsetattr can return 0 on * partial success. */ if (tcgetattr(fd, &buf) < 0) { err = errno; tcsetattr(fd, TCSAFLUSH, save_termios); errno = err; return (-1); } if ((buf.c_lflag & (ECHO | ICANON | IEXTEN | ISIG)) || (buf.c_iflag & (BRKINT | ICRNL | INPCK | ISTRIP | IXON)) || (buf.c_cflag & (CSIZE | PARENB | CS8)) != CS8 || (buf.c_oflag & OPOST) || buf.c_cc[VMIN] != 1 || buf.c_cc[VTIME] != 0) { /* * Only some of the changes were made. Restore the * original settings. */ tcsetattr(fd, TCSAFLUSH, save_termios); errno = EINVAL; return (-1); } return (0); } int tty_reset(int fd, struct termios *termio) { if (tcsetattr(fd, TCSAFLUSH, termio) < 0) return (-1); return (0); } void tty_atexit(void) { if (g.interactive) { tty_reset(STDIN_FILENO, &g.termios_stdin); } } void big_loop(int fd_ptym) { char buf[4096]; int nread; int r, status; struct timeval timeout; fd_set readfds; bool stdin_eof = false; while (true) { FD_ZERO(&readfds); if (!stdin_eof) { FD_SET(STDIN_FILENO, &readfds); } FD_SET(fd_ptym, &readfds); timeout.tv_sec = 0; timeout.tv_usec = 100 * 1000; r = select(fd_ptym + 1, &readfds, NULL, NULL, &timeout); if (r == 0) { continue; } else if (r < 0) { if (errno == EINTR) { continue; } else { fatal_sys("select error"); } } if (!stdin_eof && FD_ISSET(STDIN_FILENO, &readfds) ) { nread = read(STDIN_FILENO, buf, sizeof(buf) ); if (nread <= 0) { /* EOF from stdin */ stdin_eof = true; write(fd_ptym, "\004", 1); /* send */ } else { write(fd_ptym, buf, nread); } } if (FD_ISSET(fd_ptym, &readfds) ) { nread = read(fd_ptym, buf, sizeof(buf) ); if (nread <= 0) { /* child exited */ break; } write(STDOUT_FILENO, buf, nread); } } wait(&status); if (WIFEXITED(status) ) { exit(WEXITSTATUS(status) ); } else if (WIFSIGNALED(status) ) { exit(status + 128); } else { exit(1); } } int main(int argc, char **argv) { char slave_name[32]; pid_t pid; struct winsize size; g.interactive = isatty(STDIN_FILENO); if (g.interactive) { if (tcgetattr(STDIN_FILENO, &g.termios_stdin) < 0) fatal_sys("tcgetattr error on stdin"); if (ioctl(STDIN_FILENO, TIOCGWINSZ, (char *) &size) < 0) fatal_sys("TIOCGWINSZ error"); pid = pty_fork(&g.fd_ptym, slave_name, sizeof(slave_name), &g.termios_stdin, &size); } else { pid = pty_fork(&g.fd_ptym, slave_name, sizeof(slave_name), NULL, NULL); } if (pid < 0) { fatal_sys("fork error"); } else if (pid == 0) { /* child */ if (execvp(argv[1], argv + 1) < 0) fatal_sys("execvp"); } if (g.interactive) { /* user's tty to raw mode */ if (tty_raw(STDIN_FILENO, NULL) < 0) fatal_sys("tty_raw error"); /* reset user's tty on exit */ if (atexit(tty_atexit) < 0) fatal_sys("atexit error"); } big_loop(g.fd_ptym); return 0; }