int main(int argc, char *argv[]) { pid_t child_pid; struct termios tty_attr; struct winsize window_size; int pty_master; int retval = 0; /* for select */ fd_set readfds; fd_set writefds; fd_set exceptfds; int err_count = 0; /* for sigtimedwait() */ struct timespec timeout; char buf[16384]; if (argc == 1) { printf("usage: %s PROGRAM [ARGS]...\n", argv[0]); exit(1); } sigset_t signal_set; siginfo_t signalinfo; /* set up SIGCHLD */ sigemptyset(&signal_set); /* no signals */ sigaddset(&signal_set, SIGCHLD); /* Add sig child */ sigprocmask(SIG_BLOCK, &signal_set, NULL); /* Block the signal */ /* Set both to 0, so sigtimed wait just does a poll */ timeout.tv_sec = 0; timeout.tv_nsec = 0; if (isatty(fileno(stdin))) { /* get terminal parameters associated with stdout */ if (tcgetattr(fileno(stdout), &tty_attr) < 0) { perror("tcgetattr:"); exit(EX_OSERR); } /* end of if(tcsetattr(&tty_attr)) */ /* get window size */ if (ioctl(fileno(stdout), TIOCGWINSZ, &window_size) < 0) { perror("ioctl stdout:"); exit(1); } child_pid = forkpty(&pty_master, NULL, &tty_attr, &window_size); } /* end of if(isatty(fileno(stdin))) */ else { /* not interactive */ child_pid = forkpty(&pty_master, NULL, NULL, NULL); } if (child_pid < 0) { perror("forkpty():"); fflush(stdout); fflush(stderr); exit(EX_OSERR); } /* end of if(child_pid < 0) */ if (child_pid == 0) { /* in the child */ struct termios s_tty_attr; if (tcgetattr(fileno(stdin), &s_tty_attr)) { perror("forkpty child:"); fflush(stdout); fflush(stderr); exit(EXIT_FAILURE); } /* Turn off echo */ s_tty_attr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); /* Also turn of NL to CR?LF on output */ s_tty_attr.c_oflag &= ~(ONLCR); if (tcsetattr(fileno(stdin), TCSANOW, &s_tty_attr)) { perror("Child:"); exit(EXIT_FAILURE); } { /* There is no reason to block sigchild for the process we shall exec here */ sigset_t chld_signal_set; /* release SIGCHLD */ sigemptyset(&chld_signal_set); /* no signals */ sigaddset(&chld_signal_set, SIGCHLD); /* Add sig child */ sigprocmask(SIG_UNBLOCK, &chld_signal_set, NULL); /* Unblock the signal */ } if (execvp(argv[1], argv + 1)) { perror("Exec:"); fflush(stdout); fflush(stderr); exit(EXIT_FAILURE); } } /* end of if(child_pid == 0) */ /* * OK. Prepare to handle IO from the child. We need to transfer * everything from the child's stdout to ours. */ FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); /* * Read current file descriptor flags, preparing to do non blocking reads */ retval = fcntl(pty_master, F_GETFL); if (retval < 0) { perror("fcntl_get"); fflush(stdout); fflush(stderr); exit(EX_IOERR); } /* Set the connection to be non-blocking */ if (fcntl(pty_master, F_SETFL, retval | O_NONBLOCK) < 0) { perror("fcnt_setFlag_nonblock:"); fflush(stdout); fflush(stderr); exit(1); } FD_SET(pty_master, &readfds); FD_SET(pty_master, &writefds); FD_SET(fileno(stdin), &readfds); if (isatty(fileno(stdin))) { if (tty_semi_raw(fileno(stdin)) < 0) { perror("Error: settingraw mode:"); fflush(stdout); fflush(stderr); } /* end of if(tty_raw(fileno(stdin)) < 0) */ if (atexit(tty_atexit) < 0) { perror("Atexit setup:"); fflush(stdout); fflush(stderr); } /* end of if(atexit(tty_atexit) < 0) */ } /* ignore return from nice, but lower our priority */ int ignore __attribute__ ((unused)) = nice(19); /* while no signal, we loop around */ int done = 0; while (!done) { struct timeval interval; fd_set t_readfds; fd_set t_writefds; fd_set t_exceptfds; /* * We still use a blocked signal, and check for SIGCHLD every * loop, since waiting infinitely did not really help the load * when running, say, top. */ interval.tv_sec = 0; interval.tv_usec = 200000; /* so, check for signals every 200 milli seconds */ t_readfds = readfds; t_writefds = writefds; t_exceptfds = exceptfds; /* check for the signal */ retval = sigtimedwait(&signal_set, &signalinfo, &timeout); if (retval == SIGCHLD) { /* child terminated */ done = 1; /* in case they do not close off their file descriptors */ } else { if (retval < 0) { if (errno != EAGAIN) { perror("sigtimedwait"); fflush(stdout); fflush(stderr); exit(EX_IOERR); } else { /* No signal in set was delivered within the timeout period specified */ } } } /* end of else */ if (select (pty_master + 1, &t_readfds, &t_writefds, &t_exceptfds, &interval) < 0) { perror("Select:"); fflush(stdout); fflush(stderr); exit(EX_IOERR); } if (FD_ISSET(pty_master, &t_readfds)) { retval = read(pty_master, buf, (unsigned int)16384); if (retval < 0) { if (errno != EINTR && errno != EAGAIN) { /* Nothing left to read? */ fflush(stdout); fflush(stderr); /* fprintf(stderr, "DEBUG: %d: Nothing left to read?\n", __LINE__); */ exit(EXIT_SUCCESS); } /* end of else */ } /* end of if(retval < 0) */ else { if (retval == 0) { if (++err_count > 5) { /* child closed connection */ fflush(stdout); fflush(stderr); /*fprintf(stderr, "DEBUG: %d: child closed connection?\n", __LINE__); */ exit(EXIT_SUCCESS); } } /* end of if(retval == 0) */ else { ssize_t nleft = retval; ssize_t nwritten = 0; char *ptr = buf; while (nleft > 0) { if ((nwritten = write(fileno(stdout), ptr, (unsigned int)nleft)) <= 0) { if (errno == EINTR) { nwritten = 0; } /* end of if(errno == EINTR) */ else { perror("write"); fflush(stdout); fflush(stderr); exit(EXIT_SUCCESS); } /* end of else */ } /* end of if((nwritten = write(sockfd, ptr, nleft)) <= 0) */ nleft -= nwritten; ptr += nwritten; } /* end of while(nleft > 0) */ /* fprintf(stderr, "DEBUG: %d: wrote %d\n", __LINE__, retval); */ fflush(stdout); } /* end of else */ } /* end of else */ } if (FD_ISSET(fileno(stdin), &t_readfds)) { if (FD_ISSET(pty_master, &t_writefds)) { retval = read(fileno(stdin), buf, (unsigned int)16384); if (retval < 0) { if (errno != EINTR && errno != EAGAIN) { /* Nothing left to read? */ fflush(stdout); fflush(stderr); exit(EXIT_SUCCESS); } /* end of else */ } /* end of if(retval < 0) */ else { if (retval == 0) { if (++err_count > 5) { /* lost controlling tty */ fflush(stdout); fflush(stderr); exit(EXIT_SUCCESS); } } /* end of if(retval == 0) */ else { ssize_t nleft = retval; ssize_t nwritten = 0; char *ptr = buf; while (nleft > 0) { if ((nwritten = write(pty_master, ptr, (unsigned int)nleft)) <= 0) { if (errno == EINTR) { nwritten = 0; } /* end of if(errno == EINTR) */ else { perror ("write"); fflush (stdout); fflush (stderr); exit(EXIT_SUCCESS); } /* end of else */ } /* end of if((nwritten = write(sockfd, ptr, nleft)) <= 0) */ nleft -= nwritten; ptr += nwritten; } /* end of while(nleft > 0) */ fflush(stdout); } /* end of else */ } /* end of else */ } /* end of if(FD_ISSET(pty_master, &writefds)) */ } /* something to read on stdin */ } /* Loop */ fflush(stdout); fflush(stderr); exit(EXIT_SUCCESS); } /* end of main() */
int main(int argc, char *argv[]) { pid_t child_pid; int child_exit_status; struct termios tty_attr; struct winsize window_size; int pty_master; /* for select */ fd_set readfds; fd_set writefds; unsigned err_n_rpty = 0; unsigned err_n_wpty = 0; unsigned err_n_stdin = 0; unsigned err_n_stdout = 0; int done = 0; /* the ring buffers */ char inbuf_mem[BUFSIZE]; char outbuf_mem[BUFSIZE]; struct ring_buffer inbuf; struct ring_buffer outbuf; rb_init(&inbuf, inbuf_mem, sizeof(inbuf_mem)); rb_init(&outbuf, outbuf_mem, sizeof(outbuf_mem)); if (argc == 1) { printf("usage: %s PROGRAM [ARGS]...\n", argv[0]); exit(1); } /* We need I/O calls to fail with EINTR on SIGCHLD... */ if (signal(SIGCHLD, sigchld_handler) == SIG_ERR) { perror("signal(SIGCHLD,...)"); exit(EX_OSERR); } if (isatty(STDIN_FILENO)) { /* get terminal parameters associated with stdout */ if (tcgetattr(STDOUT_FILENO, &tty_attr) < 0) { perror("tcgetattr(stdout,...)"); exit(EX_OSERR); } /* get window size */ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window_size) < 0) { perror("ioctl(stdout,...)"); exit(1); } child_pid = forkpty(&pty_master, NULL, &tty_attr, &window_size); } else { /* not interactive */ child_pid = forkpty(&pty_master, NULL, NULL, NULL); } if (child_pid < 0) { perror("forkpty()"); exit(EX_OSERR); } if (child_pid == 0) { /* in the child */ struct termios s_tty_attr; if (tcgetattr(STDIN_FILENO, &s_tty_attr)) { perror("tcgetattr(stdin,...)"); exit(EXIT_FAILURE); } /* Turn off echo */ s_tty_attr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); /* Also turn of NL to CR?LF on output */ s_tty_attr.c_oflag &= ~(ONLCR); if (tcsetattr(STDIN_FILENO, TCSANOW, &s_tty_attr)) { perror("tcsetattr(stdin,...)"); exit(EXIT_FAILURE); } if (execvp(argv[1], argv + 1)) { perror("execvp()"); exit(EXIT_FAILURE); } } /* Non blocking mode for all file descriptors. */ setfd_nonblock(pty_master); setfd_nonblock(STDIN_FILENO); setfd_nonblock(STDOUT_FILENO); if (isatty(STDIN_FILENO)) { if (tty_semi_raw(STDIN_FILENO) < 0) { perror("tty_semi_raw(stdin)"); } if (atexit(tty_atexit) < 0) { perror("atexit()"); } } do { /* Accept events only on fds, that we can handle now. */ int do_select = 0; FD_ZERO(&readfds); FD_ZERO(&writefds); if (rb_space(&outbuf) > 0 && err_n_rpty < MAXRETR) { FD_SET(pty_master, &readfds); do_select = 1; } if (!rb_isempty(&inbuf) && err_n_wpty < MAXRETR) { FD_SET(pty_master, &writefds); do_select = 1; } if (rb_space(&inbuf) > 0 && err_n_stdin < MAXRETR) { FD_SET(STDIN_FILENO, &readfds); do_select = 1; } if (!rb_isempty(&outbuf) && err_n_stdout < MAXRETR) { FD_SET(STDOUT_FILENO, &writefds); do_select = 1; } if (!do_select) { #ifdef DEBUG fprintf(stderr, "No I/O job for us, calling waitpid()...\n"); #endif while (waitpid(child_pid, &child_exit_status, 0) < 0) { /* nothing */ } break; } errno = 0; int select_rc = select(pty_master + 1, &readfds, &writefds, NULL, NULL); if (select_rc < 0 && errno != EINTR) { perror("select()"); exit(EX_IOERR); } #ifdef DEBUG fprintf(stderr, "select() returned %d\n", select_rc); #endif if (FD_ISSET(STDOUT_FILENO, &writefds)) { #ifdef DEBUG fprintf(stderr, "stdout can be written\n"); #endif ssize_t n = rb_write(&outbuf, STDOUT_FILENO); if (n <= 0 && n != EINTR && n != EAGAIN) err_n_stdout++; #ifdef DEBUG if (n >= 0) fprintf(stderr, "%d bytes written into stdout\n", n); else perror("write(stdout,...)"); #endif } if (FD_ISSET(pty_master, &writefds)) { #ifdef DEBUG fprintf(stderr, "pty_master can be written\n"); #endif ssize_t n = rb_write(&inbuf, pty_master); if (n <= 0 && n != EINTR && n != EAGAIN) err_n_wpty++; #ifdef DEBUG if (n >= 0) fprintf(stderr, "%d bytes written into pty_master\n", n); else perror("write(pty_master,...)"); #endif } if (FD_ISSET(STDIN_FILENO, &readfds)) { #ifdef DEBUG fprintf(stderr, "stdin can be read\n"); #endif ssize_t n = rb_read(&inbuf, STDIN_FILENO); if (n <= 0 && n != EINTR && n != EAGAIN) err_n_stdin++; #ifdef DEBUG if (n >= 0) fprintf(stderr, "%d bytes read from stdin\n", n); else perror("read(stdin,...)"); #endif } if (FD_ISSET(pty_master, &readfds)) { #ifdef DEBUG fprintf(stderr, "pty_master can be read\n"); #endif ssize_t n = rb_read(&outbuf, pty_master); if (n <= 0 && n != EINTR && n != EAGAIN) err_n_rpty++; #ifdef DEBUG if (n >= 0) fprintf(stderr, "%d bytes read from pty_master\n", n); else perror("read(pty_master,...)"); #endif } if (!done && waitpid(child_pid, &child_exit_status, WNOHANG) > 0) done = 1; } while (!done || !(rb_isempty(&inbuf) || err_n_wpty >= MAXRETR) || !(rb_isempty(&outbuf) || err_n_stdout >= MAXRETR)); #ifdef DEBUG fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n", inbuf.count, outbuf.count); fprintf(stderr, "err_n_rpty=%u, err_n_wpty=%u, err_n_stdin=%u, err_n_stdout=%u\n", err_n_rpty, err_n_wpty, err_n_stdin, err_n_stdout); #endif if (WIFEXITED(child_exit_status)) exit(WEXITSTATUS(child_exit_status)); else if (WIFSIGNALED(child_exit_status)) exit(128 + WTERMSIG(child_exit_status)); exit(EXIT_FAILURE); }