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