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