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;
+}