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