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