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