Replace usage of TIOCSTI in mail while editing headers using a more common
authoranton <anton@openbsd.org>
Wed, 28 Jun 2017 14:58:23 +0000 (14:58 +0000)
committeranton <anton@openbsd.org>
Wed, 28 Jun 2017 14:58:23 +0000 (14:58 +0000)
IO-loop where ICANON is disabled and a single char of input is read at a time.
This requires the line editing capabilities provided when ICANON is enabled to
be implemented.

ok deraadt@

usr.bin/mail/tty.c

index 1942814..4f7057b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: tty.c,v 1.20 2014/01/17 18:42:30 okan Exp $   */
+/*     $OpenBSD: tty.c,v 1.21 2017/06/28 14:58:23 anton Exp $  */
 /*     $NetBSD: tty.c,v 1.7 1997/07/09 05:25:46 mikel Exp $    */
 
 /*
 #include "extern.h"
 #include <sys/ioctl.h>
 #include <errno.h>
+#include <fcntl.h>
 
-static cc_t            c_erase;        /* Current erase char */
-static cc_t            c_kill;         /* Current kill char */
-#ifndef TIOCSTI
-static int             ttyset;         /* We must now do erase/kill */
-#endif
+#define        TABWIDTH        8
+
+struct tty {
+       int      fdin;
+       int      fdout;
+       int      flags;
+#define        TTY_ALTWERASE   0x1
+#define        TTY_ERR         0x2
+       cc_t    *keys;
+       char    *buf;
+       size_t   size;
+       size_t   len;
+       size_t   cursor;
+};
+
+static void    tty_flush(struct tty *);
+static int     tty_getc(struct tty *);
+static int     tty_insert(struct tty *, int, int);
+static void    tty_putc(struct tty *, int);
+static void    tty_reset(struct tty *);
+static void    tty_visc(struct tty *, int);
+
+static struct tty              tty;
 static volatile sig_atomic_t   ttysignal;      /* Interrupted by a signal? */
 
 /*
@@ -54,14 +73,10 @@ static      volatile sig_atomic_t   ttysignal;      /* Interrupted by a signal? */
 int
 grabh(struct header *hp, int gflags)
 {
-       struct termios ttybuf;
-#ifndef TIOCSTI
-       struct sigaction savequit;
-#else
-# ifdef        TIOCEXT
+       struct termios newtio, oldtio;
+#ifdef TIOCEXT
        int extproc;
        int flag;
-# endif /* TIOCEXT */
 #endif
        struct sigaction savetstp;
        struct sigaction savettou;
@@ -77,67 +92,53 @@ grabh(struct header *hp, int gflags)
        (void)sigaction(SIGTTOU, &act, &savettou);
        (void)sigaction(SIGTTIN, &act, &savettin);
        error = 1;
-#ifndef TIOCSTI
-       ttyset = 0;
-#endif
-       if (tcgetattr(fileno(stdin), &ttybuf) < 0) {
+       memset(&tty, 0, sizeof(tty));
+       tty.fdin = fileno(stdin);
+       tty.fdout = fileno(stdout);
+       if (tcgetattr(tty.fdin, &oldtio) < 0) {
                warn("tcgetattr");
                return(-1);
        }
-       c_erase = ttybuf.c_cc[VERASE];
-       c_kill = ttybuf.c_cc[VKILL];
-#ifndef TIOCSTI
-       ttybuf.c_cc[VERASE] = 0;
-       ttybuf.c_cc[VKILL] = 0;
-       act.sa_handler = SIG_IGN;
-       if (sigaction(SIGQUIT, &act, &savequit) == 0 &&
-           savequit.sa_handler == SIG_DFL)
-               (void)sigaction(SIGQUIT, &savequit, NULL);
-#else
-# ifdef        TIOCEXT
-       extproc = ((ttybuf.c_lflag & EXTPROC) ? 1 : 0);
+       tty.keys = oldtio.c_cc;
+       if (oldtio.c_lflag & ALTWERASE)
+               tty.flags |= TTY_ALTWERASE;
+
+       newtio = oldtio;
+       newtio.c_lflag &= ~(ECHO | ICANON);
+       newtio.c_cc[VMIN] = 1;
+       newtio.c_cc[VTIME] = 0;
+       if (tcsetattr(tty.fdin, TCSADRAIN, &newtio) < 0) {
+               warn("tcsetattr");
+               return(-1);
+       }
+
+#ifdef TIOCEXT
+       extproc = ((oldtio.c_lflag & EXTPROC) ? 1 : 0);
        if (extproc) {
                flag = 0;
                if (ioctl(fileno(stdin), TIOCEXT, &flag) < 0)
                        warn("TIOCEXT: off");
        }
-# endif /* TIOCEXT */
 #endif
        if (gflags & GTO) {
-#ifndef TIOCSTI
-               if (!ttyset && hp->h_to != NULL)
-                       ttyset++, tcsetattr(fileno(stdin), TCSADRAIN, &ttybuf);
-#endif
                s = readtty("To: ", detract(hp->h_to, 0));
                if (s == NULL)
                        goto out;
                hp->h_to = extract(s, GTO);
        }
        if (gflags & GSUBJECT) {
-#ifndef TIOCSTI
-               if (!ttyset && hp->h_subject != NULL)
-                       ttyset++, tcsetattr(fileno(stdin), TCSADRAIN, &ttybuf);
-#endif
                s = readtty("Subject: ", hp->h_subject);
                if (s == NULL)
                        goto out;
                hp->h_subject = s;
        }
        if (gflags & GCC) {
-#ifndef TIOCSTI
-               if (!ttyset && hp->h_cc != NULL)
-                       ttyset++, tcsetattr(fileno(stdin), TCSADRAIN, &ttybuf);
-#endif
                s = readtty("Cc: ", detract(hp->h_cc, 0));
                if (s == NULL)
                        goto out;
                hp->h_cc = extract(s, GCC);
        }
        if (gflags & GBCC) {
-#ifndef TIOCSTI
-               if (!ttyset && hp->h_bcc != NULL)
-                       ttyset++, tcsetattr(fileno(stdin), TCSADRAIN, &ttybuf);
-#endif
                s = readtty("Bcc: ", detract(hp->h_bcc, 0));
                if (s == NULL)
                        goto out;
@@ -148,21 +149,15 @@ out:
        (void)sigaction(SIGTSTP, &savetstp, NULL);
        (void)sigaction(SIGTTOU, &savettou, NULL);
        (void)sigaction(SIGTTIN, &savettin, NULL);
-#ifndef TIOCSTI
-       ttybuf.c_cc[VERASE] = c_erase;
-       ttybuf.c_cc[VKILL] = c_kill;
-       if (ttyset)
-               tcsetattr(fileno(stdin), TCSADRAIN, &ttybuf);
-       (void)sigaction(SIGQUIT, &savequit, NULL);
-#else
-# ifdef        TIOCEXT
+#ifdef TIOCEXT
        if (extproc) {
                flag = 1;
                if (ioctl(fileno(stdin), TIOCEXT, &flag) < 0)
                        warn("TIOCEXT: on");
        }
-# endif /* TIOCEXT */
 #endif
+       if (tcsetattr(tty.fdin, TCSADRAIN, &oldtio) < 0)
+               warn("tcsetattr");
        return(error);
 }
 
@@ -176,38 +171,30 @@ char *
 readtty(char *pr, char *src)
 {
        struct sigaction act, saveint;
-       char ch, canonb[BUFSIZ];
-       char *cp, *cp2;
+       unsigned char canonb[BUFSIZ];
+       char *cp;
        sigset_t oset;
-       int c;
+       int c, done;
+
+       memset(canonb, 0, sizeof(canonb));
+       tty.buf = canonb;
+       tty.size = sizeof(canonb) - 1;
+
+       for (cp = pr; *cp != '\0'; cp++)
+               tty_insert(&tty, *cp, 1);
+       tty_flush(&tty);
+       tty_reset(&tty);
 
-       fputs(pr, stdout);
-       fflush(stdout);
        if (src != NULL && strlen(src) > sizeof(canonb) - 2) {
                puts("too long to edit");
                return(src);
        }
-#ifndef TIOCSTI
-       if (src != NULL)
-               cp = copy(src, canonb); /* safe, bounds checked above */
-       else
-               cp = copy("", canonb);
-       fputs(canonb, stdout);
-       fflush(stdout);
-#else
-       cp = src == NULL ? "" : src;
-       while ((c = (unsigned char)*cp++) != '\0') {
-               if ((c_erase != _POSIX_VDISABLE && c == c_erase) ||
-                   (c_kill != _POSIX_VDISABLE && c == c_kill)) {
-                       ch = '\\';
-                       ioctl(0, TIOCSTI, &ch);
-               }
-               ch = c;
-               ioctl(0, TIOCSTI, &ch);
+       if (src != NULL) {
+               for (cp = src; *cp != '\0'; cp++)
+                       tty_insert(&tty, *cp, 1);
+               tty_flush(&tty);
        }
-       cp = canonb;
-       *cp = 0;
-#endif
+
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;               /* Note: will not restart syscalls */
        act.sa_handler = ttyint;
@@ -217,15 +204,11 @@ readtty(char *pr, char *src)
        (void)sigaction(SIGTTOU, &act, NULL);
        (void)sigaction(SIGTTIN, &act, NULL);
        (void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
-       clearerr(stdin);
-       memset(cp, 0, canonb + sizeof(canonb) - cp);
-       for (cp2 = cp; cp2 < canonb + sizeof(canonb) - 1; ) {
-               c = getc(stdin);
+       for (;;) {
+               c = tty_getc(&tty);
                switch (ttysignal) {
                        case SIGINT:
-                               ttysignal = 0;
-                               cp2 = NULL;
-                               c = EOF;
+                               tty_visc(&tty, '\003'); /* output ^C */
                                /* FALLTHROUGH */
                        case 0:
                                break;
@@ -233,9 +216,17 @@ readtty(char *pr, char *src)
                                ttysignal = 0;
                                goto redo;
                }
-               if (c == EOF || c == '\n')
+               if (c == 0) {
+                       done = 1;
+               } else if (c == '\n') {
+                       tty_putc(&tty, c);
+                       done = 1;
+               } else {
+                       done = tty_insert(&tty, c, 0);
+                       tty_flush(&tty);
+               }
+               if (done)
                        break;
-               *cp2++ = c;
        }
        act.sa_handler = SIG_DFL;
        sigemptyset(&act.sa_mask);
@@ -245,48 +236,17 @@ readtty(char *pr, char *src)
        (void)sigaction(SIGTTOU, &act, NULL);
        (void)sigaction(SIGTTIN, &act, NULL);
        (void)sigaction(SIGINT, &saveint, NULL);
-       if (cp2 == NULL)
-               return(NULL);                   /* user hit ^C */
-       *cp2 = '\0';
-       if (c == EOF && ferror(stdin)) {
+       if (tty.flags & TTY_ERR) {
+               if (ttysignal == SIGINT) {
+                       ttysignal = 0;
+                       return(NULL);   /* user hit ^C */
+               }
+
 redo:
                cp = strlen(canonb) > 0 ? canonb : NULL;
-               clearerr(stdin);
                /* XXX - make iterative, not recursive */
                return(readtty(pr, cp));
        }
-#ifndef TIOCSTI
-       if (cp == NULL || *cp == '\0')
-               return(src);
-       cp2 = cp;
-       if (!ttyset)
-               return(strlen(canonb) > 0 ? savestr(canonb) : NULL);
-       while (*cp != '\0') {
-               c = (unsigned char)*cp++;
-               if (c_erase != _POSIX_VDISABLE && c == c_erase) {
-                       if (cp2 == canonb)
-                               continue;
-                       if (cp2[-1] == '\\') {
-                               cp2[-1] = c;
-                               continue;
-                       }
-                       cp2--;
-                       continue;
-               }
-               if (c_kill != _POSIX_VDISABLE && c == c_kill) {
-                       if (cp2 == canonb)
-                               continue;
-                       if (cp2[-1] == '\\') {
-                               cp2[-1] = c;
-                               continue;
-                       }
-                       cp2 = canonb;
-                       continue;
-               }
-               *cp2++ = c;
-       }
-       *cp2 = '\0';
-#endif
        if (equal("", canonb))
                return("");
        return(savestr(canonb));
@@ -328,3 +288,121 @@ ttyint(int s)
 
        ttysignal = s;
 }
+
+static void
+tty_flush(struct tty *t)
+{
+       size_t  i, len;
+       int     c;
+
+       if (t->cursor < t->len) {
+               for (; t->cursor < t->len; t->cursor++)
+                       tty_visc(t, t->buf[t->cursor]);
+       } else if (t->cursor > t->len) {
+               len = t->cursor - t->len;
+               for (i = len; i > 0; i--) {
+                       c = t->buf[--t->cursor];
+                       if (c == '\t')
+                               len += TABWIDTH - 1;
+                       else if (iscntrl(c))
+                               len++;  /* account for leading ^ */
+               }
+               for (i = 0; i < len; i++)
+                       tty_putc(t, '\b');
+               for (i = 0; i < len; i++)
+                       tty_putc(t, ' ');
+               for (i = 0; i < len; i++)
+                       tty_putc(t, '\b');
+               t->cursor = t->len;
+       }
+
+       t->buf[t->len] = '\0';
+}
+
+static int
+tty_getc(struct tty *t)
+{
+       ssize_t         n;
+       unsigned char   c;
+
+       n = read(t->fdin, &c, 1);
+       switch (n) {
+       case -1:
+               t->flags |= TTY_ERR;
+               /* FALLTHROUGH */
+       case 0:
+               return 0;
+       default:
+               return c & 0x7f;
+       }
+}
+
+static int
+tty_insert(struct tty *t, int c, int nocntrl)
+{
+       const unsigned char     *ws = " \t";
+
+       if (CCEQ(t->keys[VERASE], c)) {
+               if (nocntrl)
+                       return 0;
+               if (t->len > 0)
+                       t->len--;
+       } else if (CCEQ(t->keys[VWERASE], c)) {
+               if (nocntrl)
+                       return 0;
+               for (; t->len > 0; t->len--)
+                       if (strchr(ws, t->buf[t->len - 1]) == NULL
+                           && ((t->flags & TTY_ALTWERASE) == 0
+                                   || isalpha(t->buf[t->len - 1])))
+                               break;
+               for (; t->len > 0; t->len--)
+                       if (strchr(ws, t->buf[t->len - 1]) != NULL
+                           || ((t->flags & TTY_ALTWERASE)
+                                   && !isalpha(t->buf[t->len - 1])))
+                               break;
+       } else if (CCEQ(t->keys[VKILL], c)) {
+               if (nocntrl)
+                       return 0;
+               t->len = 0;
+       } else {
+               if (t->len == t->size)
+                       return 1;
+               t->buf[t->len++] = c;
+       }
+
+       return 0;
+}
+
+static void
+tty_putc(struct tty *t, int c)
+{
+       unsigned char   cc = c;
+
+       write(t->fdout, &cc, 1);
+}
+
+static void
+tty_reset(struct tty *t)
+{
+       memset(t->buf, 0, t->len);
+       t->len = t->cursor = 0;
+}
+
+static void
+tty_visc(struct tty *t, int c)
+{
+       int     i;
+
+       if (c == '\t') {
+               for (i = 0; i < TABWIDTH; i++)
+                       tty_putc(t, ' ');
+       } else if (iscntrl(c)) {
+               tty_putc(t, '^');
+               if (c == 0x7F)
+                       tty_putc(t, '?');
+               else
+                       tty_putc(t, (c | 0x40));
+       } else {
+               tty_putc(t, c);
+       }
+}