Mercurial > hg > rlgwebd
diff ptyhelper.c @ 0:bd412f63ce0d
Put this project under version control, finally.
author | John "Elwin" Edwards <elwin@sdf.org> |
---|---|
date | Sun, 06 May 2012 08:45:40 -0700 |
parents | |
children | 9bef0941c6dd |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ptyhelper.c Sun May 06 08:45:40 2012 -0700 @@ -0,0 +1,149 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <pty.h> +#include <utmp.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/select.h> +#include <termios.h> + +int got_sighup = 0; + +void handle_HUP(int signum) { + if (signum == SIGHUP) + got_sighup = 1; + return; +} + +int main(int argc, char *argv[]) { + + int ptymaster, ptyslave; /* File descriptors */ + int child; + int status, selstatus; + int w = 80, h = 24, t; + struct sigaction sighup_act; + fd_set readset; + struct timeval select_time; + char buf[4096]; + int nread; + char *penv, *ptmp; +#if 0 + struct termios ptysettings; +#endif + struct winsize ptysize; + + if (argc == 1) { + fprintf(stderr, "No command given.\n"); + exit(1); + } + + /* Set up the signal handler. */ + sighup_act.sa_handler = &handle_HUP; + sighup_act.sa_flags = SA_RESTART; + sigaction(SIGHUP, &sighup_act, NULL); + + /* Check the environment for configuration options. */ + penv = getenv("PTYHELPER"); + if (penv != NULL) { + t = strtol(penv, &ptmp, 10); + if (t > 0 && t < 256) + h = t; + if (*ptmp != '\0') { + penv = ptmp + 1; + t = strtol(penv, &ptmp, 10); + if (t > 0 && t < 256) + w = t; + } + } + /* Set up the size. */ + ptysize.ws_row = h; + ptysize.ws_col = w; + + /* Open a pty */ + if (openpty(&ptymaster, &ptyslave, NULL, NULL, &ptysize)) { + return 1; + } +#if 0 + /* Put it into raw mode. */ + tcgetattr(ptyslave, &ptysettings); + cfmakeraw(&ptysettings); + tcsetattr(ptyslave, TCSANOW, &ptysettings); +#endif + + /* Start the child */ + /* forkpty() might be more convenient. */ + if (!(child = fork())) { + /* Child process */ + login_tty(ptyslave); + close(ptymaster); + execvp(argv[1], argv + 1); + perror("execvp() failed"); + return 1; + } + close(ptyslave); + + while (1) { + /* Now do a select() over stdin and ptymaster, and write anything that + * appears to ptymaster and stdout respectively. */ + FD_ZERO(&readset); + FD_SET(0, &readset); + FD_SET(ptymaster, &readset); + select_time.tv_sec = 1; + select_time.tv_usec = 0; + selstatus = select(ptymaster + 1, &readset, NULL, NULL, &select_time); + if (selstatus > 0) { + /* TODO make sure it all gets written if a signal interrupts write(). */ + if (FD_ISSET(0, &readset)) { + nread = read(0, buf, 4096); + if (nread > 0) { + write(ptymaster, buf, nread); + } + } + if (FD_ISSET(ptymaster, &readset)) { + nread = read(ptymaster, buf, 4096); + if (nread > 0) { + write(1, buf, nread); + } + } + } + + /* Periodically check to see if we're done. */ + /* TODO: catch SIGCHLD and only wait() if it is delivered. */ + if (waitpid(child, &status, WNOHANG)) { + break; + } + + /* If node sighup's us, pass it along. */ + if (got_sighup) { + kill(child, SIGHUP); + } + } + + /* Get any leftover output and clean up. */ + /* FIXME looping over select() is pointless if there's only one fd that + * nothing's writing to. Just loop over read() until it's empty. */ + while (1) { + FD_ZERO(&readset); + FD_SET(ptymaster, &readset); + select_time.tv_sec = 0; + select_time.tv_usec = 0; + if (select(ptymaster + 1, &readset, NULL, NULL, &select_time) > 0) { + nread = read(ptymaster, buf, 4096); + if (nread > 0) { + write(1, buf, nread); + } + else + break; + } + else + break; + } + close(ptymaster); + + /* Return the child's exit status. */ + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return 0; +}