-/* $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? */
/*
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;
(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;
(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);
}
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;
(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;
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);
(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));
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);
+ }
+}