I want the pty to be seen.
[apps/madtty.git] / madtty / madtty.c
index 80db4c6..8fbc469 100644 (file)
     Copyright © 2006 Pierre Habouzit
  */
 
+#include <errno.h>
 #include <stdlib.h>
-#ifdef USE_PTY
-#include <pty.h>
-#endif
+#include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/ioctl.h>
 
 #include "madtty.h"
 #include "roteprivate.h"
@@ -36,10 +36,12 @@ RoteTerm *rote_vt_create(int rows, int cols)
     RoteTerm *rt;
     int i, j;
 
-    if (rows <= 0 || cols <= 0) return NULL;
+    if (rows <= 0 || cols <= 0)
+        return NULL;
 
-    if (! (rt = (RoteTerm*) malloc(sizeof(RoteTerm))) ) return NULL;
-    memset(rt, 0, sizeof(RoteTerm));
+    rt = (RoteTerm*)calloc(sizeof(RoteTerm), 1);
+    if (!rt)
+        return NULL;
 
     /* record dimensions */
     rt->rows = rows;
@@ -56,23 +58,22 @@ RoteTerm *rote_vt_create(int rows, int cols)
 
         /* fill row with spaces */
         for (j = 0; j < rt->cols; j++) {
-            rt->cells[i][j].ch = 0x20;    /* a space */
+            rt->cells[i][j].ch   = 0x20;  /* a space */
             rt->cells[i][j].attr = 0x70;  /* white text, black background */
         }
     }
 
     /* allocate dirtiness array */
-    rt->line_dirty = (bool*) malloc(sizeof(bool) * rt->rows);
+    rt->line_dirty = (bool*)calloc(sizeof(bool), rt->rows);
 
     /* initialization of other public fields */
     rt->crow = rt->ccol = 0;
     rt->curattr = 0x70;  /* white text over black background */
 
     /* allocate private data */
-    rt->pd = (RoteTermPrivate*) malloc(sizeof(RoteTermPrivate));
-    memset(rt->pd, 0, sizeof(RoteTermPrivate));
+    rt->pd = (RoteTermPrivate*)calloc(sizeof(RoteTermPrivate), 1);
 
-    rt->pd->pty = -1;  /* no pty for now */
+    rt->pty = -1;  /* no pty for now */
 
     /* initial scrolling area is the whole window */
     rt->pd->scrolltop = 0;
@@ -84,11 +85,14 @@ RoteTerm *rote_vt_create(int rows, int cols)
 void rote_vt_destroy(RoteTerm *rt)
 {
     int i;
-    if (!rt) return;
+    if (!rt)
+        return;
 
     free(rt->pd);
     free(rt->line_dirty);
-    for (i = 0; i < rt->rows; i++) free(rt->cells[i]);
+    for (i = 0; i < rt->rows; i++) {
+        free(rt->cells[i]);
+    }
     free(rt->cells);
     free(rt);
 }
@@ -130,56 +134,112 @@ void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol,
 
 #endif
 
-#ifdef USE_PTY
+/******************************************************/
+
+#define PTYCHAR1 "pqrstuvwxyz"
+#define PTYCHAR2 "0123456789abcdef"
+
+/* allocate one pty/tty pair */
+static int get_pty(char *tty_str)
+{
+   int fd;
+   char ptydev[] = "/dev/pty??";
+   char ttydev[] = "/dev/tty??";
+   int len = strlen(ttydev);
+   const char *c1, *c2;
+
+   for (c1 = PTYCHAR1; *c1; c1++) {
+       ptydev[len-2] = ttydev[len-2] = *c1;
+       for (c2 = PTYCHAR2; *c2; c2++) {
+           ptydev[len-1] = ttydev[len-1] = *c2;
+           if ((fd = open(ptydev, O_RDWR)) >= 0) {
+               if (access(ttydev, R_OK|W_OK) == 0) {
+                   strcpy(tty_str, ttydev);
+                   return fd;
+               }
+               close(fd);
+           }
+       }
+   }
+   return -1;
+}
 
-pid_t rote_vt_forkpty(RoteTerm *rt, const char *command)
+static int
+run_process(const char *path, const char **argv, int *fd_ptr, int *pid_ptr)
 {
+    int pty_fd, pid, i, nb_fds;
+    char tty_name[32];
     struct winsize ws;
-    pid_t childpid;
 
-    ws.ws_row = rt->rows;
-    ws.ws_col = rt->cols;
-    ws.ws_xpixel = ws.ws_ypixel = 0;
+    pty_fd = get_pty(tty_name);
+    if (pty_fd < 0)
+        return -1;
+
+    fcntl(pty_fd, F_SETFL, O_NONBLOCK);
 
-    childpid = forkpty(&rt->pd->pty, NULL, NULL, &ws);
-    if (childpid < 0) return -1;
+    /* set dummy screen size */
+    ws.ws_col = 80;
+    ws.ws_row = 25;
+    ws.ws_xpixel = ws.ws_col;
+    ws.ws_ypixel = ws.ws_row;
+    ioctl(pty_fd, TIOCSWINSZ, &ws);
 
-    if (childpid == 0) {
-        /* we are the child, running under the slave side of the pty. */
+    pid = fork();
+    if (pid < 0)
+        return -1;
+
+    if (pid == 0) {
+        /* child process */
+        nb_fds = getdtablesize();
+        for (i = 0; i < nb_fds; i++)
+            close(i);
+        /* open pseudo tty for standard i/o */
+        open(tty_name, O_RDWR);
+        dup(0);
+        dup(0);
+
+        setsid();
 
-        /* Cajole application into using linux-console-compatible escape
-         * sequences (which is what we are prepared to interpret) */
         setenv("TERM", "linux", 1);
+        execv(path, (char *const*)argv);
+        fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
+        exit(1);
+    }
+    /* return file info */
+    *fd_ptr = pty_fd;
+    *pid_ptr = pid;
+    return 0;
+}
+
+pid_t rote_vt_forkpty(RoteTerm *rt, const char *path, const char *argv[])
+{
+    struct winsize ws;
 
-        /* Now we will exec /bin/sh -c command. */
-        execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+    ws.ws_row = rt->rows;
+    ws.ws_col = rt->cols;
+    ws.ws_xpixel = ws.ws_ypixel = 0;
 
-        fprintf(stderr, "\nexecl() failed.\nCommand: '%s'\n", command);
-        exit(127);  /* error exec'ing */
+    if (run_process(path, argv, &rt->pty, &rt->childpid)) {
+        return -1;
     }
 
-    /* if we got here we are the parent process */
-    rt->childpid = childpid;
-    return childpid;
+    ioctl(rt->pty, TIOCSWINSZ, &ws);
+    return rt->childpid;
 }
 
 void rote_vt_forsake_child(RoteTerm *rt)
 {
-    if (rt->pd->pty >= 0) close(rt->pd->pty);
-    rt->pd->pty = -1;
+    if (rt->pty >= 0)
+        close(rt->pty);
+    rt->pty  = -1;
     rt->childpid = 0;
 }
 
-#endif
-
 void rote_vt_update(RoteTerm *rt)
 {
-    fd_set ifs;
-    struct timeval tvzero;
     char buf[512];
-    int bytesread;
+    int nbread;
     int n = ROTE_VT_UPDATE_ITERATIONS;
-    if (rt->pd->pty < 0) return;  /* nothing to pump */
 
     while (n--) { /* iterate at most ROVE_VT_UPDATE_ITERATIONS times.
                    * As Phil Endecott pointed out, if we don't restrict this,
@@ -189,26 +249,28 @@ void rote_vt_update(RoteTerm *rt)
                    * calling rote_vt_update often, as the documentation
                    * recommends :-) */
 
-        /* check if pty has something to say */
-        FD_ZERO(&ifs); FD_SET(rt->pd->pty, &ifs);
-        tvzero.tv_sec = 0; tvzero.tv_usec = 0;
-
-        if (select(rt->pd->pty + 1, &ifs, NULL, NULL, &tvzero) <= 0)
-            return; /* nothing to read, or select() failed */
-
-        /* read what we can. This is guaranteed not to block, since
-         * select() told us there was something to read. */
-        bytesread = read(rt->pd->pty, buf, 512);
-        if (bytesread <= 0) return;   
+        nbread = rote_vt_read(rt, buf, sizeof(buf));
+        if (nbread <= 0)
+            return;
 
         /* inject the data into the terminal */
-        rote_vt_inject(rt, buf, bytesread);
+        rote_vt_inject(rt, buf, nbread);
+    }
+}
+
+int rote_vt_read(RoteTerm *rt, char *buf, int buflen)
+{
+    if (rt->pty < 0) {
+        errno = EINVAL;
+        return -1;
     }
+
+    return read(rt->pty, buf, buflen);
 }
 
 void rote_vt_write(RoteTerm *rt, const char *data, int len)
 {
-    if (rt->pd->pty < 0) {
+    if (rt->pty < 0) {
         /* no pty, so just inject the data plain and simple */
         rote_vt_inject(rt, data, len);
         return;
@@ -217,7 +279,7 @@ void rote_vt_write(RoteTerm *rt, const char *data, int len)
     /* write data to pty. Keep calling write() until we have written
      * everything. */
     while (len > 0) {
-        int byteswritten = write(rt->pd->pty, data, len);
+        int byteswritten = write(rt->pty, data, len);
         if (byteswritten < 0) {
             /* very ugly way to inform the error. Improvements welcome! */
             static char errormsg[] = "\n(ROTE: pty write() error)\n";
@@ -230,17 +292,12 @@ void rote_vt_write(RoteTerm *rt, const char *data, int len)
     }
 }
 
-void rote_vt_install_handler(RoteTerm *rt, rote_es_handler_t handler)
-{
-    rt->pd->handler = handler;
-}
-
 void *rote_vt_take_snapshot(RoteTerm *rt)
 {
-    int i;
-    int bytes_per_row = sizeof(RoteCell) * rt->cols;
+    const int bytes_per_row = sizeof(RoteCell) * rt->cols;
     void *buf = malloc(bytes_per_row * rt->rows);
     void *ptr = buf;
+    int i;
 
     for (i = 0; i < rt->rows; i++, ptr += bytes_per_row)
         memcpy(ptr, rt->cells[i], bytes_per_row);
@@ -250,8 +307,9 @@ void *rote_vt_take_snapshot(RoteTerm *rt)
 
 void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf)
 {
+    const int bytes_per_row = sizeof(RoteCell) * rt->cols;
+
     int i;
-    int bytes_per_row = sizeof(RoteCell) * rt->cols;
 
     for (i = 0; i < rt->rows; i++, snapbuf += bytes_per_row) {
         rt->line_dirty[i] = true;
@@ -259,31 +317,10 @@ void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf)
     }
 }
 
-int rote_vt_get_pty_fd(RoteTerm *rt)
-{
-    return rt->pd->pty;
-}
-
 static const char *keytable[KEY_MAX+1];
-static int initialized = 0;
-
-static void keytable_init();
-
-void rote_vt_keypress(RoteTerm *rt, int keycode)
-{
-    char c = (char) keycode;
-
-    if (!initialized) keytable_init();
-
-    if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode])
-        rote_vt_write(rt, keytable[keycode], strlen(keytable[keycode]));
-    else
-        rote_vt_write(rt, &c, 1); /* not special, just write it */
-}
 
 static void keytable_init()
 {
-    initialized = 1;
     memset(keytable, 0, KEY_MAX+1 * sizeof(const char*));
 
     keytable['\n']          = "\r";
@@ -311,4 +348,17 @@ static void keytable_init()
     keytable[KEY_F(10)]     = "\e[21~";
 }
 
+void rote_vt_keypress(RoteTerm *rt, int keycode)
+{
+    char c = (char) keycode;
+
+    if (keytable['\n'] == NULL)
+        keytable_init();
+
+    if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
+        rote_vt_write(rt, keytable[keycode], strlen(keytable[keycode]));
+    } else {
+        rote_vt_write(rt, &c, 1); /* not special, just write it */
+    }
+}