1 /* $OpenBSD: tty.c,v 1.24 2023/11/03 19:32:28 anton Exp $ */
2 /* $NetBSD: tty.c,v 1.7 1997/07/09 05:25:46 mikel Exp $ */
3
4 /*
5 * Copyright (c) 1980, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of the University nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 */
32
33 /*
34 * Mail -- a mail program
35 *
36 * Generally useful tty stuff.
37 */
38
39 #include "rcv.h"
40 #include "extern.h"
41 #include <sys/ioctl.h>
42 #include <errno.h>
43 #include <fcntl.h>
44
45 #define TABWIDTH 8
46
47 struct tty {
48 int fdin;
49 int fdout;
50 int flags;
51 #define TTY_ALTWERASE 0x1
52 #define TTY_ERR 0x2
53 cc_t *keys;
54 char *buf;
55 size_t size;
56 size_t len;
57 size_t cursor;
58 };
59
60 static void tty_flush(struct tty *);
61 static int tty_getc(struct tty *);
62 static int tty_insert(struct tty *, int, int);
63 static void tty_putc(struct tty *, int);
64 static void tty_reset(struct tty *);
65 static void tty_visc(struct tty *, int);
66
67 static struct tty tty;
68 static volatile sig_atomic_t ttysignal; /* Interrupted by a signal? */
69
70 /*
71 * Read all relevant header fields.
72 */
73 int
grabh(struct header * hp,int gflags)74 grabh(struct header *hp, int gflags)
75 {
76 struct termios newtio, oldtio;
77 #ifdef TIOCEXT
78 int extproc;
79 int flag;
80 #endif
81 struct sigaction savetstp;
82 struct sigaction savettou;
83 struct sigaction savettin;
84 struct sigaction act;
85 char *s;
86 int error;
87
88 sigemptyset(&act.sa_mask);
89 act.sa_flags = SA_RESTART;
90 act.sa_handler = SIG_DFL;
91 (void)sigaction(SIGTSTP, &act, &savetstp);
92 (void)sigaction(SIGTTOU, &act, &savettou);
93 (void)sigaction(SIGTTIN, &act, &savettin);
94 error = 1;
95 memset(&tty, 0, sizeof(tty));
96 tty.fdin = fileno(stdin);
97 tty.fdout = fileno(stdout);
98 if (tcgetattr(tty.fdin, &oldtio) == -1) {
99 warn("tcgetattr");
100 return(-1);
101 }
102 tty.keys = oldtio.c_cc;
103 if (oldtio.c_lflag & ALTWERASE)
104 tty.flags |= TTY_ALTWERASE;
105
106 newtio = oldtio;
107 newtio.c_lflag &= ~(ECHO | ICANON);
108 newtio.c_cc[VMIN] = 1;
109 newtio.c_cc[VTIME] = 0;
110 if (tcsetattr(tty.fdin, TCSADRAIN, &newtio) == -1) {
111 warn("tcsetattr");
112 return(-1);
113 }
114
115 #ifdef TIOCEXT
116 extproc = ((oldtio.c_lflag & EXTPROC) ? 1 : 0);
117 if (extproc) {
118 flag = 0;
119 if (ioctl(fileno(stdin), TIOCEXT, &flag) == -1)
120 warn("TIOCEXT: off");
121 }
122 #endif
123 if (gflags & GTO) {
124 s = readtty("To: ", detract(hp->h_to, 0));
125 if (s == NULL)
126 goto out;
127 hp->h_to = extract(s, GTO);
128 }
129 if (gflags & GSUBJECT) {
130 s = readtty("Subject: ", hp->h_subject);
131 if (s == NULL)
132 goto out;
133 hp->h_subject = s;
134 }
135 if (gflags & GCC) {
136 s = readtty("Cc: ", detract(hp->h_cc, 0));
137 if (s == NULL)
138 goto out;
139 hp->h_cc = extract(s, GCC);
140 }
141 if (gflags & GBCC) {
142 s = readtty("Bcc: ", detract(hp->h_bcc, 0));
143 if (s == NULL)
144 goto out;
145 hp->h_bcc = extract(s, GBCC);
146 }
147 error = 0;
148 out:
149 (void)sigaction(SIGTSTP, &savetstp, NULL);
150 (void)sigaction(SIGTTOU, &savettou, NULL);
151 (void)sigaction(SIGTTIN, &savettin, NULL);
152 #ifdef TIOCEXT
153 if (extproc) {
154 flag = 1;
155 if (ioctl(fileno(stdin), TIOCEXT, &flag) == -1)
156 warn("TIOCEXT: on");
157 }
158 #endif
159 if (tcsetattr(tty.fdin, TCSADRAIN, &oldtio) == -1)
160 warn("tcsetattr");
161 return(error);
162 }
163
164 /*
165 * Read up a header from standard input.
166 * The source string has the preliminary contents to
167 * be read.
168 *
169 */
170 char *
readtty(char * pr,char * src)171 readtty(char *pr, char *src)
172 {
173 struct sigaction act, saveint;
174 unsigned char canonb[BUFSIZ];
175 char *cp;
176 sigset_t oset;
177 int c, done;
178
179 memset(canonb, 0, sizeof(canonb));
180 tty.buf = canonb;
181 tty.size = sizeof(canonb) - 1;
182
183 for (cp = pr; *cp != '\0'; cp++)
184 tty_insert(&tty, *cp, 1);
185 tty_flush(&tty);
186 tty_reset(&tty);
187
188 if (src != NULL && strlen(src) > sizeof(canonb) - 2) {
189 puts("too long to edit");
190 return(src);
191 }
192 if (src != NULL) {
193 for (cp = src; *cp != '\0'; cp++)
194 tty_insert(&tty, *cp, 1);
195 tty_flush(&tty);
196 }
197
198 sigemptyset(&act.sa_mask);
199 act.sa_flags = 0; /* Note: will not restart syscalls */
200 act.sa_handler = ttyint;
201 (void)sigaction(SIGINT, &act, &saveint);
202 act.sa_handler = ttystop;
203 (void)sigaction(SIGTSTP, &act, NULL);
204 (void)sigaction(SIGTTOU, &act, NULL);
205 (void)sigaction(SIGTTIN, &act, NULL);
206 (void)sigprocmask(SIG_UNBLOCK, &intset, &oset);
207 for (;;) {
208 c = tty_getc(&tty);
209 switch (ttysignal) {
210 case SIGINT:
211 tty_visc(&tty, '\003'); /* output ^C */
212 /* FALLTHROUGH */
213 case 0:
214 break;
215 default:
216 ttysignal = 0;
217 goto redo;
218 }
219 if (c == 0) {
220 done = 1;
221 } else if (c == '\n') {
222 tty_putc(&tty, c);
223 done = 1;
224 } else {
225 done = tty_insert(&tty, c, 0);
226 tty_flush(&tty);
227 }
228 if (done)
229 break;
230 }
231 act.sa_handler = SIG_DFL;
232 sigemptyset(&act.sa_mask);
233 act.sa_flags = SA_RESTART;
234 (void)sigprocmask(SIG_SETMASK, &oset, NULL);
235 (void)sigaction(SIGTSTP, &act, NULL);
236 (void)sigaction(SIGTTOU, &act, NULL);
237 (void)sigaction(SIGTTIN, &act, NULL);
238 (void)sigaction(SIGINT, &saveint, NULL);
239 if (tty.flags & TTY_ERR) {
240 if (ttysignal == SIGINT) {
241 ttysignal = 0;
242 return(NULL); /* user hit ^C */
243 }
244
245 redo:
246 cp = strlen(canonb) > 0 ? canonb : NULL;
247 /* XXX - make iterative, not recursive */
248 return(readtty(pr, cp));
249 }
250 if (equal("", canonb))
251 return("");
252 return(savestr(canonb));
253 }
254
255 /*
256 * Receipt continuation.
257 */
258 void
ttystop(int s)259 ttystop(int s)
260 {
261 struct sigaction act, oact;
262 sigset_t nset;
263 int save_errno;
264
265 /*
266 * Save old handler and set to default.
267 * Unblock receipt of 's' and then resend it.
268 */
269 save_errno = errno;
270 (void)sigemptyset(&act.sa_mask);
271 act.sa_flags = SA_RESTART;
272 act.sa_handler = SIG_DFL;
273 (void)sigaction(s, &act, &oact);
274 (void)sigemptyset(&nset);
275 (void)sigaddset(&nset, s);
276 (void)sigprocmask(SIG_UNBLOCK, &nset, NULL);
277 (void)kill(0, s);
278 (void)sigprocmask(SIG_BLOCK, &nset, NULL);
279 (void)sigaction(s, &oact, NULL);
280 ttysignal = s;
281 errno = save_errno;
282 }
283
284 void
ttyint(int s)285 ttyint(int s)
286 {
287
288 ttysignal = s;
289 }
290
291 static void
tty_flush(struct tty * t)292 tty_flush(struct tty *t)
293 {
294 size_t i, len;
295 int c;
296
297 if (t->cursor < t->len) {
298 for (; t->cursor < t->len; t->cursor++)
299 tty_visc(t, t->buf[t->cursor]);
300 } else if (t->cursor > t->len) {
301 len = t->cursor - t->len;
302 for (i = len; i > 0; i--) {
303 c = t->buf[--t->cursor];
304 if (c == '\t')
305 len += TABWIDTH - 1;
306 else if (iscntrl(c))
307 len++; /* account for leading ^ */
308 }
309 for (i = 0; i < len; i++)
310 tty_putc(t, '\b');
311 for (i = 0; i < len; i++)
312 tty_putc(t, ' ');
313 for (i = 0; i < len; i++)
314 tty_putc(t, '\b');
315 t->cursor = t->len;
316 }
317
318 t->buf[t->len] = '\0';
319 }
320
321 static int
tty_getc(struct tty * t)322 tty_getc(struct tty *t)
323 {
324 ssize_t n;
325 unsigned char c;
326
327 if (ttysignal != 0)
328 n = -1;
329 else
330 n = read(t->fdin, &c, 1);
331 switch (n) {
332 case -1:
333 t->flags |= TTY_ERR;
334 /* FALLTHROUGH */
335 case 0:
336 return 0;
337 default:
338 return c & 0x7f;
339 }
340 }
341
342 static int
tty_insert(struct tty * t,int c,int nocntrl)343 tty_insert(struct tty *t, int c, int nocntrl)
344 {
345 const unsigned char *ws = " \t";
346
347 if (CCEQ(t->keys[VERASE], c)) {
348 if (nocntrl)
349 return 0;
350 if (t->len > 0)
351 t->len--;
352 } else if (CCEQ(t->keys[VWERASE], c)) {
353 if (nocntrl)
354 return 0;
355 for (; t->len > 0; t->len--)
356 if (strchr(ws, t->buf[t->len - 1]) == NULL
357 && ((t->flags & TTY_ALTWERASE) == 0
358 || isalpha(t->buf[t->len - 1])))
359 break;
360 for (; t->len > 0; t->len--)
361 if (strchr(ws, t->buf[t->len - 1]) != NULL
362 || ((t->flags & TTY_ALTWERASE)
363 && !isalpha(t->buf[t->len - 1])))
364 break;
365 } else if (CCEQ(t->keys[VKILL], c)) {
366 if (nocntrl)
367 return 0;
368 t->len = 0;
369 } else {
370 if (t->len == t->size)
371 return 1;
372 t->buf[t->len++] = c;
373 }
374
375 return 0;
376 }
377
378 static void
tty_putc(struct tty * t,int c)379 tty_putc(struct tty *t, int c)
380 {
381 unsigned char cc = c;
382
383 write(t->fdout, &cc, 1);
384 }
385
386 static void
tty_reset(struct tty * t)387 tty_reset(struct tty *t)
388 {
389 memset(t->buf, 0, t->len);
390 t->len = t->cursor = 0;
391 }
392
393 static void
tty_visc(struct tty * t,int c)394 tty_visc(struct tty *t, int c)
395 {
396 int i;
397
398 if (c == '\t') {
399 for (i = 0; i < TABWIDTH; i++)
400 tty_putc(t, ' ');
401 } else if (iscntrl(c)) {
402 tty_putc(t, '^');
403 if (c == 0x7F)
404 tty_putc(t, '?');
405 else
406 tty_putc(t, (c | 0x40));
407 } else {
408 tty_putc(t, c);
409 }
410 }
411