xref: /freebsd/contrib/ncurses/progs/tset.c (revision 34b867ca30479cec104fd069178df294f8ea35f1)
1 /****************************************************************************
2  * Copyright 2020 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 && HAVE_TTYENT_H
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.125 2020/09/05 22:54:47 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 void exit_error(void) GCC_NORETURN;
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 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 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 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     char *p;
171 
172     /* We can get recalled; if so, don't continue uselessly. */
173     clearerr(stdin);
174     if (feof(stdin) || ferror(stdin)) {
175 	(void) fprintf(stderr, "\n");
176 	exit_error();
177 	/* NOTREACHED */
178     }
179     for (;;) {
180 	if (dflt)
181 	    (void) fprintf(stderr, "Terminal type? [%s] ", dflt);
182 	else
183 	    (void) fprintf(stderr, "Terminal type? ");
184 	(void) fflush(stderr);
185 
186 	if (fgets(answer, sizeof(answer), stdin) == 0) {
187 	    if (dflt == 0) {
188 		exit_error();
189 		/* NOTREACHED */
190 	    }
191 	    return (dflt);
192 	}
193 
194 	if ((p = strchr(answer, '\n')) != 0)
195 	    *p = '\0';
196 	if (answer[0])
197 	    return (answer);
198 	if (dflt != 0)
199 	    return (dflt);
200     }
201 }
202 
203 /**************************************************************************
204  *
205  * Mapping logic begins here
206  *
207  **************************************************************************/
208 
209 /* Baud rate conditionals for mapping. */
210 #define	GT		0x01
211 #define	EQ		0x02
212 #define	LT		0x04
213 #define	NOT		0x08
214 #define	GE		(GT | EQ)
215 #define	LE		(LT | EQ)
216 
217 typedef struct map {
218     struct map *next;		/* Linked list of maps. */
219     const char *porttype;	/* Port type, or "" for any. */
220     const char *type;		/* Terminal type to select. */
221     int conditional;		/* Baud rate conditionals bitmask. */
222     int speed;			/* Baud rate to compare against. */
223 } MAP;
224 
225 static MAP *cur, *maplist;
226 
227 #define DATA(name,value) { { name }, value }
228 
229 typedef struct speeds {
230     const char string[8];
231     int speed;
232 } SPEEDS;
233 
234 #if defined(EXP_WIN32_DRIVER)
235 static const SPEEDS speeds[] =
236 {
237     {"0", 0}
238 };
239 #else
240 static const SPEEDS speeds[] =
241 {
242     DATA("0", B0),
243     DATA("50", B50),
244     DATA("75", B75),
245     DATA("110", B110),
246     DATA("134", B134),
247     DATA("134.5", B134),
248     DATA("150", B150),
249     DATA("200", B200),
250     DATA("300", B300),
251     DATA("600", B600),
252     DATA("1200", B1200),
253     DATA("1800", B1800),
254     DATA("2400", B2400),
255     DATA("4800", B4800),
256     DATA("9600", B9600),
257     /* sgttyb may define up to this point */
258 #ifdef B19200
259     DATA("19200", B19200),
260 #endif
261 #ifdef B38400
262     DATA("38400", B38400),
263 #endif
264 #ifdef B19200
265     DATA("19200", B19200),
266 #endif
267 #ifdef B38400
268     DATA("38400", B38400),
269 #endif
270 #ifdef B19200
271     DATA("19200", B19200),
272 #else
273 #ifdef EXTA
274     DATA("19200", EXTA),
275 #endif
276 #endif
277 #ifdef B38400
278     DATA("38400", B38400),
279 #else
280 #ifdef EXTB
281     DATA("38400", EXTB),
282 #endif
283 #endif
284 #ifdef B57600
285     DATA("57600", B57600),
286 #endif
287 #ifdef B76800
288     DATA("76800", B57600),
289 #endif
290 #ifdef B115200
291     DATA("115200", B115200),
292 #endif
293 #ifdef B153600
294     DATA("153600", B153600),
295 #endif
296 #ifdef B230400
297     DATA("230400", B230400),
298 #endif
299 #ifdef B307200
300     DATA("307200", B307200),
301 #endif
302 #ifdef B460800
303     DATA("460800", B460800),
304 #endif
305 #ifdef B500000
306     DATA("500000", B500000),
307 #endif
308 #ifdef B576000
309     DATA("576000", B576000),
310 #endif
311 #ifdef B921600
312     DATA("921600", B921600),
313 #endif
314 #ifdef B1000000
315     DATA("1000000", B1000000),
316 #endif
317 #ifdef B1152000
318     DATA("1152000", B1152000),
319 #endif
320 #ifdef B1500000
321     DATA("1500000", B1500000),
322 #endif
323 #ifdef B2000000
324     DATA("2000000", B2000000),
325 #endif
326 #ifdef B2500000
327     DATA("2500000", B2500000),
328 #endif
329 #ifdef B3000000
330     DATA("3000000", B3000000),
331 #endif
332 #ifdef B3500000
333     DATA("3500000", B3500000),
334 #endif
335 #ifdef B4000000
336     DATA("4000000", B4000000),
337 #endif
338 };
339 #undef DATA
340 #endif
341 
342 static int
343 tbaudrate(char *rate)
344 {
345     const SPEEDS *sp = 0;
346     size_t n;
347 
348     /* The baudrate number can be preceded by a 'B', which is ignored. */
349     if (*rate == 'B')
350 	++rate;
351 
352     for (n = 0; n < SIZEOF(speeds); ++n) {
353 	if (n > 0 && (speeds[n].speed <= speeds[n - 1].speed)) {
354 	    /* if the speeds are not increasing, likely a numeric overflow */
355 	    break;
356 	}
357 	if (!CaselessCmp(rate, speeds[n].string)) {
358 	    sp = speeds + n;
359 	    break;
360 	}
361     }
362     if (sp == 0)
363 	err("unknown baud rate %s", rate);
364     return (sp->speed);
365 }
366 
367 /*
368  * Syntax for -m:
369  * [port-type][test baudrate]:terminal-type
370  * The baud rate tests are: >, <, @, =, !
371  */
372 static void
373 add_mapping(const char *port, char *arg)
374 {
375     MAP *mapp;
376     char *copy, *p;
377     const char *termp;
378     char *base = 0;
379 
380     copy = strdup(arg);
381     mapp = typeMalloc(MAP, 1);
382     if (copy == 0 || mapp == 0)
383 	failed("malloc");
384 
385     assert(copy != 0);
386     assert(mapp != 0);
387 
388     mapp->next = 0;
389     if (maplist == 0)
390 	cur = maplist = mapp;
391     else {
392 	cur->next = mapp;
393 	cur = mapp;
394     }
395 
396     mapp->porttype = arg;
397     mapp->conditional = 0;
398 
399     arg = strpbrk(arg, "><@=!:");
400 
401     if (arg == 0) {		/* [?]term */
402 	mapp->type = mapp->porttype;
403 	mapp->porttype = 0;
404 	goto done;
405     }
406 
407     if (arg == mapp->porttype)	/* [><@=! baud]:term */
408 	termp = mapp->porttype = 0;
409     else
410 	termp = base = arg;
411 
412     for (;; ++arg) {		/* Optional conditionals. */
413 	switch (*arg) {
414 	case '<':
415 	    if (mapp->conditional & GT)
416 		goto badmopt;
417 	    mapp->conditional |= LT;
418 	    break;
419 	case '>':
420 	    if (mapp->conditional & LT)
421 		goto badmopt;
422 	    mapp->conditional |= GT;
423 	    break;
424 	case '@':
425 	case '=':		/* Not documented. */
426 	    mapp->conditional |= EQ;
427 	    break;
428 	case '!':
429 	    mapp->conditional |= NOT;
430 	    break;
431 	default:
432 	    goto next;
433 	}
434     }
435 
436   next:
437     if (*arg == ':') {
438 	if (mapp->conditional)
439 	    goto badmopt;
440 	++arg;
441     } else {			/* Optional baudrate. */
442 	arg = strchr(p = arg, ':');
443 	if (arg == 0)
444 	    goto badmopt;
445 	*arg++ = '\0';
446 	mapp->speed = tbaudrate(p);
447     }
448 
449     mapp->type = arg;
450 
451     /* Terminate porttype, if specified. */
452     if (termp != 0)
453 	*base = '\0';
454 
455     /* If a NOT conditional, reverse the test. */
456     if (mapp->conditional & NOT)
457 	mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
458 
459     /* If user specified a port with an option flag, set it. */
460   done:
461     if (port) {
462 	if (mapp->porttype) {
463 	  badmopt:
464 	    err("illegal -m option format: %s", copy);
465 	}
466 	mapp->porttype = port;
467     }
468     free(copy);
469 #ifdef MAPDEBUG
470     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
471     (void) printf("type: %s\n", mapp->type);
472     (void) printf("conditional: ");
473     p = "";
474     if (mapp->conditional & GT) {
475 	(void) printf("GT");
476 	p = "/";
477     }
478     if (mapp->conditional & EQ) {
479 	(void) printf("%sEQ", p);
480 	p = "/";
481     }
482     if (mapp->conditional & LT)
483 	(void) printf("%sLT", p);
484     (void) printf("\nspeed: %d\n", mapp->speed);
485 #endif
486 }
487 
488 /*
489  * Return the type of terminal to use for a port of type 'type', as specified
490  * by the first applicable mapping in 'map'.  If no mappings apply, return
491  * 'type'.
492  */
493 static const char *
494 mapped(const char *type)
495 {
496     MAP *mapp;
497     int match;
498 
499     for (mapp = maplist; mapp; mapp = mapp->next)
500 	if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
501 	    switch (mapp->conditional) {
502 	    case 0:		/* No test specified. */
503 		match = TRUE;
504 		break;
505 	    case EQ:
506 		match = ((int) ospeed == mapp->speed);
507 		break;
508 	    case GE:
509 		match = ((int) ospeed >= mapp->speed);
510 		break;
511 	    case GT:
512 		match = ((int) ospeed > mapp->speed);
513 		break;
514 	    case LE:
515 		match = ((int) ospeed <= mapp->speed);
516 		break;
517 	    case LT:
518 		match = ((int) ospeed < mapp->speed);
519 		break;
520 	    default:
521 		match = FALSE;
522 	    }
523 	    if (match)
524 		return (mapp->type);
525 	}
526     /* No match found; return given type. */
527     return (type);
528 }
529 
530 /**************************************************************************
531  *
532  * Entry fetching
533  *
534  **************************************************************************/
535 
536 /*
537  * Figure out what kind of terminal we're dealing with, and then read in
538  * its termcap entry.
539  */
540 static const char *
541 get_termcap_entry(int fd, char *userarg)
542 {
543     int errret;
544     char *p;
545     const char *ttype;
546 #if HAVE_GETTTYNAM
547     struct ttyent *t;
548 #else
549     FILE *fp;
550 #endif
551     char *ttypath;
552 
553     (void) fd;
554 
555     if (userarg) {
556 	ttype = userarg;
557 	goto found;
558     }
559 
560     /* Try the environment. */
561     if ((ttype = getenv("TERM")) != 0)
562 	goto map;
563 
564     if ((ttypath = ttyname(fd)) != 0) {
565 	p = _nc_basename(ttypath);
566 #if HAVE_GETTTYNAM
567 	/*
568 	 * We have the 4.3BSD library call getttynam(3); that means
569 	 * there's an /etc/ttys to look up device-to-type mappings in.
570 	 * Try ttyname(3); check for dialup or other mapping.
571 	 */
572 	if ((t = getttynam(p))) {
573 	    ttype = t->ty_type;
574 	    goto map;
575 	}
576 #else
577 	if ((fp = fopen("/etc/ttytype", "r")) != 0
578 	    || (fp = fopen("/etc/ttys", "r")) != 0) {
579 	    char buffer[BUFSIZ];
580 	    char *s, *t, *d;
581 
582 	    while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
583 		for (s = buffer, t = d = 0; *s; s++) {
584 		    if (isspace(UChar(*s)))
585 			*s = '\0';
586 		    else if (t == 0)
587 			t = s;
588 		    else if (d == 0 && s != buffer && s[-1] == '\0')
589 			d = s;
590 		}
591 		if (t != 0 && d != 0 && !strcmp(d, p)) {
592 		    ttype = strdup(t);
593 		    fclose(fp);
594 		    goto map;
595 		}
596 	    }
597 	    fclose(fp);
598 	}
599 #endif /* HAVE_GETTTYNAM */
600     }
601 
602     /* If still undefined, use "unknown". */
603     ttype = "unknown";
604 
605   map:ttype = mapped(ttype);
606 
607     /*
608      * If not a path, remove TERMCAP from the environment so we get a
609      * real entry from /etc/termcap.  This prevents us from being fooled
610      * by out of date stuff in the environment.
611      */
612   found:
613     if ((p = getenv("TERMCAP")) != 0 && !_nc_is_abs_path(p)) {
614 	/* 'unsetenv("TERMCAP")' is not portable.
615 	 * The 'environ' array is better.
616 	 */
617 	int n;
618 	for (n = 0; environ[n] != 0; n++) {
619 	    if (!strncmp("TERMCAP=", environ[n], (size_t) 8)) {
620 		while ((environ[n] = environ[n + 1]) != 0) {
621 		    n++;
622 		}
623 		break;
624 	    }
625 	}
626     }
627 
628     /*
629      * ttype now contains a pointer to the type of the terminal.
630      * If the first character is '?', ask the user.
631      */
632     if (ttype[0] == '?') {
633 	if (ttype[1] != '\0')
634 	    ttype = askuser(ttype + 1);
635 	else
636 	    ttype = askuser(0);
637     }
638     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
639     while (setupterm((NCURSES_CONST char *) ttype, fd, &errret)
640 	   != OK) {
641 	if (errret == 0) {
642 	    (void) fprintf(stderr, "%s: unknown terminal type %s\n",
643 			   _nc_progname, ttype);
644 	    ttype = 0;
645 	} else {
646 	    (void) fprintf(stderr,
647 			   "%s: can't initialize terminal type %s (error %d)\n",
648 			   _nc_progname, ttype, errret);
649 	    ttype = 0;
650 	}
651 	ttype = askuser(ttype);
652     }
653 #if BROKEN_LINKER
654     tgetflag("am");		/* force lib_termcap.o to be linked for 'ospeed' */
655 #endif
656     return (ttype);
657 }
658 
659 /**************************************************************************
660  *
661  * Main sequence
662  *
663  **************************************************************************/
664 
665 /*
666  * Convert the obsolete argument forms into something that getopt can handle.
667  * This means that -e, -i and -k get default arguments supplied for them.
668  */
669 static void
670 obsolete(char **argv)
671 {
672     for (; *argv; ++argv) {
673 	char *parm = argv[0];
674 
675 	if (parm[0] == '-' && parm[1] == '\0') {
676 	    argv[0] = strdup("-q");
677 	    continue;
678 	}
679 
680 	if ((parm[0] != '-')
681 	    || (argv[1] && argv[1][0] != '-')
682 	    || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
683 	    || (parm[2] != '\0'))
684 	    continue;
685 	switch (argv[0][1]) {
686 	case 'e':
687 	    argv[0] = strdup("-e^H");
688 	    break;
689 	case 'i':
690 	    argv[0] = strdup("-i^C");
691 	    break;
692 	case 'k':
693 	    argv[0] = strdup("-k^U");
694 	    break;
695 	}
696     }
697 }
698 
699 static void
700 print_shell_commands(const char *ttype)
701 {
702     const char *p;
703     int len;
704     char *var;
705     char *leaf;
706     /*
707      * Figure out what shell we're using.  A hack, we look for an
708      * environmental variable SHELL ending in "csh".
709      */
710     if ((var = getenv("SHELL")) != 0
711 	&& ((len = (int) strlen(leaf = _nc_basename(var))) >= 3)
712 	&& !strcmp(leaf + len - 3, "csh"))
713 	p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
714     else
715 	p = "TERM=%s;\n";
716     (void) printf(p, ttype);
717 }
718 
719 static void
720 usage(void)
721 {
722 #define SKIP(s)			/* nothing */
723 #define KEEP(s) s "\n"
724     static const char msg[] =
725     {
726 	KEEP("")
727 	KEEP("Options:")
728 	SKIP("  -a arpanet  (obsolete)")
729 	KEEP("  -c          set control characters")
730 	SKIP("  -d dialup   (obsolete)")
731 	KEEP("  -e ch       erase character")
732 	KEEP("  -I          no initialization strings")
733 	KEEP("  -i ch       interrupt character")
734 	KEEP("  -k ch       kill character")
735 	KEEP("  -m mapping  map identifier to type")
736 	SKIP("  -p plugboard (obsolete)")
737 	KEEP("  -Q          do not output control key settings")
738 	KEEP("  -q          display term only, do no changes")
739 	KEEP("  -r          display term on stderr")
740 	SKIP("  -S          (obsolete)")
741 	KEEP("  -s          output TERM set command")
742 	KEEP("  -V          print curses-version")
743 	KEEP("  -w          set window-size")
744 	KEEP("")
745 	KEEP("If neither -c/-w are given, both are assumed.")
746     };
747 #undef KEEP
748 #undef SKIP
749     (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname);
750     fputs(msg, stderr);
751     ExitProgram(EXIT_FAILURE);
752     /* NOTREACHED */
753 }
754 
755 static char
756 arg_to_char(void)
757 {
758     return (char) ((optarg[0] == '^' && optarg[1] != '\0')
759 		   ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
760 		   : optarg[0]);
761 }
762 
763 int
764 main(int argc, char **argv)
765 {
766     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
767     const char *ttype;
768     int terasechar = -1;	/* new erase character */
769     int intrchar = -1;		/* new interrupt character */
770     int tkillchar = -1;		/* new kill character */
771     int my_fd;
772     bool opt_c = FALSE;		/* set control-chars */
773     bool opt_w = FALSE;		/* set window-size */
774     TTY mode, oldmode;
775 
776     my_fd = STDERR_FILENO;
777     obsolete(argv);
778     noinit = noset = quiet = Sflag = sflag = showterm = 0;
779     while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:p:qQrSsVw")) != -1) {
780 	switch (ch) {
781 	case 'c':		/* set control-chars */
782 	    opt_c = TRUE;
783 	    break;
784 	case 'a':		/* OBSOLETE: map identifier to type */
785 	    add_mapping("arpanet", optarg);
786 	    break;
787 	case 'd':		/* OBSOLETE: map identifier to type */
788 	    add_mapping("dialup", optarg);
789 	    break;
790 	case 'e':		/* erase character */
791 	    terasechar = arg_to_char();
792 	    break;
793 	case 'I':		/* no initialization strings */
794 	    noinit = 1;
795 	    break;
796 	case 'i':		/* interrupt character */
797 	    intrchar = arg_to_char();
798 	    break;
799 	case 'k':		/* kill character */
800 	    tkillchar = arg_to_char();
801 	    break;
802 	case 'm':		/* map identifier to type */
803 	    add_mapping(0, optarg);
804 	    break;
805 	case 'p':		/* OBSOLETE: map identifier to type */
806 	    add_mapping("plugboard", optarg);
807 	    break;
808 	case 'Q':		/* don't output control key settings */
809 	    quiet = 1;
810 	    break;
811 	case 'q':		/* display term only */
812 	    noset = 1;
813 	    break;
814 	case 'r':		/* display term on stderr */
815 	    showterm = 1;
816 	    break;
817 	case 'S':		/* OBSOLETE: output TERM & TERMCAP */
818 	    Sflag = 1;
819 	    break;
820 	case 's':		/* output TERM set command */
821 	    sflag = 1;
822 	    break;
823 	case 'V':		/* print curses-version */
824 	    puts(curses_version());
825 	    ExitProgram(EXIT_SUCCESS);
826 	case 'w':		/* set window-size */
827 	    opt_w = TRUE;
828 	    break;
829 	case '?':
830 	default:
831 	    usage();
832 	}
833     }
834 
835     _nc_progname = _nc_rootname(*argv);
836     argc -= optind;
837     argv += optind;
838 
839     if (argc > 1)
840 	usage();
841 
842     if (!opt_c && !opt_w)
843 	opt_c = opt_w = TRUE;
844 
845     my_fd = save_tty_settings(&mode, TRUE);
846     oldmode = mode;
847 #ifdef TERMIOS
848     ospeed = (NCURSES_OSPEED) cfgetospeed(&mode);
849 #elif defined(EXP_WIN32_DRIVER)
850     ospeed = 0;
851 #else
852     ospeed = (NCURSES_OSPEED) mode.sg_ospeed;
853 #endif
854 
855     if (same_program(_nc_progname, PROG_RESET)) {
856 	reset_start(stderr, TRUE, FALSE);
857 	reset_tty_settings(my_fd, &mode);
858     } else {
859 	reset_start(stderr, FALSE, TRUE);
860     }
861 
862     ttype = get_termcap_entry(my_fd, *argv);
863 
864     if (!noset) {
865 #if HAVE_SIZECHANGE
866 	if (opt_w) {
867 	    set_window_size(my_fd, &lines, &columns);
868 	}
869 #endif
870 	if (opt_c) {
871 	    set_control_chars(&mode, terasechar, intrchar, tkillchar);
872 	    set_conversions(&mode);
873 
874 	    if (!noinit) {
875 		if (send_init_strings(my_fd, &oldmode)) {
876 		    (void) putc('\r', stderr);
877 		    (void) fflush(stderr);
878 		    (void) napms(1000);		/* Settle the terminal. */
879 		}
880 	    }
881 
882 	    update_tty_settings(&oldmode, &mode);
883 	}
884     }
885 
886     if (noset) {
887 	(void) printf("%s\n", ttype);
888     } else {
889 	if (showterm)
890 	    (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
891 	/*
892 	 * If erase, kill and interrupt characters could have been
893 	 * modified and not -Q, display the changes.
894 	 */
895 	if (!quiet) {
896 	    print_tty_chars(&oldmode, &mode);
897 	}
898     }
899 
900     if (Sflag)
901 	err("The -S option is not supported under terminfo.");
902 
903     if (sflag) {
904 	print_shell_commands(ttype);
905     }
906 
907     ExitProgram(EXIT_SUCCESS);
908 }
909