xref: /freebsd/contrib/ncurses/progs/tset.c (revision 7fdf597e96a02165cfe22ff357b857d5fa15ed8a)
1 /****************************************************************************
2  * Copyright 2020-2021,2024 Thomas E. Dickey                                *
3  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 
30 /****************************************************************************
31  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
32  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
33  *     and: Thomas E. Dickey                        1996-on                 *
34  ****************************************************************************/
35 
36 /*
37  * Notes:
38  * The initial adaptation from 4.4BSD Lite sources in September 1995 used 686
39  * lines from that version, and made changes/additions for 150 lines.  There
40  * was no reformatting, so with/without ignoring whitespace, the amount of
41  * change is the same.
42  *
43  * Comparing with current (2009) source, excluding this comment:
44  * a) 209 lines match identically to the 4.4BSD Lite sources, with 771 lines
45  *    changed/added.
46  * a) Ignoring whitespace, the current version still uses 516 lines from the
47  *    4.4BSD Lite sources, with 402 lines changed/added.
48  *
49  * Raymond's original comment on this follows...
50  */
51 
52 /*
53  * tset.c - terminal initialization utility
54  *
55  * This code was mostly swiped from 4.4BSD tset, with some obsolescent
56  * cruft removed and substantial portions rewritten.  A Regents of the
57  * University of California copyright applies to some portions of the
58  * code, and is reproduced below:
59  */
60 /*-
61  * Copyright (c) 1980, 1991, 1993
62  *	The Regents of the University of California.  All rights reserved.
63  *
64  * Redistribution and use in source and binary forms, with or without
65  * modification, are permitted provided that the following conditions
66  * are met:
67  * 1. Redistributions of source code must retain the above copyright
68  *    notice, this list of conditions and the following disclaimer.
69  * 2. Redistributions in binary form must reproduce the above copyright
70  *    notice, this list of conditions and the following disclaimer in the
71  *    documentation and/or other materials provided with the distribution.
72  * 3. Neither the name of the University nor the names of its contributors
73  *    may be used to endorse or promote products derived from this software
74  *    without specific prior written permission.
75  *
76  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
77  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
78  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
79  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
80  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
81  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
82  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
83  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
84  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
85  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
86  * SUCH DAMAGE.
87  */
88 
89 #include <reset_cmd.h>
90 #include <termcap.h>
91 #include <transform.h>
92 #include <tty_settings.h>
93 
94 #if HAVE_GETTTYNAM
95 #include <ttyent.h>
96 #endif
97 #ifdef NeXT
98 char *ttyname(int fd);
99 #endif
100 
101 MODULE_ID("$Id: tset.c,v 1.135 2024/04/20 22:20:41 tom Exp $")
102 
103 #ifndef environ
104 extern char **environ;
105 #endif
106 
107 const char *_nc_progname = "tset";
108 
109 #define LOWERCASE(c) ((isalpha(UChar(c)) && isupper(UChar(c))) ? tolower(UChar(c)) : (c))
110 
111 static GCC_NORETURN void exit_error(void);
112 
113 static int
114 CaselessCmp(const char *a, const char *b)
115 {				/* strcasecmp isn't portable */
116     while (*a && *b) {
117 	int cmp = LOWERCASE(*a) - LOWERCASE(*b);
118 	if (cmp != 0)
119 	    break;
120 	a++, b++;
121     }
122     return LOWERCASE(*a) - LOWERCASE(*b);
123 }
124 
125 static GCC_NORETURN void
126 exit_error(void)
127 {
128     restore_tty_settings();
129     (void) fprintf(stderr, "\n");
130     fflush(stderr);
131     ExitProgram(EXIT_FAILURE);
132     /* NOTREACHED */
133 }
134 
135 static GCC_NORETURN void
136 err(const char *fmt, ...)
137 {
138     va_list ap;
139     va_start(ap, fmt);
140     (void) fprintf(stderr, "%s: ", _nc_progname);
141     (void) vfprintf(stderr, fmt, ap);
142     va_end(ap);
143     exit_error();
144     /* NOTREACHED */
145 }
146 
147 static GCC_NORETURN void
148 failed(const char *msg)
149 {
150     char temp[BUFSIZ];
151     size_t len = strlen(_nc_progname) + 2;
152 
153     if ((int) len < (int) sizeof(temp) - 12) {
154 	_nc_STRCPY(temp, _nc_progname, sizeof(temp));
155 	_nc_STRCAT(temp, ": ", sizeof(temp));
156     } else {
157 	_nc_STRCPY(temp, "tset: ", sizeof(temp));
158     }
159     _nc_STRNCAT(temp, msg, sizeof(temp), sizeof(temp) - strlen(temp) - 2);
160     perror(temp);
161     exit_error();
162     /* NOTREACHED */
163 }
164 
165 /* Prompt the user for a terminal type. */
166 static const char *
167 askuser(const char *dflt)
168 {
169     static char answer[256];
170 
171     /* We can get recalled; if so, don't continue uselessly. */
172     clearerr(stdin);
173     if (feof(stdin) || ferror(stdin)) {
174 	(void) fprintf(stderr, "\n");
175 	exit_error();
176 	/* NOTREACHED */
177     }
178 
179     for (;;) {
180 	char *p;
181 
182 	if (dflt)
183 	    (void) fprintf(stderr, "Terminal type? [%s] ", dflt);
184 	else
185 	    (void) fprintf(stderr, "Terminal type? ");
186 	(void) fflush(stderr);
187 
188 	if (fgets(answer, sizeof(answer), stdin) == 0) {
189 	    if (dflt == 0) {
190 		exit_error();
191 		/* NOTREACHED */
192 	    }
193 	    return (dflt);
194 	}
195 
196 	if ((p = strchr(answer, '\n')) != 0)
197 	    *p = '\0';
198 	if (answer[0])
199 	    return (answer);
200 	if (dflt != 0)
201 	    return (dflt);
202     }
203 }
204 
205 /**************************************************************************
206  *
207  * Mapping logic begins here
208  *
209  **************************************************************************/
210 
211 /* Baud rate conditionals for mapping. */
212 #define	GT		0x01
213 #define	EQ		0x02
214 #define	LT		0x04
215 #define	NOT		0x08
216 #define	GE		(GT | EQ)
217 #define	LE		(LT | EQ)
218 
219 typedef struct map {
220     struct map *next;		/* Linked list of maps. */
221     const char *porttype;	/* Port type, or "" for any. */
222     const char *type;		/* Terminal type to select. */
223     int conditional;		/* Baud rate conditionals bitmask. */
224     int speed;			/* Baud rate to compare against. */
225 } MAP;
226 
227 static MAP *cur, *maplist;
228 
229 #define DATA(name,value) { { name }, value }
230 
231 typedef struct speeds {
232     const char string[8];
233     int speed;
234 } SPEEDS;
235 
236 #if defined(EXP_WIN32_DRIVER)
237 static const SPEEDS speeds[] =
238 {
239     {"0", 0}
240 };
241 #else
242 static const SPEEDS speeds[] =
243 {
244     DATA("0", B0),
245     DATA("50", B50),
246     DATA("75", B75),
247     DATA("110", B110),
248     DATA("134", B134),
249     DATA("134.5", B134),
250     DATA("150", B150),
251     DATA("200", B200),
252     DATA("300", B300),
253     DATA("600", B600),
254     DATA("1200", B1200),
255     DATA("1800", B1800),
256     DATA("2400", B2400),
257     DATA("4800", B4800),
258     DATA("9600", B9600),
259     /* sgttyb may define up to this point */
260 #ifdef B19200
261     DATA("19200", B19200),
262 #endif
263 #ifdef B38400
264     DATA("38400", B38400),
265 #endif
266 #ifdef B19200
267     DATA("19200", B19200),
268 #endif
269 #ifdef B38400
270     DATA("38400", B38400),
271 #endif
272 #ifdef B19200
273     DATA("19200", B19200),
274 #else
275 #ifdef EXTA
276     DATA("19200", EXTA),
277 #endif
278 #endif
279 #ifdef B38400
280     DATA("38400", B38400),
281 #else
282 #ifdef EXTB
283     DATA("38400", EXTB),
284 #endif
285 #endif
286 #ifdef B57600
287     DATA("57600", B57600),
288 #endif
289 #ifdef B76800
290     DATA("76800", B57600),
291 #endif
292 #ifdef B115200
293     DATA("115200", B115200),
294 #endif
295 #ifdef B153600
296     DATA("153600", B153600),
297 #endif
298 #ifdef B230400
299     DATA("230400", B230400),
300 #endif
301 #ifdef B307200
302     DATA("307200", B307200),
303 #endif
304 #ifdef B460800
305     DATA("460800", B460800),
306 #endif
307 #ifdef B500000
308     DATA("500000", B500000),
309 #endif
310 #ifdef B576000
311     DATA("576000", B576000),
312 #endif
313 #ifdef B921600
314     DATA("921600", B921600),
315 #endif
316 #ifdef B1000000
317     DATA("1000000", B1000000),
318 #endif
319 #ifdef B1152000
320     DATA("1152000", B1152000),
321 #endif
322 #ifdef B1500000
323     DATA("1500000", B1500000),
324 #endif
325 #ifdef B2000000
326     DATA("2000000", B2000000),
327 #endif
328 #ifdef B2500000
329     DATA("2500000", B2500000),
330 #endif
331 #ifdef B3000000
332     DATA("3000000", B3000000),
333 #endif
334 #ifdef B3500000
335     DATA("3500000", B3500000),
336 #endif
337 #ifdef B4000000
338     DATA("4000000", B4000000),
339 #endif
340 };
341 #undef DATA
342 #endif
343 
344 static int
345 tbaudrate(char *rate)
346 {
347     const SPEEDS *sp = 0;
348     size_t n;
349 
350     /* The baudrate number can be preceded by a 'B', which is ignored. */
351     if (*rate == 'B')
352 	++rate;
353 
354     for (n = 0; n < SIZEOF(speeds); ++n) {
355 	if (n > 0 && (speeds[n].speed <= speeds[n - 1].speed)) {
356 	    /* if the speeds are not increasing, likely a numeric overflow */
357 	    break;
358 	}
359 	if (!CaselessCmp(rate, speeds[n].string)) {
360 	    sp = speeds + n;
361 	    break;
362 	}
363     }
364     if (sp == 0)
365 	err("unknown baud rate %s", rate);
366     return (sp->speed);
367 }
368 
369 /*
370  * Syntax for -m:
371  * [port-type][test baudrate]:terminal-type
372  * The baud rate tests are: >, <, @, =, !
373  */
374 static void
375 add_mapping(const char *port, char *arg)
376 {
377     MAP *mapp;
378     char *copy, *p;
379     const char *termp;
380     char *base = 0;
381 
382     copy = strdup(arg);
383     mapp = typeMalloc(MAP, 1);
384     if (copy == 0 || mapp == 0)
385 	failed("malloc");
386 
387     assert(copy != 0);
388     assert(mapp != 0);
389 
390     mapp->next = 0;
391     if (maplist == 0)
392 	cur = maplist = mapp;
393     else {
394 	cur->next = mapp;
395 	cur = mapp;
396     }
397 
398     mapp->porttype = arg;
399     mapp->conditional = 0;
400 
401     arg = strpbrk(arg, "><@=!:");
402 
403     if (arg == 0) {		/* [?]term */
404 	mapp->type = mapp->porttype;
405 	mapp->porttype = 0;
406 	goto done;
407     }
408 
409     if (arg == mapp->porttype)	/* [><@=! baud]:term */
410 	termp = mapp->porttype = 0;
411     else
412 	termp = base = arg;
413 
414     for (;; ++arg) {		/* Optional conditionals. */
415 	switch (*arg) {
416 	case '<':
417 	    if (mapp->conditional & GT)
418 		goto badmopt;
419 	    mapp->conditional |= LT;
420 	    break;
421 	case '>':
422 	    if (mapp->conditional & LT)
423 		goto badmopt;
424 	    mapp->conditional |= GT;
425 	    break;
426 	case '@':
427 	case '=':		/* Not documented. */
428 	    mapp->conditional |= EQ;
429 	    break;
430 	case '!':
431 	    mapp->conditional |= NOT;
432 	    break;
433 	default:
434 	    goto next;
435 	}
436     }
437 
438   next:
439     if (*arg == ':') {
440 	if (mapp->conditional)
441 	    goto badmopt;
442 	++arg;
443     } else {			/* Optional baudrate. */
444 	arg = strchr(p = arg, ':');
445 	if (arg == 0)
446 	    goto badmopt;
447 	*arg++ = '\0';
448 	mapp->speed = tbaudrate(p);
449     }
450 
451     mapp->type = arg;
452 
453     /* Terminate porttype, if specified. */
454     if (termp != 0)
455 	*base = '\0';
456 
457     /* If a NOT conditional, reverse the test. */
458     if (mapp->conditional & NOT)
459 	mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
460 
461     /* If user specified a port with an option flag, set it. */
462   done:
463     if (port) {
464 	if (mapp->porttype) {
465 	  badmopt:
466 	    err("illegal -m option format: %s", copy);
467 	}
468 	mapp->porttype = port;
469     }
470     free(copy);
471 #ifdef MAPDEBUG
472     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
473     (void) printf("type: %s\n", mapp->type);
474     (void) printf("conditional: ");
475     p = "";
476     if (mapp->conditional & GT) {
477 	(void) printf("GT");
478 	p = "/";
479     }
480     if (mapp->conditional & EQ) {
481 	(void) printf("%sEQ", p);
482 	p = "/";
483     }
484     if (mapp->conditional & LT)
485 	(void) printf("%sLT", p);
486     (void) printf("\nspeed: %d\n", mapp->speed);
487 #endif
488 }
489 
490 /*
491  * Return the type of terminal to use for a port of type 'type', as specified
492  * by the first applicable mapping in 'map'.  If no mappings apply, return
493  * 'type'.
494  */
495 static const char *
496 mapped(const char *type)
497 {
498     MAP *mapp;
499     int match;
500 
501     for (mapp = maplist; mapp; mapp = mapp->next)
502 	if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
503 	    switch (mapp->conditional) {
504 	    case 0:		/* No test specified. */
505 		match = TRUE;
506 		break;
507 	    case EQ:
508 		match = ((int) ospeed == mapp->speed);
509 		break;
510 	    case GE:
511 		match = ((int) ospeed >= mapp->speed);
512 		break;
513 	    case GT:
514 		match = ((int) ospeed > mapp->speed);
515 		break;
516 	    case LE:
517 		match = ((int) ospeed <= mapp->speed);
518 		break;
519 	    case LT:
520 		match = ((int) ospeed < mapp->speed);
521 		break;
522 	    default:
523 		match = FALSE;
524 	    }
525 	    if (match)
526 		return (mapp->type);
527 	}
528     /* No match found; return given type. */
529     return (type);
530 }
531 
532 /**************************************************************************
533  *
534  * Entry fetching
535  *
536  **************************************************************************/
537 
538 /*
539  * Figure out what kind of terminal we're dealing with, and then read in
540  * its termcap entry.
541  */
542 static const char *
543 get_termcap_entry(int fd, char *userarg)
544 {
545     int errret;
546     char *p;
547     const char *ttype;
548 #if HAVE_PATH_TTYS
549 #if HAVE_GETTTYNAM
550     struct ttyent *t;
551 #else
552     FILE *fp;
553 #endif
554     char *ttypath;
555 #endif /* HAVE_PATH_TTYS */
556 
557     (void) fd;
558 
559     if (userarg) {
560 	ttype = userarg;
561 	goto found;
562     }
563 
564     /* Try the environment. */
565     if ((ttype = getenv("TERM")) != 0)
566 	goto map;
567 
568 #if HAVE_PATH_TTYS
569     if ((ttypath = ttyname(fd)) != 0) {
570 	p = _nc_basename(ttypath);
571 #if HAVE_GETTTYNAM
572 	/*
573 	 * We have the 4.3BSD library call getttynam(3); that means
574 	 * there's an /etc/ttys to look up device-to-type mappings in.
575 	 * Try ttyname(3); check for dialup or other mapping.
576 	 */
577 	if ((t = getttynam(p))) {
578 	    ttype = t->ty_type;
579 	    goto map;
580 	}
581 #else
582 	if ((fp = fopen("/etc/ttytype", "r")) != 0
583 	    || (fp = fopen("/etc/ttys", "r")) != 0) {
584 	    char buffer[BUFSIZ];
585 	    char *s, *t, *d;
586 
587 	    while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
588 		for (s = buffer, t = d = 0; *s; s++) {
589 		    if (isspace(UChar(*s)))
590 			*s = '\0';
591 		    else if (t == 0)
592 			t = s;
593 		    else if (d == 0 && s != buffer && s[-1] == '\0')
594 			d = s;
595 		}
596 		if (t != 0 && d != 0 && !strcmp(d, p)) {
597 		    ttype = strdup(t);
598 		    fclose(fp);
599 		    goto map;
600 		}
601 	    }
602 	    fclose(fp);
603 	}
604 #endif /* HAVE_GETTTYNAM */
605     }
606 #endif /* HAVE_PATH_TTYS */
607 
608     /* If still undefined, use "unknown". */
609     ttype = "unknown";
610 
611   map:ttype = mapped(ttype);
612 
613     /*
614      * If not a path, remove TERMCAP from the environment so we get a
615      * real entry from /etc/termcap.  This prevents us from being fooled
616      * by out of date stuff in the environment.
617      */
618   found:
619     if ((p = getenv("TERMCAP")) != 0 && !_nc_is_abs_path(p)) {
620 	/* 'unsetenv("TERMCAP")' is not portable.
621 	 * The 'environ' array is better.
622 	 */
623 	int n;
624 	for (n = 0; environ[n] != 0; n++) {
625 	    if (!strncmp("TERMCAP=", environ[n], (size_t) 8)) {
626 		while ((environ[n] = environ[n + 1]) != 0) {
627 		    n++;
628 		}
629 		break;
630 	    }
631 	}
632     }
633 
634     /*
635      * ttype now contains a pointer to the type of the terminal.
636      * If the first character is '?', ask the user.
637      */
638     if (ttype[0] == '?') {
639 	if (ttype[1] != '\0')
640 	    ttype = askuser(ttype + 1);
641 	else
642 	    ttype = askuser(0);
643     }
644     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
645     while (setupterm((NCURSES_CONST char *) ttype, fd, &errret)
646 	   != OK) {
647 	if (errret == 0) {
648 	    (void) fprintf(stderr, "%s: unknown terminal type %s\n",
649 			   _nc_progname, ttype);
650 	    ttype = 0;
651 	} else {
652 	    (void) fprintf(stderr,
653 			   "%s: can't initialize terminal type %s (error %d)\n",
654 			   _nc_progname, ttype, errret);
655 	    ttype = 0;
656 	}
657 	ttype = askuser(ttype);
658     }
659 #if BROKEN_LINKER
660     tgetflag("am");		/* force lib_termcap.o to be linked for 'ospeed' */
661 #endif
662     return (ttype);
663 }
664 
665 /**************************************************************************
666  *
667  * Main sequence
668  *
669  **************************************************************************/
670 
671 /*
672  * Convert the obsolete argument forms into something that getopt can handle.
673  * This means that -e, -i and -k get default arguments supplied for them.
674  */
675 static void
676 obsolete(char **argv)
677 {
678     for (; *argv; ++argv) {
679 	char *parm = argv[0];
680 
681 	if (parm[0] == '-' && parm[1] == '\0') {
682 	    argv[0] = strdup("-q");
683 	    continue;
684 	}
685 
686 	if ((parm[0] != '-')
687 	    || (argv[1] && argv[1][0] != '-')
688 	    || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
689 	    || (parm[2] != '\0'))
690 	    continue;
691 	switch (argv[0][1]) {
692 	case 'e':
693 	    argv[0] = strdup("-e^H");
694 	    break;
695 	case 'i':
696 	    argv[0] = strdup("-i^C");
697 	    break;
698 	case 'k':
699 	    argv[0] = strdup("-k^U");
700 	    break;
701 	}
702     }
703 }
704 
705 static void
706 print_shell_commands(const char *ttype)
707 {
708     const char *p;
709     int len;
710     char *var;
711     char *leaf;
712     /*
713      * Figure out what shell we're using.  A hack, we look for an
714      * environmental variable SHELL ending in "csh".
715      */
716     if ((var = getenv("SHELL")) != 0
717 	&& ((len = (int) strlen(leaf = _nc_basename(var))) >= 3)
718 	&& !strcmp(leaf + len - 3, "csh"))
719 	p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
720     else
721 	p = "TERM=%s;\n";
722     (void) printf(p, ttype);
723 }
724 
725 static void
726 usage(void)
727 {
728 #define SKIP(s)			/* nothing */
729 #define KEEP(s) s "\n"
730     static const char msg[] =
731     {
732 	KEEP("")
733 	KEEP("Options:")
734 	SKIP("  -a arpanet  (obsolete)")
735 	KEEP("  -c          set control characters")
736 	SKIP("  -d dialup   (obsolete)")
737 	KEEP("  -e ch       erase character")
738 	KEEP("  -I          no initialization strings")
739 	KEEP("  -i ch       interrupt character")
740 	KEEP("  -k ch       kill character")
741 	KEEP("  -m mapping  map identifier to type")
742 	SKIP("  -p plugboard (obsolete)")
743 	KEEP("  -Q          do not output control key settings")
744 	KEEP("  -q          display term only, do no changes")
745 	KEEP("  -r          display term on stderr")
746 	SKIP("  -S          (obsolete)")
747 	KEEP("  -s          output TERM set command")
748 	KEEP("  -V          print curses-version")
749 	KEEP("  -w          set window-size")
750 	KEEP("")
751 	KEEP("If neither -c/-w are given, both are assumed.")
752     };
753 #undef KEEP
754 #undef SKIP
755     (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname);
756     fputs(msg, stderr);
757     ExitProgram(EXIT_FAILURE);
758     /* NOTREACHED */
759 }
760 
761 static char
762 arg_to_char(void)
763 {
764     return (char) ((optarg[0] == '^' && optarg[1] != '\0')
765 		   ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
766 		   : optarg[0]);
767 }
768 
769 int
770 main(int argc, char **argv)
771 {
772     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
773     const char *ttype;
774     int terasechar = -1;	/* new erase character */
775     int intrchar = -1;		/* new interrupt character */
776     int tkillchar = -1;		/* new kill character */
777     int my_fd;
778     bool opt_c = FALSE;		/* set control-chars */
779     bool opt_w = FALSE;		/* set window-size */
780     TTY mode, oldmode;
781 
782     _nc_progname = _nc_rootname(*argv);
783     obsolete(argv);
784     noinit = noset = quiet = Sflag = sflag = showterm = 0;
785     while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:p:qQrSsVw")) != -1) {
786 	switch (ch) {
787 	case 'c':		/* set control-chars */
788 	    opt_c = TRUE;
789 	    break;
790 	case 'a':		/* OBSOLETE: map identifier to type */
791 	    add_mapping("arpanet", optarg);
792 	    break;
793 	case 'd':		/* OBSOLETE: map identifier to type */
794 	    add_mapping("dialup", optarg);
795 	    break;
796 	case 'e':		/* erase character */
797 	    terasechar = arg_to_char();
798 	    break;
799 	case 'I':		/* no initialization strings */
800 	    noinit = 1;
801 	    break;
802 	case 'i':		/* interrupt character */
803 	    intrchar = arg_to_char();
804 	    break;
805 	case 'k':		/* kill character */
806 	    tkillchar = arg_to_char();
807 	    break;
808 	case 'm':		/* map identifier to type */
809 	    add_mapping(0, optarg);
810 	    break;
811 	case 'p':		/* OBSOLETE: map identifier to type */
812 	    add_mapping("plugboard", optarg);
813 	    break;
814 	case 'Q':		/* don't output control key settings */
815 	    quiet = 1;
816 	    break;
817 	case 'q':		/* display term only */
818 	    noset = 1;
819 	    break;
820 	case 'r':		/* display term on stderr */
821 	    showterm = 1;
822 	    break;
823 	case 'S':		/* OBSOLETE: output TERM & TERMCAP */
824 	    Sflag = 1;
825 	    break;
826 	case 's':		/* output TERM set command */
827 	    sflag = 1;
828 	    break;
829 	case 'V':		/* print curses-version */
830 	    puts(curses_version());
831 	    ExitProgram(EXIT_SUCCESS);
832 	case 'w':		/* set window-size */
833 	    opt_w = TRUE;
834 	    break;
835 	case '?':
836 	default:
837 	    usage();
838 	}
839     }
840 
841     argc -= optind;
842     argv += optind;
843 
844     if (argc > 1)
845 	usage();
846 
847     if (!opt_c && !opt_w)
848 	opt_c = opt_w = TRUE;
849 
850     my_fd = save_tty_settings(&mode, TRUE);
851     oldmode = mode;
852 #ifdef TERMIOS
853     ospeed = (NCURSES_OSPEED) cfgetospeed(&mode);
854 #elif defined(EXP_WIN32_DRIVER)
855     ospeed = 0;
856 #else
857     ospeed = (NCURSES_OSPEED) mode.sg_ospeed;
858 #endif
859 
860     if (same_program(_nc_progname, PROG_RESET)) {
861 	reset_start(stderr, TRUE, FALSE);
862 	reset_tty_settings(my_fd, &mode, noset);
863     } else {
864 	reset_start(stderr, FALSE, TRUE);
865     }
866 
867     ttype = get_termcap_entry(my_fd, *argv);
868 
869     if (!noset) {
870 #if HAVE_SIZECHANGE
871 	if (opt_w) {
872 	    NCURSES_INT2 my_rows = lines;
873 	    NCURSES_INT2 my_cols = columns;
874 	    set_window_size(my_fd, &my_rows, &my_cols);
875 	    lines = my_rows;
876 	    columns = my_cols;
877 	}
878 #endif
879 	if (opt_c) {
880 	    set_control_chars(&mode, terasechar, intrchar, tkillchar);
881 	    set_conversions(&mode);
882 
883 	    if (!noinit) {
884 		if (send_init_strings(my_fd, &oldmode)) {
885 		    const char *name;
886 
887 		    (void) putc('\r', stderr);
888 		    (void) fflush(stderr);
889 		    if (IsRealTty(my_fd, name)) {
890 			(void) napms(1000);	/* Settle the terminal. */
891 		    }
892 		}
893 	    }
894 
895 	    update_tty_settings(&oldmode, &mode);
896 	}
897     }
898 
899     if (noset) {
900 	(void) printf("%s\n", ttype);
901     } else {
902 	if (showterm)
903 	    (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
904 	/*
905 	 * If erase, kill and interrupt characters could have been
906 	 * modified and not -Q, display the changes.
907 	 */
908 	if (!quiet) {
909 	    print_tty_chars(&oldmode, &mode);
910 	}
911     }
912 
913     if (Sflag)
914 	err("The -S option is not supported under terminfo.");
915 
916     if (sflag) {
917 	print_shell_commands(ttype);
918     }
919 
920     ExitProgram(EXIT_SUCCESS);
921 }
922