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