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