xref: /openbsd-src/usr.bin/tset/tset.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: tset.c,v 1.27 2003/06/12 20:58:11 deraadt Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 /*
37  * tset.c - terminal initialization utility
38  *
39  * This code was mostly swiped from 4.4BSD tset, with some obsolescent
40  * cruft removed and substantial portions rewritten.  A Regents of the
41  * University of California copyright applies to some portions of the
42  * code, and is reproduced below:
43  */
44 /*-
45  * Copyright (c) 1980, 1991, 1993
46  *	The Regents of the University of California.  All rights reserved.
47  *
48  * Redistribution and use in source and binary forms, with or without
49  * modification, are permitted provided that the following conditions
50  * are met:
51  * 1. Redistributions of source code must retain the above copyright
52  *    notice, this list of conditions and the following disclaimer.
53  * 2. Redistributions in binary form must reproduce the above copyright
54  *    notice, this list of conditions and the following disclaimer in the
55  *    documentation and/or other materials provided with the distribution.
56  * 3. Neither the name of the University nor the names of its contributors
57  *    may be used to endorse or promote products derived from this software
58  *    without specific prior written permission.
59  *
60  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
61  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
62  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
63  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
64  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
65  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
66  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
67  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
68  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
69  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
70  * SUCH DAMAGE.
71  */
72 
73 #define __INTERNAL_CAPS_VISIBLE	/* we need to see has_hardware_tabs */
74 #include <progs.priv.h>
75 
76 #include <errno.h>
77 #include <stdio.h>
78 #include <termcap.h>
79 #include <fcntl.h>
80 
81 #if HAVE_GETTTYNAM && HAVE_TTYENT_H
82 #include <ttyent.h>
83 #endif
84 #ifdef NeXT
85 char *ttyname(int fd);
86 #endif
87 
88 /* this is just to stifle a missing-prototype warning */
89 #if defined(linux) || defined(__OpenBSD__)
90 # include <sys/ioctl.h>
91 #endif
92 
93 #if NEED_PTEM_H
94 /* they neglected to define struct winsize in termios.h -- it's only
95    in termio.h	*/
96 #include	<sys/stream.h>
97 #include	<sys/ptem.h>
98 #endif
99 
100 #include <curses.h>		/* for bool typedef */
101 #include <dump_entry.h>
102 #include <transform.h>
103 
104 MODULE_ID("$From: tset.c,v 0.49 2001/02/24 23:29:33 tom Exp $")
105 
106 extern char **environ;
107 
108 #undef CTRL
109 #define CTRL(x)	((x) & 0x1f)
110 
111 const char *_nc_progname = "tset";
112 
113 static TTY mode, oldmode;
114 
115 static bool isreset = FALSE;	/* invoked as reset */
116 static int terasechar = -1;	/* new erase character */
117 static int intrchar = -1;	/* new interrupt character */
118 static int tkillchar = -1;	/* new kill character */
119 static int tlines, tcolumns;	/* window size */
120 
121 #define LOWERCASE(c) ((isalpha(CharOf(c)) && isupper(CharOf(c))) ? tolower(CharOf(c)) : (c))
122 
123 static int
124 CaselessCmp(const char *a, const char *b)
125 {				/* strcasecmp isn't portable */
126     while (*a && *b) {
127 	int cmp = LOWERCASE(*a) - LOWERCASE(*b);
128 	if (cmp != 0)
129 	    break;
130 	a++, b++;
131     }
132     return LOWERCASE(*a) - LOWERCASE(*b);
133 }
134 
135 static void
136 err(const char *fmt,...)
137 {
138     va_list ap;
139     va_start(ap, fmt);
140     (void) fprintf(stderr, "tset: ");
141     (void) vfprintf(stderr, fmt, ap);
142     va_end(ap);
143     (void) fprintf(stderr, "\n");
144     exit(EXIT_FAILURE);
145     /* NOTREACHED */
146 }
147 
148 static void
149 failed(const char *msg)
150 {
151     (void)fputs("tset: ", stderr);
152     perror(msg);
153     exit(EXIT_FAILURE);
154     /* NOTREACHED */
155 }
156 
157 static void
158 cat(char *file)
159 {
160     int fd, nr;
161     char buf[BUFSIZ];
162 
163     if ((fd = open(file, O_RDONLY, 0)) < 0)
164 	failed(file);
165 
166     while ((nr = read(fd, buf, sizeof(buf))) > 0)
167 	if (write(STDERR_FILENO, buf, (size_t) nr) == -1)
168 	    failed("write to stderr");
169     if (nr != 0)
170 	failed(file);
171     (void) close(fd);
172 }
173 
174 static int
175 outc(int c)
176 {
177     return putc(c, stderr);
178 }
179 
180 /* Prompt the user for a terminal type. */
181 static const char *
182 askuser(const char *dflt)
183 {
184     static char answer[256];
185     char *p;
186 
187     /* We can get recalled; if so, don't continue uselessly. */
188     if (feof(stdin) || ferror(stdin)) {
189 	(void) fprintf(stderr, "\n");
190 	exit(EXIT_FAILURE);
191     }
192     for (;;) {
193 	if (dflt)
194 	    (void) fprintf(stderr, "Terminal type? [%s] ", dflt);
195 	else
196 	    (void) fprintf(stderr, "Terminal type? ");
197 	(void) fflush(stderr);
198 
199 	if (fgets(answer, sizeof(answer), stdin) == 0) {
200 	    if (dflt == 0) {
201 		(void) fprintf(stderr, "\n");
202 		exit(EXIT_FAILURE);
203 	    }
204 	    return (dflt);
205 	}
206 
207 	if ((p = strchr(answer, '\n')) != 0)
208 	    *p = '\0';
209 	if (answer[0])
210 	    return (answer);
211 	if (dflt != 0)
212 	    return (dflt);
213     }
214 }
215 
216 /**************************************************************************
217  *
218  * Mapping logic begins here
219  *
220  **************************************************************************/
221 
222 /* Baud rate conditionals for mapping. */
223 #define	GT		0x01
224 #define	EQ		0x02
225 #define	LT		0x04
226 #define	NOT		0x08
227 #define	GE		(GT | EQ)
228 #define	LE		(LT | EQ)
229 
230 typedef struct map {
231     struct map *next;		/* Linked list of maps. */
232     const char *porttype;	/* Port type, or "" for any. */
233     const char *type;		/* Terminal type to select. */
234     int conditional;		/* Baud rate conditionals bitmask. */
235     int speed;			/* Baud rate to compare against. */
236 } MAP;
237 
238 static MAP *cur, *maplist;
239 
240 typedef struct speeds {
241     const char *string;
242     int speed;
243 } SPEEDS;
244 
245 static const SPEEDS speeds[] =
246 {
247     {"0", B0},
248     {"50", B50},
249     {"75", B75},
250     {"110", B110},
251     {"134", B134},
252     {"134.5", B134},
253     {"150", B150},
254     {"200", B200},
255     {"300", B300},
256     {"600", B600},
257     {"1200", B1200},
258     {"1800", B1800},
259     {"2400", B2400},
260     {"4800", B4800},
261     {"9600", B9600},
262     /* sgttyb may define up to this point */
263 #ifdef B19200
264     {"19200", B19200},
265 #endif
266 #ifdef B38400
267     {"38400", B38400},
268 #endif
269 #ifdef B19200
270     {"19200", B19200},
271 #endif
272 #ifdef B38400
273     {"38400", B38400},
274 #endif
275 #ifdef B19200
276     {"19200", B19200},
277 #else
278 #ifdef EXTA
279     {"19200", EXTA},
280 #endif
281 #endif
282 #ifdef B38400
283     {"38400", B38400},
284 #else
285 #ifdef EXTB
286     {"38400", EXTB},
287 #endif
288 #endif
289 #ifdef B57600
290     {"57600", B57600},
291 #endif
292 #ifdef B115200
293     {"115200", B115200},
294 #endif
295 #ifdef B230400
296     {"230400", B230400},
297 #endif
298 #ifdef B460800
299     {"460800", B460800},
300 #endif
301     {(char *) 0, 0}
302 };
303 
304 static int
305 tbaudrate(char *rate)
306 {
307     const SPEEDS *sp;
308     int found = FALSE;
309 
310     /* The baudrate number can be preceded by a 'B', which is ignored. */
311     if (*rate == 'B')
312 	++rate;
313 
314     for (sp = speeds; sp->string; ++sp) {
315 	if (!CaselessCmp(rate, sp->string)) {
316 	    found = TRUE;
317 	    break;
318 	}
319     }
320     if (!found)
321 	err("unknown baud rate %s", rate);
322     return (sp->speed);
323 }
324 
325 /*
326  * Syntax for -m:
327  * [port-type][test baudrate]:terminal-type
328  * The baud rate tests are: >, <, @, =, !
329  */
330 static void
331 add_mapping(const char *port, char *arg)
332 {
333     MAP *mapp;
334     char *copy, *p;
335     const char *termp;
336     char *base = 0;
337 
338     copy = strdup(arg);
339     mapp = malloc(sizeof(MAP));
340     if (copy == 0 || mapp == 0)
341 	failed("malloc");
342     mapp->next = 0;
343     if (maplist == 0)
344 	cur = maplist = mapp;
345     else {
346 	cur->next = mapp;
347 	cur = mapp;
348     }
349 
350     mapp->porttype = arg;
351     mapp->conditional = 0;
352 
353     arg = strpbrk(arg, "><@=!:");
354 
355     if (arg == 0) {		/* [?]term */
356 	mapp->type = mapp->porttype;
357 	mapp->porttype = 0;
358 	goto done;
359     }
360 
361     if (arg == mapp->porttype)	/* [><@=! baud]:term */
362 	termp = mapp->porttype = 0;
363     else
364 	termp = base = arg;
365 
366     for (;; ++arg) {		/* Optional conditionals. */
367 	switch (*arg) {
368 	case '<':
369 	    if (mapp->conditional & GT)
370 		goto badmopt;
371 	    mapp->conditional |= LT;
372 	    break;
373 	case '>':
374 	    if (mapp->conditional & LT)
375 		goto badmopt;
376 	    mapp->conditional |= GT;
377 	    break;
378 	case '@':
379 	case '=':		/* Not documented. */
380 	    mapp->conditional |= EQ;
381 	    break;
382 	case '!':
383 	    mapp->conditional |= NOT;
384 	    break;
385 	default:
386 	    goto next;
387 	}
388     }
389 
390   next:
391     if (*arg == ':') {
392 	if (mapp->conditional)
393 	    goto badmopt;
394 	++arg;
395     } else {			/* Optional baudrate. */
396 	arg = strchr(p = arg, ':');
397 	if (arg == 0)
398 	    goto badmopt;
399 	*arg++ = '\0';
400 	mapp->speed = tbaudrate(p);
401     }
402 
403     if (arg == (char *) 0)	/* Non-optional type. */
404 	goto badmopt;
405 
406     mapp->type = arg;
407 
408     /* Terminate porttype, if specified. */
409     if (termp != 0)
410 	*base = '\0';
411 
412     /* If a NOT conditional, reverse the test. */
413     if (mapp->conditional & NOT)
414 	mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
415 
416     /* If user specified a port with an option flag, set it. */
417   done:if (port) {
418 	if (mapp->porttype)
419 	  badmopt:err("illegal -m option format: %s", copy);
420 	mapp->porttype = port;
421     }
422 #ifdef MAPDEBUG
423     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
424     (void) printf("type: %s\n", mapp->type);
425     (void) printf("conditional: ");
426     p = "";
427     if (mapp->conditional & GT) {
428 	(void) printf("GT");
429 	p = "/";
430     }
431     if (mapp->conditional & EQ) {
432 	(void) printf("%sEQ", p);
433 	p = "/";
434     }
435     if (mapp->conditional & LT)
436 	(void) printf("%sLT", p);
437     (void) printf("\nspeed: %d\n", mapp->speed);
438 #endif
439 }
440 
441 /*
442  * Return the type of terminal to use for a port of type 'type', as specified
443  * by the first applicable mapping in 'map'.  If no mappings apply, return
444  * 'type'.
445  */
446 static const char *
447 mapped(const char *type)
448 {
449     MAP *mapp;
450     int match;
451 
452     for (mapp = maplist; mapp; mapp = mapp->next)
453 	if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
454 	    switch (mapp->conditional) {
455 	    case 0:		/* No test specified. */
456 		match = TRUE;
457 		break;
458 	    case EQ:
459 		match = (ospeed == mapp->speed);
460 		break;
461 	    case GE:
462 		match = (ospeed >= mapp->speed);
463 		break;
464 	    case GT:
465 		match = (ospeed > mapp->speed);
466 		break;
467 	    case LE:
468 		match = (ospeed <= mapp->speed);
469 		break;
470 	    case LT:
471 		match = (ospeed < mapp->speed);
472 		break;
473 	    default:
474 		match = FALSE;
475 	    }
476 	    if (match)
477 		return (mapp->type);
478 	}
479     /* No match found; return given type. */
480     return (type);
481 }
482 
483 /**************************************************************************
484  *
485  * Entry fetching
486  *
487  **************************************************************************/
488 
489 /*
490  * Figure out what kind of terminal we're dealing with, and then read in
491  * its termcap entry.
492  */
493 static const char *
494 get_termcap_entry(char *userarg)
495 {
496     int errret;
497     char *p;
498     const char *ttype;
499 #if HAVE_GETTTYNAM
500     struct ttyent *t;
501 #else
502     FILE *fp;
503 #endif
504     char *ttypath;
505 
506     if (userarg) {
507 	ttype = userarg;
508 	goto found;
509     }
510 
511     /* Try the environment. */
512     if ((ttype = getenv("TERM")) != 0)
513 	goto map;
514 
515     if ((ttypath = ttyname(STDERR_FILENO)) != 0) {
516 	p = _nc_basename(ttypath);
517 #if HAVE_GETTTYNAM
518 	/*
519 	 * We have the 4.3BSD library call getttynam(3); that means
520 	 * there's an /etc/ttys to look up device-to-type mappings in.
521 	 * Try ttyname(3); check for dialup or other mapping.
522 	 */
523 	if ((t = getttynam(p))) {
524 	    ttype = t->ty_type;
525 	    goto map;
526 	}
527 #else
528 	if ((fp = fopen("/etc/ttytype", "r")) != 0
529 	    || (fp = fopen("/etc/ttys", "r")) != 0) {
530 	    char buffer[BUFSIZ];
531 	    char *s, *t, *d;
532 
533 	    while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
534 		for (s = buffer, t = d = 0; *s; s++) {
535 		    if (isspace(CharOf(*s)))
536 			*s = '\0';
537 		    else if (t == 0)
538 			t = s;
539 		    else if (d == 0 && s != buffer && s[-1] == '\0')
540 			d = s;
541 		}
542 		if (t != 0 && d != 0 && !strcmp(d, p)) {
543 		    ttype = strdup(t);
544 		    fclose(fp);
545 		    goto map;
546 		}
547 	    }
548 	    fclose(fp);
549 	}
550 #endif /* HAVE_GETTTYNAM */
551     }
552 
553     /* If still undefined, use "unknown". */
554     ttype = "unknown";
555 
556   map:ttype = mapped(ttype);
557 
558     /*
559      * If not a path, remove TERMCAP from the environment so we get a
560      * real entry from /etc/termcap.  This prevents us from being fooled
561      * by out of date stuff in the environment.
562      */
563   found:if ((p = getenv("TERMCAP")) != 0 && *p != '/') {
564 	/* 'unsetenv("TERMCAP")' is not portable.
565 	 * The 'environ' array is better.
566 	 */
567 	int n;
568 	for (n = 0; environ[n] != 0; n++) {
569 	    if (!strncmp("TERMCAP=", environ[n], 8)) {
570 		while ((environ[n] = environ[n + 1]) != 0) {
571 		    n++;
572 		}
573 		break;
574 	    }
575 	}
576     }
577 
578     /*
579      * ttype now contains a pointer to the type of the terminal.
580      * If the first character is '?', ask the user.
581      */
582     if (ttype[0] == '?') {
583 	if (ttype[1] != '\0')
584 	    ttype = askuser(ttype + 1);
585 	else
586 	    ttype = askuser(0);
587     }
588     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
589     while (setupterm((NCURSES_CONST char *) ttype, STDOUT_FILENO, &errret)
590 	   != OK) {
591 	if (errret == 0) {
592 	    (void) fprintf(stderr, "tset: unknown terminal type %s\n",
593 			   ttype);
594 	    ttype = 0;
595 	} else {
596 	    (void) fprintf(stderr,
597 			   "tset: can't initialize terminal type %s (error %d)\n",
598 			   ttype, errret);
599 	    ttype = 0;
600 	}
601 	ttype = askuser(ttype);
602     }
603 #if BROKEN_LINKER
604     tgetflag("am");		/* force lib_termcap.o to be linked for 'ospeed' */
605 #endif
606     return (ttype);
607 }
608 
609 /**************************************************************************
610  *
611  * Mode-setting logic
612  *
613  **************************************************************************/
614 
615 /* some BSD systems have these built in, some systems are missing
616  * one or more definitions. The safest solution is to override.
617  */
618 #undef CEOF
619 #undef CERASE
620 #undef CINTR
621 #undef CKILL
622 #undef CLNEXT
623 #undef CRPRNT
624 #undef CQUIT
625 #undef CSTART
626 #undef CSTOP
627 #undef CSUSP
628 
629 /* control-character defaults */
630 #define CEOF	CTRL('D')
631 #define CERASE	CTRL('H')
632 #define CINTR	127		/* ^? */
633 #define CKILL	CTRL('U')
634 #define CLNEXT  CTRL('v')
635 #define CRPRNT  CTRL('r')
636 #define CQUIT	CTRL('\\')
637 #define CSTART	CTRL('Q')
638 #define CSTOP	CTRL('S')
639 #define CSUSP	CTRL('Z')
640 
641 #define	CHK(val, dft)	((int)val <= 0 ? dft : val)
642 
643 static bool set_tabs(void);
644 
645 /*
646  * Reset the terminal mode bits to a sensible state.  Very useful after
647  * a child program dies in raw mode.
648  */
649 static void
650 reset_mode(void)
651 {
652 #ifdef TERMIOS
653     tcgetattr(STDERR_FILENO, &mode);
654 #else
655     stty(STDERR_FILENO, &mode);
656 #endif
657 
658 #ifdef TERMIOS
659 #if defined(VDISCARD) && defined(CDISCARD)
660     mode.c_cc[VDISCARD] = CHK(mode.c_cc[VDISCARD], CDISCARD);
661 #endif
662     mode.c_cc[VEOF] = CHK(mode.c_cc[VEOF], CEOF);
663     mode.c_cc[VERASE] = CHK(mode.c_cc[VERASE], CERASE);
664 #if defined(VFLUSH) && defined(CFLUSH)
665     mode.c_cc[VFLUSH] = CHK(mode.c_cc[VFLUSH], CFLUSH);
666 #endif
667     mode.c_cc[VINTR] = CHK(mode.c_cc[VINTR], CINTR);
668     mode.c_cc[VKILL] = CHK(mode.c_cc[VKILL], CKILL);
669 #if defined(VLNEXT) && defined(CLNEXT)
670     mode.c_cc[VLNEXT] = CHK(mode.c_cc[VLNEXT], CLNEXT);
671 #endif
672     mode.c_cc[VQUIT] = CHK(mode.c_cc[VQUIT], CQUIT);
673 #if defined(VREPRINT) && defined(CRPRNT)
674     mode.c_cc[VREPRINT] = CHK(mode.c_cc[VREPRINT], CRPRNT);
675 #endif
676 #if defined(VSTART) && defined(CSTART)
677     mode.c_cc[VSTART] = CHK(mode.c_cc[VSTART], CSTART);
678 #endif
679 #if defined(VSTOP) && defined(CSTOP)
680     mode.c_cc[VSTOP] = CHK(mode.c_cc[VSTOP], CSTOP);
681 #endif
682 #if defined(VSUSP) && defined(CSUSP)
683     mode.c_cc[VSUSP] = CHK(mode.c_cc[VSUSP], CSUSP);
684 #endif
685 #if defined(VWERASE) && defined(CWERASE)
686     mode.c_cc[VWERASE] = CHK(mode.c_cc[VWERASE], CWERASE);
687 #endif
688 
689     mode.c_iflag &= ~(IGNBRK | PARMRK | INPCK | ISTRIP | INLCR | IGNCR
690 #ifdef IUCLC
691 		      | IUCLC
692 #endif
693 #ifdef IXANY
694 		      | IXANY
695 #endif
696 		      | IXOFF);
697 
698     mode.c_iflag |= (BRKINT | IGNPAR | ICRNL | IXON
699 #ifdef IMAXBEL
700 		     | IMAXBEL
701 #endif
702 	);
703 
704     mode.c_oflag &= ~(0
705 #ifdef OLCUC
706 		      | OLCUC
707 #endif
708 #ifdef OCRNL
709 		      | OCRNL
710 #endif
711 #ifdef ONOCR
712 		      | ONOCR
713 #endif
714 #ifdef ONLRET
715 		      | ONLRET
716 #endif
717 #ifdef OFILL
718 		      | OFILL
719 #endif
720 #ifdef OFDEL
721 		      | OFDEL
722 #endif
723 #ifdef NLDLY
724 		      | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY | FFDLY
725 #endif
726 	);
727 
728     mode.c_oflag |= (OPOST
729 #ifdef ONLCR
730 		     | ONLCR
731 #endif
732 	);
733 
734     mode.c_cflag &= ~(CSIZE | CSTOPB | PARENB | PARODD | CLOCAL);
735     mode.c_cflag |= (CS8 | CREAD);
736     mode.c_lflag &= ~(ECHONL | NOFLSH
737 #ifdef TOSTOP
738 		      | TOSTOP
739 #endif
740 #ifdef ECHOPTR
741 		      | ECHOPRT
742 #endif
743 #ifdef XCASE
744 		      | XCASE
745 #endif
746 	);
747 
748     mode.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK
749 #ifdef ECHOCTL
750 		     | ECHOCTL
751 #endif
752 #ifdef ECHOKE
753 		     | ECHOKE
754 #endif
755 	);
756 #endif
757 
758 #ifdef TERMIOS
759     tcsetattr(STDERR_FILENO, TCSADRAIN, &mode);
760 #else
761     stty(STDERR_FILENO, &mode);
762 #endif
763 }
764 
765 /*
766  * Returns a "good" value for the erase character.  This is loosely based on
767  * the BSD4.4 logic.
768  */
769 #ifdef TERMIOS
770 static int
771 default_erase(void)
772 {
773     int result;
774 
775     if (over_strike
776 	&& key_backspace != 0
777 	&& strlen(key_backspace) == 1)
778 	result = key_backspace[0];
779     else
780 	result = CERASE;
781 
782     return result;
783 }
784 #endif
785 
786 /*
787  * Update the values of the erase, interrupt, and kill characters in 'mode'.
788  *
789  * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
790  * characters if they're unset, or if we specify them as options.  This differs
791  * from BSD 4.4 tset, which always sets erase.
792  */
793 static void
794 set_control_chars(void)
795 {
796 #ifdef TERMIOS
797     if (mode.c_cc[VERASE] == 0 || terasechar >= 0)
798 	mode.c_cc[VERASE] = terasechar >= 0 ? terasechar : default_erase();
799 
800     if (mode.c_cc[VINTR] == 0 || intrchar >= 0)
801 	mode.c_cc[VINTR] = intrchar >= 0 ? intrchar : CINTR;
802 
803     if (mode.c_cc[VKILL] == 0 || tkillchar >= 0)
804 	mode.c_cc[VKILL] = tkillchar >= 0 ? tkillchar : CKILL;
805 #endif
806 }
807 
808 /*
809  * Set up various conversions in 'mode', including parity, tabs, returns,
810  * echo, and case, according to the termcap entry.  If the program we're
811  * running was named with a leading upper-case character, map external
812  * uppercase to internal lowercase.
813  */
814 static void
815 set_conversions(void)
816 {
817 #ifdef __OBSOLETE__
818     /*
819      * Conversion logic for some *really* ancient terminal glitches,
820      * not supported in terminfo.  Left here for succeeding generations
821      * to marvel at.
822      */
823     if (tgetflag("UC")) {
824 #ifdef IUCLC
825 	mode.c_iflag |= IUCLC;
826 	mode.c_oflag |= OLCUC;
827 #endif
828     } else if (tgetflag("LC")) {
829 #ifdef IUCLC
830 	mode.c_iflag &= ~IUCLC;
831 	mode.c_oflag &= ~OLCUC;
832 #endif
833     }
834     mode.c_iflag &= ~(PARMRK | INPCK);
835     mode.c_lflag |= ICANON;
836     if (tgetflag("EP")) {
837 	mode.c_cflag |= PARENB;
838 	mode.c_cflag &= ~PARODD;
839     }
840     if (tgetflag("OP")) {
841 	mode.c_cflag |= PARENB;
842 	mode.c_cflag |= PARODD;
843     }
844 #endif /* __OBSOLETE__ */
845 
846 #ifdef TERMIOS
847 #ifdef ONLCR
848     mode.c_oflag |= ONLCR;
849 #endif
850     mode.c_iflag |= ICRNL;
851     mode.c_lflag |= ECHO;
852 #ifdef OXTABS
853     mode.c_oflag |= OXTABS;
854 #endif /* OXTABS */
855 
856     /* test used to be tgetflag("NL") */
857     if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
858 	/* Newline, not linefeed. */
859 #ifdef ONLCR
860 	mode.c_oflag &= ~ONLCR;
861 #endif
862 	mode.c_iflag &= ~ICRNL;
863     }
864 #ifdef __OBSOLETE__
865     if (tgetflag("HD"))		/* Half duplex. */
866 	mode.c_lflag &= ~ECHO;
867 #endif /* __OBSOLETE__ */
868 #ifdef OXTABS
869     /* test used to be tgetflag("pt") */
870     if (has_hardware_tabs)	/* Print tabs. */
871 	mode.c_oflag &= ~OXTABS;
872 #endif /* OXTABS */
873     mode.c_lflag |= (ECHOE | ECHOK);
874 #endif
875 }
876 
877 /* Output startup string. */
878 static void
879 set_init(void)
880 {
881     char *p;
882     bool settle;
883 
884 #ifdef __OBSOLETE__
885     if (pad_char != (char *) 0)	/* Get/set pad character. */
886 	PC = pad_char[0];
887 #endif /* OBSOLETE */
888 
889 #ifdef TAB3
890     if (oldmode.c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
891 	oldmode.c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
892 	tcsetattr(STDERR_FILENO, TCSADRAIN, &oldmode);
893     }
894 #endif
895     settle = set_tabs();
896 
897     if (isreset) {
898 	if ((p = reset_1string) != 0) {
899 	    tputs(p, 0, outc);
900 	    settle = TRUE;
901 	}
902 	if ((p = reset_2string) != 0) {
903 	    tputs(p, 0, outc);
904 	    settle = TRUE;
905 	}
906 	/* What about rf, rs3, as per terminfo man page? */
907 	/* also might be nice to send rmacs, rmul, rmm */
908 	if ((p = reset_file) != 0
909 	    || (p = init_file) != 0) {
910 	    cat(p);
911 	    settle = TRUE;
912 	}
913     }
914 
915     if (settle) {
916 	(void) putc('\r', stderr);
917 	(void) fflush(stderr);
918 	(void) napms(1000);	/* Settle the terminal. */
919     }
920 }
921 
922 /*
923  * Set the hardware tabs on the terminal, using the ct (clear all tabs),
924  * st (set one tab) and ch (horizontal cursor addressing) capabilities.
925  * This is done before if and is, so they can patch in case we blow this.
926  * Return TRUE if we set any tab stops, FALSE if not.
927  */
928 static bool
929 set_tabs(void)
930 {
931     if (set_tab && clear_all_tabs) {
932 	int c;
933 
934 	(void) putc('\r', stderr);	/* Force to left margin. */
935 	tputs(clear_all_tabs, 0, outc);
936 
937 	for (c = 8; c < tcolumns; c += 8) {
938 	    /* Get to the right column.  In BSD tset, this
939 	     * used to try a bunch of half-clever things
940 	     * with cup and hpa, for an average saving of
941 	     * somewhat less than two character times per
942 	     * tab stop, less that .01 sec at 2400cps. We
943 	     * lost all this cruft because it seemed to be
944 	     * introducing some odd bugs.
945 	     * ----------12345678----------- */
946 	    (void) fputs("        ", stderr);
947 	    tputs(set_tab, 0, outc);
948 	}
949 	putc('\r', stderr);
950 	return (TRUE);
951     }
952     return (FALSE);
953 }
954 
955 /**************************************************************************
956  *
957  * Main sequence
958  *
959  **************************************************************************/
960 
961 /*
962  * Tell the user if a control key has been changed from the default value.
963  */
964 #ifdef TERMIOS
965 static void
966 report(const char *name, int which, unsigned def)
967 {
968     unsigned older, newer;
969     char *p;
970 
971     newer = mode.c_cc[which];
972     older = oldmode.c_cc[which];
973 
974     if (older == newer && older == def)
975 	return;
976 
977     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
978 
979     /*
980      * Check 'delete' before 'backspace', since the key_backspace value
981      * is ambiguous.
982      */
983     if (newer == 0177)
984 	(void) fprintf(stderr, "delete.\n");
985     else if ((p = key_backspace) != 0
986 	     && newer == (unsigned char) p[0]
987 	     && p[1] == '\0')
988 	(void) fprintf(stderr, "backspace.\n");
989     else if (newer < 040) {
990 	newer ^= 0100;
991 	(void) fprintf(stderr, "control-%c (^%c).\n", newer, newer);
992     } else
993 	(void) fprintf(stderr, "%c.\n", newer);
994 }
995 #endif
996 
997 /*
998  * Convert the obsolete argument forms into something that getopt can handle.
999  * This means that -e, -i and -k get default arguments supplied for them.
1000  */
1001 static void
1002 obsolete(char **argv)
1003 {
1004     for (; *argv; ++argv) {
1005 	char *parm = argv[0];
1006 
1007 	if (parm[0] == '-' && parm[1] == '\0') {
1008 	    argv[0] = strdup("-q");
1009 	    continue;
1010 	}
1011 
1012 	if ((parm[0] != '-')
1013 	    || (argv[1] && argv[1][0] != '-')
1014 	    || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
1015 	    || (parm[2] != '\0'))
1016 	    continue;
1017 	switch (argv[0][1]) {
1018 	case 'e':
1019 	    argv[0] = strdup("-e^H");
1020 	    break;
1021 	case 'i':
1022 	    argv[0] = strdup("-i^C");
1023 	    break;
1024 	case 'k':
1025 	    argv[0] = strdup("-k^U");
1026 	    break;
1027 	}
1028     }
1029 }
1030 
1031 static void
1032 usage(const char *pname)
1033 {
1034     (void) fprintf(stderr,
1035 		   "usage: %s [-IQVrs] [-] [-e ch] [-i ch] [-k ch] [-m mapping] [terminal]\n", pname);
1036     exit(EXIT_FAILURE);
1037 }
1038 
1039 static char
1040 arg_to_char(void)
1041 {
1042     return (optarg[0] == '^' && optarg[1] != '\0')
1043 	? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
1044 	: optarg[0];
1045 }
1046 
1047 int
1048 main(int argc, char **argv)
1049 {
1050 #if defined(TIOCGWINSZ) && defined(TIOCSWINSZ)
1051     struct winsize win;
1052 #endif
1053     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
1054     const char *p;
1055     const char *ttype;
1056 #ifdef __OpenBSD__
1057     char tcapbuf[1024], *t;
1058     int tcgetent(char *, const char *);
1059     void wrtermcap (char *);
1060 #endif /* __OpenBSD__ */
1061 
1062     if (GET_TTY(STDERR_FILENO, &mode) < 0)
1063 	failed("standard error");
1064     oldmode = mode;
1065 #ifdef TERMIOS
1066     ospeed = cfgetospeed(&mode);
1067 #else
1068     ospeed = mode.sg_ospeed;
1069 #endif
1070 
1071     p = _nc_basename(*argv);
1072     if (!strcmp(p, PROG_RESET)) {
1073 	isreset = TRUE;
1074 	reset_mode();
1075     }
1076 
1077     obsolete(argv);
1078     noinit = noset = quiet = Sflag = sflag = showterm = 0;
1079     while ((ch = getopt(argc, argv, "a:d:e:Ii:k:m:np:qQSrsV")) != -1) {
1080 	switch (ch) {
1081 	case 'q':		/* display term only */
1082 	    noset = 1;
1083 	    break;
1084 	case 'a':		/* OBSOLETE: map identifier to type */
1085 	    add_mapping("arpanet", optarg);
1086 	    break;
1087 	case 'd':		/* OBSOLETE: map identifier to type */
1088 	    add_mapping("dialup", optarg);
1089 	    break;
1090 	case 'e':		/* erase character */
1091 	    terasechar = arg_to_char();
1092 	    break;
1093 	case 'I':		/* no initialization strings */
1094 	    noinit = 1;
1095 	    break;
1096 	case 'i':		/* interrupt character */
1097 	    intrchar = arg_to_char();
1098 	    break;
1099 	case 'k':		/* kill character */
1100 	    tkillchar = arg_to_char();
1101 	    break;
1102 	case 'm':		/* map identifier to type */
1103 	    add_mapping(0, optarg);
1104 	    break;
1105 	case 'n':		/* OBSOLETE: set new tty driver */
1106 	    break;
1107 	case 'p':		/* OBSOLETE: map identifier to type */
1108 	    add_mapping("plugboard", optarg);
1109 	    break;
1110 	case 'Q':		/* don't output control key settings */
1111 	    quiet = 1;
1112 	    break;
1113 	case 'S':		/* OBSOLETE: output TERM & TERMCAP */
1114 	    Sflag = 1;
1115 	    break;
1116 	case 'r':		/* display term on stderr */
1117 	    showterm = 1;
1118 	    break;
1119 	case 's':		/* output TERM set command */
1120 	    sflag = 1;
1121 	    break;
1122 	case 'V':
1123 	    puts(curses_version());
1124 	    return EXIT_SUCCESS;
1125 	case '?':
1126 	default:
1127 	    usage(*argv);
1128 	}
1129     }
1130     argc -= optind;
1131     argv += optind;
1132 
1133     if (argc > 1)
1134 	usage(*argv);
1135 
1136     ttype = get_termcap_entry(*argv);
1137 #ifdef __OpenBSD__
1138     if (tcgetent(tcapbuf, ttype) < 0)
1139 	    tcapbuf[0] = '\0';
1140 #endif /* __OpenBSD__ */
1141 
1142     if (!noset) {
1143 	tcolumns = columns;
1144 	tlines = lines;
1145 
1146 #if defined(TIOCGWINSZ) && defined(TIOCSWINSZ)
1147 	/* Set window size */
1148 	(void) ioctl(STDERR_FILENO, TIOCGWINSZ, &win);
1149 	if (win.ws_row == 0 && win.ws_col == 0 &&
1150 	    tlines > 0 && tcolumns > 0) {
1151 	    win.ws_row = tlines;
1152 	    win.ws_col = tcolumns;
1153 	    (void) ioctl(STDERR_FILENO, TIOCSWINSZ, &win);
1154 	}
1155 #endif
1156 	set_control_chars();
1157 	set_conversions();
1158 
1159 	if (!noinit)
1160 	    set_init();
1161 
1162 	/* Set the modes if they've changed. */
1163 	if (memcmp(&mode, &oldmode, sizeof(mode))) {
1164 #ifdef TERMIOS
1165 	    tcsetattr(STDERR_FILENO, TCSADRAIN, &mode);
1166 #else
1167 	    stty(STDERR_FILENO, &mode);
1168 #endif
1169 	}
1170     }
1171 
1172     /* Get the terminal name from the entry. */
1173     ttype = _nc_first_name(cur_term->type.term_names);
1174 
1175     if (noset)
1176 	(void) printf("%s\n", ttype);
1177     else {
1178 	if (showterm)
1179 	    (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
1180 	/*
1181 	 * If erase, kill and interrupt characters could have been
1182 	 * modified and not -Q, display the changes.
1183 	 */
1184 #ifdef TERMIOS
1185 	if (!quiet) {
1186 	    report("Erase", VERASE, CERASE);
1187 	    report("Kill", VKILL, CINTR);
1188 	    report("Interrupt", VINTR, CKILL);
1189 	}
1190 #endif
1191     }
1192 
1193 #ifdef __OpenBSD__
1194     if (Sflag) {
1195 	if (tcapbuf[0]) {
1196 	    (void) printf("%s ", ttype);
1197 	    wrtermcap(tcapbuf);
1198 	} else
1199 	    err("No termcap entry for %s, only terminfo.", ttype);
1200     }
1201 #else
1202     if (Sflag)
1203 	err("The -S option is not supported under terminfo.");
1204 #endif /* __OpenBSD__ */
1205 
1206 #ifdef __OpenBSD__
1207     if (sflag) {
1208 	/*
1209 	 * Figure out what shell we're using.  A hack, we look for an
1210 	 * environmental variable SHELL ending in "csh".
1211 	 */
1212 	if ((p = getenv("SHELL")) != 0 && !strcmp(p + strlen(p) - 3, "csh")) {
1213 	    if (tcapbuf[0])
1214 		p = "set noglob histchars="";\nsetenv TERM %s;\nsetenv TERMCAP ";
1215 	    else
1216 		p = "set noglob histchars="";\nsetenv TERM %s;\n";
1217 		t = "unset noglob histchars;\n";
1218 	} else {
1219 	    if (tcapbuf) {
1220 		p = "TERM=%s;\nTERMCAP=";
1221 		t = "export TERMCAP TERM;\n";
1222 	    } else {
1223 		if (tcapbuf) {
1224 		    p = "TERM=%s;\nTERMCAP=";
1225 		    t = "export TERMCAP TERM;\n";
1226 		} else {
1227 		    p = "TERM=%s;\n";
1228 		    t = "export TERMCAP;\n";
1229 		}
1230 	    }
1231 	}
1232 	(void) printf(p, ttype);
1233 	if (tcapbuf[0]) {
1234 	    putchar('\'');
1235 	    wrtermcap(tcapbuf);
1236 	    fputs("';\n", stdout);
1237 	}
1238 	(void)fputs(t, stdout);
1239     }
1240 #else
1241     if (sflag) {
1242 	/*
1243 	 * Figure out what shell we're using.  A hack, we look for an
1244 	 * environmental variable SHELL ending in "csh".
1245 	 */
1246 	if ((p = getenv("SHELL")) != 0
1247 	    && !strcmp(p + strlen(p) - 3, "csh"))
1248 	    p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
1249 	else
1250 	    p = "TERM=%s;\n";
1251 	(void) printf(p, ttype);
1252     }
1253 #endif /* __OpenBSD__ */
1254 
1255     return EXIT_SUCCESS;
1256 }
1257 
1258 /* tset.c ends here */
1259