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