xref: /freebsd/contrib/ncurses/progs/tset.c (revision 9ecd54f24fe9fa373e07c9fd7c052deb2188f545)
1 /****************************************************************************
2  * Copyright (c) 1998-2012,2013 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey                        1996-on                 *
33  ****************************************************************************/
34 
35 /*
36  * Notes:
37  * The initial adaptation from 4.4BSD Lite sources in September 1995 used 686
38  * lines from that version, and made changes/additions for 150 lines.  There
39  * was no reformatting, so with/without ignoring whitespace, the amount of
40  * change is the same.
41  *
42  * Comparing with current (2009) source, excluding this comment:
43  * a) 209 lines match identically to the 4.4BSD Lite sources, with 771 lines
44  *    changed/added.
45  * a) Ignoring whitespace, the current version still uses 516 lines from the
46  *    4.4BSD Lite sources, with 402 lines changed/added.
47  *
48  * Raymond's original comment on this follows...
49  */
50 
51 /*
52  * tset.c - terminal initialization utility
53  *
54  * This code was mostly swiped from 4.4BSD tset, with some obsolescent
55  * cruft removed and substantial portions rewritten.  A Regents of the
56  * University of California copyright applies to some portions of the
57  * code, and is reproduced below:
58  */
59 /*-
60  * Copyright (c) 1980, 1991, 1993
61  *	The Regents of the University of California.  All rights reserved.
62  *
63  * Redistribution and use in source and binary forms, with or without
64  * modification, are permitted provided that the following conditions
65  * are met:
66  * 1. Redistributions of source code must retain the above copyright
67  *    notice, this list of conditions and the following disclaimer.
68  * 2. Redistributions in binary form must reproduce the above copyright
69  *    notice, this list of conditions and the following disclaimer in the
70  *    documentation and/or other materials provided with the distribution.
71  * 3. Neither the name of the University nor the names of its contributors
72  *    may be used to endorse or promote products derived from this software
73  *    without specific prior written permission.
74  *
75  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
76  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
77  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
78  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
79  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
80  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
81  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
82  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
83  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
84  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
85  * SUCH DAMAGE.
86  */
87 
88 #define USE_LIBTINFO
89 #define __INTERNAL_CAPS_VISIBLE	/* we need to see has_hardware_tabs */
90 #include <progs.priv.h>
91 
92 #include <errno.h>
93 #include <stdio.h>
94 #include <termcap.h>
95 #include <fcntl.h>
96 
97 #if HAVE_GETTTYNAM && HAVE_TTYENT_H
98 #include <ttyent.h>
99 #endif
100 #ifdef NeXT
101 char *ttyname(int fd);
102 #endif
103 
104 #if HAVE_SIZECHANGE
105 # if !defined(sun) || !TERMIOS
106 #  if HAVE_SYS_IOCTL_H
107 #   include <sys/ioctl.h>
108 #  endif
109 # endif
110 #endif
111 
112 #if NEED_PTEM_H
113 /* they neglected to define struct winsize in termios.h -- it's only
114    in termio.h	*/
115 #include <sys/stream.h>
116 #include <sys/ptem.h>
117 #endif
118 
119 #include <dump_entry.h>
120 #include <transform.h>
121 
122 MODULE_ID("$Id: tset.c,v 1.93 2013/12/15 01:05:56 tom Exp $")
123 
124 /*
125  * SCO defines TIOCGSIZE and the corresponding struct.  Other systems (SunOS,
126  * Solaris, IRIX) define TIOCGWINSZ and struct winsize.
127  */
128 #ifdef TIOCGSIZE
129 # define IOCTL_GET_WINSIZE TIOCGSIZE
130 # define IOCTL_SET_WINSIZE TIOCSSIZE
131 # define STRUCT_WINSIZE struct ttysize
132 # define WINSIZE_ROWS(n) n.ts_lines
133 # define WINSIZE_COLS(n) n.ts_cols
134 #else
135 # ifdef TIOCGWINSZ
136 #  define IOCTL_GET_WINSIZE TIOCGWINSZ
137 #  define IOCTL_SET_WINSIZE TIOCSWINSZ
138 #  define STRUCT_WINSIZE struct winsize
139 #  define WINSIZE_ROWS(n) n.ws_row
140 #  define WINSIZE_COLS(n) n.ws_col
141 # endif
142 #endif
143 
144 #ifndef environ
145 extern char **environ;
146 #endif
147 
148 #undef CTRL
149 #define CTRL(x)	((x) & 0x1f)
150 
151 static void failed(const char *) GCC_NORETURN;
152 static void exit_error(void) GCC_NORETURN;
153 static void err(const char *,...) GCC_NORETURN;
154 
155 const char *_nc_progname = "tset";
156 
157 static TTY mode, oldmode, original;
158 
159 static bool opt_c;		/* set control-chars */
160 static bool opt_w;		/* set window-size */
161 
162 static bool can_restore = FALSE;
163 static bool isreset = FALSE;	/* invoked as reset */
164 static int terasechar = -1;	/* new erase character */
165 static int intrchar = -1;	/* new interrupt character */
166 static int tkillchar = -1;	/* new kill character */
167 
168 #if HAVE_SIZECHANGE
169 static int tlines, tcolumns;	/* window size */
170 #endif
171 
172 #define LOWERCASE(c) ((isalpha(UChar(c)) && isupper(UChar(c))) ? tolower(UChar(c)) : (c))
173 
174 static int
175 CaselessCmp(const char *a, const char *b)
176 {				/* strcasecmp isn't portable */
177     while (*a && *b) {
178 	int cmp = LOWERCASE(*a) - LOWERCASE(*b);
179 	if (cmp != 0)
180 	    break;
181 	a++, b++;
182     }
183     return LOWERCASE(*a) - LOWERCASE(*b);
184 }
185 
186 static void
187 exit_error(void)
188 {
189     if (can_restore)
190 	SET_TTY(STDERR_FILENO, &original);
191     (void) fprintf(stderr, "\n");
192     fflush(stderr);
193     ExitProgram(EXIT_FAILURE);
194     /* NOTREACHED */
195 }
196 
197 static void
198 err(const char *fmt,...)
199 {
200     va_list ap;
201     va_start(ap, fmt);
202     (void) fprintf(stderr, "%s: ", _nc_progname);
203     (void) vfprintf(stderr, fmt, ap);
204     va_end(ap);
205     exit_error();
206     /* NOTREACHED */
207 }
208 
209 static void
210 failed(const char *msg)
211 {
212     char temp[BUFSIZ];
213     size_t len = strlen(_nc_progname) + 2;
214 
215     if ((int) len < (int) sizeof(temp) - 12) {
216 	_nc_STRCPY(temp, _nc_progname, sizeof(temp));
217 	_nc_STRCAT(temp, ": ", sizeof(temp));
218     } else {
219 	_nc_STRCPY(temp, "tset: ", sizeof(temp));
220     }
221     perror(strncat(temp, msg, sizeof(temp) - strlen(temp) - 2));
222     exit_error();
223     /* NOTREACHED */
224 }
225 
226 static void
227 cat(char *file)
228 {
229     FILE *fp;
230     size_t nr;
231     char buf[BUFSIZ];
232 
233     if ((fp = fopen(file, "r")) == 0)
234 	failed(file);
235 
236     while ((nr = fread(buf, sizeof(char), sizeof(buf), fp)) != 0)
237 	if (fwrite(buf, sizeof(char), nr, stderr) != nr)
238 	      failed("write to stderr");
239     fclose(fp);
240 }
241 
242 static int
243 outc(int c)
244 {
245     return putc(c, stderr);
246 }
247 
248 /* Prompt the user for a terminal type. */
249 static const char *
250 askuser(const char *dflt)
251 {
252     static char answer[256];
253     char *p;
254 
255     /* We can get recalled; if so, don't continue uselessly. */
256     clearerr(stdin);
257     if (feof(stdin) || ferror(stdin)) {
258 	(void) fprintf(stderr, "\n");
259 	exit_error();
260 	/* NOTREACHED */
261     }
262     for (;;) {
263 	if (dflt)
264 	    (void) fprintf(stderr, "Terminal type? [%s] ", dflt);
265 	else
266 	    (void) fprintf(stderr, "Terminal type? ");
267 	(void) fflush(stderr);
268 
269 	if (fgets(answer, sizeof(answer), stdin) == 0) {
270 	    if (dflt == 0) {
271 		exit_error();
272 		/* NOTREACHED */
273 	    }
274 	    return (dflt);
275 	}
276 
277 	if ((p = strchr(answer, '\n')) != 0)
278 	    *p = '\0';
279 	if (answer[0])
280 	    return (answer);
281 	if (dflt != 0)
282 	    return (dflt);
283     }
284 }
285 
286 /**************************************************************************
287  *
288  * Mapping logic begins here
289  *
290  **************************************************************************/
291 
292 /* Baud rate conditionals for mapping. */
293 #define	GT		0x01
294 #define	EQ		0x02
295 #define	LT		0x04
296 #define	NOT		0x08
297 #define	GE		(GT | EQ)
298 #define	LE		(LT | EQ)
299 
300 typedef struct map {
301     struct map *next;		/* Linked list of maps. */
302     const char *porttype;	/* Port type, or "" for any. */
303     const char *type;		/* Terminal type to select. */
304     int conditional;		/* Baud rate conditionals bitmask. */
305     int speed;			/* Baud rate to compare against. */
306 } MAP;
307 
308 static MAP *cur, *maplist;
309 
310 typedef struct speeds {
311     const char *string;
312     int speed;
313 } SPEEDS;
314 
315 static const SPEEDS speeds[] =
316 {
317     {"0", B0},
318     {"50", B50},
319     {"75", B75},
320     {"110", B110},
321     {"134", B134},
322     {"134.5", B134},
323     {"150", B150},
324     {"200", B200},
325     {"300", B300},
326     {"600", B600},
327     {"1200", B1200},
328     {"1800", B1800},
329     {"2400", B2400},
330     {"4800", B4800},
331     {"9600", B9600},
332     /* sgttyb may define up to this point */
333 #ifdef B19200
334     {"19200", B19200},
335 #endif
336 #ifdef B38400
337     {"38400", B38400},
338 #endif
339 #ifdef B19200
340     {"19200", B19200},
341 #endif
342 #ifdef B38400
343     {"38400", B38400},
344 #endif
345 #ifdef B19200
346     {"19200", B19200},
347 #else
348 #ifdef EXTA
349     {"19200", EXTA},
350 #endif
351 #endif
352 #ifdef B38400
353     {"38400", B38400},
354 #else
355 #ifdef EXTB
356     {"38400", EXTB},
357 #endif
358 #endif
359 #ifdef B57600
360     {"57600", B57600},
361 #endif
362 #ifdef B115200
363     {"115200", B115200},
364 #endif
365 #ifdef B230400
366     {"230400", B230400},
367 #endif
368 #ifdef B460800
369     {"460800", B460800},
370 #endif
371     {(char *) 0, 0}
372 };
373 
374 static int
375 tbaudrate(char *rate)
376 {
377     const SPEEDS *sp;
378     int found = FALSE;
379 
380     /* The baudrate number can be preceded by a 'B', which is ignored. */
381     if (*rate == 'B')
382 	++rate;
383 
384     for (sp = speeds; sp->string; ++sp) {
385 	if (!CaselessCmp(rate, sp->string)) {
386 	    found = TRUE;
387 	    break;
388 	}
389     }
390     if (!found)
391 	err("unknown baud rate %s", rate);
392     return (sp->speed);
393 }
394 
395 /*
396  * Syntax for -m:
397  * [port-type][test baudrate]:terminal-type
398  * The baud rate tests are: >, <, @, =, !
399  */
400 static void
401 add_mapping(const char *port, char *arg)
402 {
403     MAP *mapp;
404     char *copy, *p;
405     const char *termp;
406     char *base = 0;
407 
408     copy = strdup(arg);
409     mapp = typeMalloc(MAP, 1);
410     if (copy == 0 || mapp == 0)
411 	failed("malloc");
412 
413     assert(copy != 0);
414     assert(mapp != 0);
415 
416     mapp->next = 0;
417     if (maplist == 0)
418 	cur = maplist = mapp;
419     else {
420 	cur->next = mapp;
421 	cur = mapp;
422     }
423 
424     mapp->porttype = arg;
425     mapp->conditional = 0;
426 
427     arg = strpbrk(arg, "><@=!:");
428 
429     if (arg == 0) {		/* [?]term */
430 	mapp->type = mapp->porttype;
431 	mapp->porttype = 0;
432 	goto done;
433     }
434 
435     if (arg == mapp->porttype)	/* [><@=! baud]:term */
436 	termp = mapp->porttype = 0;
437     else
438 	termp = base = arg;
439 
440     for (;; ++arg) {		/* Optional conditionals. */
441 	switch (*arg) {
442 	case '<':
443 	    if (mapp->conditional & GT)
444 		goto badmopt;
445 	    mapp->conditional |= LT;
446 	    break;
447 	case '>':
448 	    if (mapp->conditional & LT)
449 		goto badmopt;
450 	    mapp->conditional |= GT;
451 	    break;
452 	case '@':
453 	case '=':		/* Not documented. */
454 	    mapp->conditional |= EQ;
455 	    break;
456 	case '!':
457 	    mapp->conditional |= NOT;
458 	    break;
459 	default:
460 	    goto next;
461 	}
462     }
463 
464   next:
465     if (*arg == ':') {
466 	if (mapp->conditional)
467 	    goto badmopt;
468 	++arg;
469     } else {			/* Optional baudrate. */
470 	arg = strchr(p = arg, ':');
471 	if (arg == 0)
472 	    goto badmopt;
473 	*arg++ = '\0';
474 	mapp->speed = tbaudrate(p);
475     }
476 
477     mapp->type = arg;
478 
479     /* Terminate porttype, if specified. */
480     if (termp != 0)
481 	*base = '\0';
482 
483     /* If a NOT conditional, reverse the test. */
484     if (mapp->conditional & NOT)
485 	mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
486 
487     /* If user specified a port with an option flag, set it. */
488   done:
489     if (port) {
490 	if (mapp->porttype) {
491 	  badmopt:
492 	    err("illegal -m option format: %s", copy);
493 	}
494 	mapp->porttype = port;
495     }
496     free(copy);
497 #ifdef MAPDEBUG
498     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
499     (void) printf("type: %s\n", mapp->type);
500     (void) printf("conditional: ");
501     p = "";
502     if (mapp->conditional & GT) {
503 	(void) printf("GT");
504 	p = "/";
505     }
506     if (mapp->conditional & EQ) {
507 	(void) printf("%sEQ", p);
508 	p = "/";
509     }
510     if (mapp->conditional & LT)
511 	(void) printf("%sLT", p);
512     (void) printf("\nspeed: %d\n", mapp->speed);
513 #endif
514 }
515 
516 /*
517  * Return the type of terminal to use for a port of type 'type', as specified
518  * by the first applicable mapping in 'map'.  If no mappings apply, return
519  * 'type'.
520  */
521 static const char *
522 mapped(const char *type)
523 {
524     MAP *mapp;
525     int match;
526 
527     for (mapp = maplist; mapp; mapp = mapp->next)
528 	if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
529 	    switch (mapp->conditional) {
530 	    case 0:		/* No test specified. */
531 		match = TRUE;
532 		break;
533 	    case EQ:
534 		match = ((int) ospeed == mapp->speed);
535 		break;
536 	    case GE:
537 		match = ((int) ospeed >= mapp->speed);
538 		break;
539 	    case GT:
540 		match = ((int) ospeed > mapp->speed);
541 		break;
542 	    case LE:
543 		match = ((int) ospeed <= mapp->speed);
544 		break;
545 	    case LT:
546 		match = ((int) ospeed < mapp->speed);
547 		break;
548 	    default:
549 		match = FALSE;
550 	    }
551 	    if (match)
552 		return (mapp->type);
553 	}
554     /* No match found; return given type. */
555     return (type);
556 }
557 
558 /**************************************************************************
559  *
560  * Entry fetching
561  *
562  **************************************************************************/
563 
564 /*
565  * Figure out what kind of terminal we're dealing with, and then read in
566  * its termcap entry.
567  */
568 static const char *
569 get_termcap_entry(char *userarg)
570 {
571     int errret;
572     char *p;
573     const char *ttype;
574 #if HAVE_GETTTYNAM
575     struct ttyent *t;
576 #else
577     FILE *fp;
578 #endif
579     char *ttypath;
580 
581     if (userarg) {
582 	ttype = userarg;
583 	goto found;
584     }
585 
586     /* Try the environment. */
587     if ((ttype = getenv("TERM")) != 0)
588 	goto map;
589 
590     if ((ttypath = ttyname(STDERR_FILENO)) != 0) {
591 	p = _nc_basename(ttypath);
592 #if HAVE_GETTTYNAM
593 	/*
594 	 * We have the 4.3BSD library call getttynam(3); that means
595 	 * there's an /etc/ttys to look up device-to-type mappings in.
596 	 * Try ttyname(3); check for dialup or other mapping.
597 	 */
598 	if ((t = getttynam(p))) {
599 	    ttype = t->ty_type;
600 	    goto map;
601 	}
602 #else
603 	if ((fp = fopen("/etc/ttytype", "r")) != 0
604 	    || (fp = fopen("/etc/ttys", "r")) != 0) {
605 	    char buffer[BUFSIZ];
606 	    char *s, *t, *d;
607 
608 	    while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
609 		for (s = buffer, t = d = 0; *s; s++) {
610 		    if (isspace(UChar(*s)))
611 			*s = '\0';
612 		    else if (t == 0)
613 			t = s;
614 		    else if (d == 0 && s != buffer && s[-1] == '\0')
615 			d = s;
616 		}
617 		if (t != 0 && d != 0 && !strcmp(d, p)) {
618 		    ttype = strdup(t);
619 		    fclose(fp);
620 		    goto map;
621 		}
622 	    }
623 	    fclose(fp);
624 	}
625 #endif /* HAVE_GETTTYNAM */
626     }
627 
628     /* If still undefined, use "unknown". */
629     ttype = "unknown";
630 
631   map:ttype = mapped(ttype);
632 
633     /*
634      * If not a path, remove TERMCAP from the environment so we get a
635      * real entry from /etc/termcap.  This prevents us from being fooled
636      * by out of date stuff in the environment.
637      */
638   found:
639     if ((p = getenv("TERMCAP")) != 0 && !_nc_is_abs_path(p)) {
640 	/* 'unsetenv("TERMCAP")' is not portable.
641 	 * The 'environ' array is better.
642 	 */
643 	int n;
644 	for (n = 0; environ[n] != 0; n++) {
645 	    if (!strncmp("TERMCAP=", environ[n], (size_t) 8)) {
646 		while ((environ[n] = environ[n + 1]) != 0) {
647 		    n++;
648 		}
649 		break;
650 	    }
651 	}
652     }
653 
654     /*
655      * ttype now contains a pointer to the type of the terminal.
656      * If the first character is '?', ask the user.
657      */
658     if (ttype[0] == '?') {
659 	if (ttype[1] != '\0')
660 	    ttype = askuser(ttype + 1);
661 	else
662 	    ttype = askuser(0);
663     }
664     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
665     while (setupterm((NCURSES_CONST char *) ttype, STDOUT_FILENO, &errret)
666 	   != OK) {
667 	if (errret == 0) {
668 	    (void) fprintf(stderr, "%s: unknown terminal type %s\n",
669 			   _nc_progname, ttype);
670 	    ttype = 0;
671 	} else {
672 	    (void) fprintf(stderr,
673 			   "%s: can't initialize terminal type %s (error %d)\n",
674 			   _nc_progname, ttype, errret);
675 	    ttype = 0;
676 	}
677 	ttype = askuser(ttype);
678     }
679 #if BROKEN_LINKER
680     tgetflag("am");		/* force lib_termcap.o to be linked for 'ospeed' */
681 #endif
682     return (ttype);
683 }
684 
685 /**************************************************************************
686  *
687  * Mode-setting logic
688  *
689  **************************************************************************/
690 
691 /* some BSD systems have these built in, some systems are missing
692  * one or more definitions. The safest solution is to override unless the
693  * commonly-altered ones are defined.
694  */
695 #if !(defined(CERASE) && defined(CINTR) && defined(CKILL) && defined(CQUIT))
696 #undef CEOF
697 #undef CERASE
698 #undef CINTR
699 #undef CKILL
700 #undef CLNEXT
701 #undef CRPRNT
702 #undef CQUIT
703 #undef CSTART
704 #undef CSTOP
705 #undef CSUSP
706 #endif
707 
708 /* control-character defaults */
709 #ifndef CEOF
710 #define CEOF	CTRL('D')
711 #endif
712 #ifndef CERASE
713 #define CERASE	CTRL('H')
714 #endif
715 #ifndef CINTR
716 #define CINTR	127		/* ^? */
717 #endif
718 #ifndef CKILL
719 #define CKILL	CTRL('U')
720 #endif
721 #ifndef CLNEXT
722 #define CLNEXT  CTRL('v')
723 #endif
724 #ifndef CRPRNT
725 #define CRPRNT  CTRL('r')
726 #endif
727 #ifndef CQUIT
728 #define CQUIT	CTRL('\\')
729 #endif
730 #ifndef CSTART
731 #define CSTART	CTRL('Q')
732 #endif
733 #ifndef CSTOP
734 #define CSTOP	CTRL('S')
735 #endif
736 #ifndef CSUSP
737 #define CSUSP	CTRL('Z')
738 #endif
739 
740 #if defined(_POSIX_VDISABLE)
741 #define DISABLED(val)   (((_POSIX_VDISABLE != -1) \
742 		       && ((val) == _POSIX_VDISABLE)) \
743 		      || ((val) <= 0))
744 #else
745 #define DISABLED(val)   ((int)(val) <= 0)
746 #endif
747 
748 #define CHK(val, dft)   (DISABLED(val) ? dft : val)
749 
750 static bool set_tabs(void);
751 
752 /*
753  * Reset the terminal mode bits to a sensible state.  Very useful after
754  * a child program dies in raw mode.
755  */
756 static void
757 reset_mode(void)
758 {
759 #ifdef TERMIOS
760     tcgetattr(STDERR_FILENO, &mode);
761 #else
762     stty(STDERR_FILENO, &mode);
763 #endif
764 
765 #ifdef TERMIOS
766 #if defined(VDISCARD) && defined(CDISCARD)
767     mode.c_cc[VDISCARD] = CHK(mode.c_cc[VDISCARD], CDISCARD);
768 #endif
769     mode.c_cc[VEOF] = CHK(mode.c_cc[VEOF], CEOF);
770     mode.c_cc[VERASE] = CHK(mode.c_cc[VERASE], CERASE);
771 #if defined(VFLUSH) && defined(CFLUSH)
772     mode.c_cc[VFLUSH] = CHK(mode.c_cc[VFLUSH], CFLUSH);
773 #endif
774     mode.c_cc[VINTR] = CHK(mode.c_cc[VINTR], CINTR);
775     mode.c_cc[VKILL] = CHK(mode.c_cc[VKILL], CKILL);
776 #if defined(VLNEXT) && defined(CLNEXT)
777     mode.c_cc[VLNEXT] = CHK(mode.c_cc[VLNEXT], CLNEXT);
778 #endif
779     mode.c_cc[VQUIT] = CHK(mode.c_cc[VQUIT], CQUIT);
780 #if defined(VREPRINT) && defined(CRPRNT)
781     mode.c_cc[VREPRINT] = CHK(mode.c_cc[VREPRINT], CRPRNT);
782 #endif
783 #if defined(VSTART) && defined(CSTART)
784     mode.c_cc[VSTART] = CHK(mode.c_cc[VSTART], CSTART);
785 #endif
786 #if defined(VSTOP) && defined(CSTOP)
787     mode.c_cc[VSTOP] = CHK(mode.c_cc[VSTOP], CSTOP);
788 #endif
789 #if defined(VSUSP) && defined(CSUSP)
790     mode.c_cc[VSUSP] = CHK(mode.c_cc[VSUSP], CSUSP);
791 #endif
792 #if defined(VWERASE) && defined(CWERASE)
793     mode.c_cc[VWERASE] = CHK(mode.c_cc[VWERASE], CWERASE);
794 #endif
795 
796     mode.c_iflag &= ~((unsigned) (IGNBRK | PARMRK | INPCK | ISTRIP | INLCR | IGNCR
797 #ifdef IUCLC
798 				  | IUCLC
799 #endif
800 #ifdef IXANY
801 				  | IXANY
802 #endif
803 				  | IXOFF));
804 
805     mode.c_iflag |= (BRKINT | IGNPAR | ICRNL | IXON
806 #ifdef IMAXBEL
807 		     | IMAXBEL
808 #endif
809 	);
810 
811     mode.c_oflag &= ~((unsigned) (0
812 #ifdef OLCUC
813 				  | OLCUC
814 #endif
815 #ifdef OCRNL
816 				  | OCRNL
817 #endif
818 #ifdef ONOCR
819 				  | ONOCR
820 #endif
821 #ifdef ONLRET
822 				  | ONLRET
823 #endif
824 #ifdef OFILL
825 				  | OFILL
826 #endif
827 #ifdef OFDEL
828 				  | OFDEL
829 #endif
830 #ifdef NLDLY
831 				  | NLDLY
832 #endif
833 #ifdef CRDLY
834 				  | CRDLY
835 #endif
836 #ifdef TABDLY
837 				  | TABDLY
838 #endif
839 #ifdef BSDLY
840 				  | BSDLY
841 #endif
842 #ifdef VTDLY
843 				  | VTDLY
844 #endif
845 #ifdef FFDLY
846 				  | FFDLY
847 #endif
848 		      ));
849 
850     mode.c_oflag |= (OPOST
851 #ifdef ONLCR
852 		     | ONLCR
853 #endif
854 	);
855 
856     mode.c_cflag &= ~((unsigned) (CSIZE | CSTOPB | PARENB | PARODD | CLOCAL));
857     mode.c_cflag |= (CS8 | CREAD);
858     mode.c_lflag &= ~((unsigned) (ECHONL | NOFLSH
859 #ifdef TOSTOP
860 				  | TOSTOP
861 #endif
862 #ifdef ECHOPTR
863 				  | ECHOPRT
864 #endif
865 #ifdef XCASE
866 				  | XCASE
867 #endif
868 		      ));
869 
870     mode.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK
871 #ifdef ECHOCTL
872 		     | ECHOCTL
873 #endif
874 #ifdef ECHOKE
875 		     | ECHOKE
876 #endif
877 	);
878 #endif
879 
880     SET_TTY(STDERR_FILENO, &mode);
881 }
882 
883 /*
884  * Returns a "good" value for the erase character.  This is loosely based on
885  * the BSD4.4 logic.
886  */
887 #ifdef TERMIOS
888 static int
889 default_erase(void)
890 {
891     int result;
892 
893     if (over_strike
894 	&& key_backspace != 0
895 	&& strlen(key_backspace) == 1)
896 	result = key_backspace[0];
897     else
898 	result = CERASE;
899 
900     return result;
901 }
902 #endif
903 
904 /*
905  * Update the values of the erase, interrupt, and kill characters in 'mode'.
906  *
907  * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
908  * characters if they're unset, or if we specify them as options.  This differs
909  * from BSD 4.4 tset, which always sets erase.
910  */
911 static void
912 set_control_chars(void)
913 {
914 #ifdef TERMIOS
915     if (DISABLED(mode.c_cc[VERASE]) || terasechar >= 0) {
916 	mode.c_cc[VERASE] = UChar((terasechar >= 0)
917 				  ? terasechar
918 				  : default_erase());
919     }
920 
921     if (DISABLED(mode.c_cc[VINTR]) || intrchar >= 0) {
922 	mode.c_cc[VINTR] = UChar((intrchar >= 0)
923 				 ? intrchar
924 				 : CINTR);
925     }
926 
927     if (DISABLED(mode.c_cc[VKILL]) || tkillchar >= 0) {
928 	mode.c_cc[VKILL] = UChar((tkillchar >= 0)
929 				 ? tkillchar
930 				 : CKILL);
931     }
932 #endif
933 }
934 
935 /*
936  * Set up various conversions in 'mode', including parity, tabs, returns,
937  * echo, and case, according to the termcap entry.  If the program we're
938  * running was named with a leading upper-case character, map external
939  * uppercase to internal lowercase.
940  */
941 static void
942 set_conversions(void)
943 {
944 #ifdef __OBSOLETE__
945     /*
946      * Conversion logic for some *really* ancient terminal glitches,
947      * not supported in terminfo.  Left here for succeeding generations
948      * to marvel at.
949      */
950     if (tgetflag("UC")) {
951 #ifdef IUCLC
952 	mode.c_iflag |= IUCLC;
953 	mode.c_oflag |= OLCUC;
954 #endif
955     } else if (tgetflag("LC")) {
956 #ifdef IUCLC
957 	mode.c_iflag &= ~IUCLC;
958 	mode.c_oflag &= ~OLCUC;
959 #endif
960     }
961     mode.c_iflag &= ~(PARMRK | INPCK);
962     mode.c_lflag |= ICANON;
963     if (tgetflag("EP")) {
964 	mode.c_cflag |= PARENB;
965 	mode.c_cflag &= ~PARODD;
966     }
967     if (tgetflag("OP")) {
968 	mode.c_cflag |= PARENB;
969 	mode.c_cflag |= PARODD;
970     }
971 #endif /* __OBSOLETE__ */
972 
973 #ifdef TERMIOS
974 #ifdef ONLCR
975     mode.c_oflag |= ONLCR;
976 #endif
977     mode.c_iflag |= ICRNL;
978     mode.c_lflag |= ECHO;
979 #ifdef OXTABS
980     mode.c_oflag |= OXTABS;
981 #endif /* OXTABS */
982 
983     /* test used to be tgetflag("NL") */
984     if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
985 	/* Newline, not linefeed. */
986 #ifdef ONLCR
987 	mode.c_oflag &= ~((unsigned) ONLCR);
988 #endif
989 	mode.c_iflag &= ~((unsigned) ICRNL);
990     }
991 #ifdef __OBSOLETE__
992     if (tgetflag("HD"))		/* Half duplex. */
993 	mode.c_lflag &= ~ECHO;
994 #endif /* __OBSOLETE__ */
995 #ifdef OXTABS
996     /* test used to be tgetflag("pt") */
997     if (has_hardware_tabs)	/* Print tabs. */
998 	mode.c_oflag &= ~OXTABS;
999 #endif /* OXTABS */
1000     mode.c_lflag |= (ECHOE | ECHOK);
1001 #endif
1002 }
1003 
1004 /* Output startup string. */
1005 static void
1006 set_init(void)
1007 {
1008     char *p;
1009     bool settle;
1010 
1011 #ifdef __OBSOLETE__
1012     if (pad_char != (char *) 0)	/* Get/set pad character. */
1013 	PC = pad_char[0];
1014 #endif /* OBSOLETE */
1015 
1016 #ifdef TAB3
1017     if (oldmode.c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
1018 	oldmode.c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
1019 	SET_TTY(STDERR_FILENO, &oldmode);
1020     }
1021 #endif
1022     settle = set_tabs();
1023 
1024     if (isreset) {
1025 	if ((p = reset_1string) != 0) {
1026 	    tputs(p, 0, outc);
1027 	    settle = TRUE;
1028 	}
1029 	if ((p = reset_2string) != 0) {
1030 	    tputs(p, 0, outc);
1031 	    settle = TRUE;
1032 	}
1033 	/* What about rf, rs3, as per terminfo man page? */
1034 	/* also might be nice to send rmacs, rmul, rmm */
1035 	if ((p = reset_file) != 0
1036 	    || (p = init_file) != 0) {
1037 	    cat(p);
1038 	    settle = TRUE;
1039 	}
1040     }
1041 
1042     if (settle) {
1043 	(void) putc('\r', stderr);
1044 	(void) fflush(stderr);
1045 	(void) napms(1000);	/* Settle the terminal. */
1046     }
1047 }
1048 
1049 /*
1050  * Set the hardware tabs on the terminal, using the ct (clear all tabs),
1051  * st (set one tab) and ch (horizontal cursor addressing) capabilities.
1052  * This is done before if and is, so they can patch in case we blow this.
1053  * Return TRUE if we set any tab stops, FALSE if not.
1054  */
1055 static bool
1056 set_tabs(void)
1057 {
1058     if (set_tab && clear_all_tabs) {
1059 	int c;
1060 	int lim =
1061 #if HAVE_SIZECHANGE
1062 	tcolumns
1063 #else
1064 	columns
1065 #endif
1066 	 ;
1067 
1068 	(void) putc('\r', stderr);	/* Force to left margin. */
1069 	tputs(clear_all_tabs, 0, outc);
1070 
1071 	for (c = 8; c < lim; c += 8) {
1072 	    /* Get to the right column.  In BSD tset, this
1073 	     * used to try a bunch of half-clever things
1074 	     * with cup and hpa, for an average saving of
1075 	     * somewhat less than two character times per
1076 	     * tab stop, less than .01 sec at 2400cps. We
1077 	     * lost all this cruft because it seemed to be
1078 	     * introducing some odd bugs.
1079 	     * -----------12345678----------- */
1080 	    (void) fputs("        ", stderr);
1081 	    tputs(set_tab, 0, outc);
1082 	}
1083 	putc('\r', stderr);
1084 	return (TRUE);
1085     }
1086     return (FALSE);
1087 }
1088 
1089 /**************************************************************************
1090  *
1091  * Main sequence
1092  *
1093  **************************************************************************/
1094 
1095 /*
1096  * Tell the user if a control key has been changed from the default value.
1097  */
1098 #ifdef TERMIOS
1099 static void
1100 report(const char *name, int which, unsigned def)
1101 {
1102     unsigned older, newer;
1103     char *p;
1104 
1105     newer = mode.c_cc[which];
1106     older = oldmode.c_cc[which];
1107 
1108     if (older == newer && older == def)
1109 	return;
1110 
1111     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
1112 
1113     if (DISABLED(newer))
1114 	(void) fprintf(stderr, "undef.\n");
1115     /*
1116      * Check 'delete' before 'backspace', since the key_backspace value
1117      * is ambiguous.
1118      */
1119     else if (newer == 0177)
1120 	(void) fprintf(stderr, "delete.\n");
1121     else if ((p = key_backspace) != 0
1122 	     && newer == (unsigned char) p[0]
1123 	     && p[1] == '\0')
1124 	(void) fprintf(stderr, "backspace.\n");
1125     else if (newer < 040) {
1126 	newer ^= 0100;
1127 	(void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
1128     } else
1129 	(void) fprintf(stderr, "%c.\n", UChar(newer));
1130 }
1131 #endif
1132 
1133 /*
1134  * Convert the obsolete argument forms into something that getopt can handle.
1135  * This means that -e, -i and -k get default arguments supplied for them.
1136  */
1137 static void
1138 obsolete(char **argv)
1139 {
1140     for (; *argv; ++argv) {
1141 	char *parm = argv[0];
1142 
1143 	if (parm[0] == '-' && parm[1] == '\0') {
1144 	    argv[0] = strdup("-q");
1145 	    continue;
1146 	}
1147 
1148 	if ((parm[0] != '-')
1149 	    || (argv[1] && argv[1][0] != '-')
1150 	    || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
1151 	    || (parm[2] != '\0'))
1152 	    continue;
1153 	switch (argv[0][1]) {
1154 	case 'e':
1155 	    argv[0] = strdup("-e^H");
1156 	    break;
1157 	case 'i':
1158 	    argv[0] = strdup("-i^C");
1159 	    break;
1160 	case 'k':
1161 	    argv[0] = strdup("-k^U");
1162 	    break;
1163 	}
1164     }
1165 }
1166 
1167 static void
1168 usage(void)
1169 {
1170     static const char *tbl[] =
1171     {
1172 	""
1173 	,"Options:"
1174 	,"  -c          set control characters"
1175 	,"  -e ch       erase character"
1176 	,"  -I          no initialization strings"
1177 	,"  -i ch       interrupt character"
1178 	,"  -k ch       kill character"
1179 	,"  -m mapping  map identifier to type"
1180 	,"  -Q          do not output control key settings"
1181 	,"  -r          display term on stderr"
1182 	,"  -s          output TERM set command"
1183 	,"  -V          print curses-version"
1184 	,"  -w          set window-size"
1185     };
1186     unsigned n;
1187     (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname);
1188     for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n)
1189 	fprintf(stderr, "%s\n", tbl[n]);
1190     exit_error();
1191     /* NOTREACHED */
1192 }
1193 
1194 static char
1195 arg_to_char(void)
1196 {
1197     return (char) ((optarg[0] == '^' && optarg[1] != '\0')
1198 		   ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
1199 		   : optarg[0]);
1200 }
1201 
1202 int
1203 main(int argc, char **argv)
1204 {
1205     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
1206     const char *p;
1207     const char *ttype;
1208 
1209     obsolete(argv);
1210     noinit = noset = quiet = Sflag = sflag = showterm = 0;
1211     while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:np:qQSrsVw")) != -1) {
1212 	switch (ch) {
1213 	case 'c':		/* set control-chars */
1214 	    opt_c = TRUE;
1215 	    break;
1216 	case 'a':		/* OBSOLETE: map identifier to type */
1217 	    add_mapping("arpanet", optarg);
1218 	    break;
1219 	case 'd':		/* OBSOLETE: map identifier to type */
1220 	    add_mapping("dialup", optarg);
1221 	    break;
1222 	case 'e':		/* erase character */
1223 	    terasechar = arg_to_char();
1224 	    break;
1225 	case 'I':		/* no initialization strings */
1226 	    noinit = 1;
1227 	    break;
1228 	case 'i':		/* interrupt character */
1229 	    intrchar = arg_to_char();
1230 	    break;
1231 	case 'k':		/* kill character */
1232 	    tkillchar = arg_to_char();
1233 	    break;
1234 	case 'm':		/* map identifier to type */
1235 	    add_mapping(0, optarg);
1236 	    break;
1237 	case 'n':		/* OBSOLETE: set new tty driver */
1238 	    break;
1239 	case 'p':		/* OBSOLETE: map identifier to type */
1240 	    add_mapping("plugboard", optarg);
1241 	    break;
1242 	case 'Q':		/* don't output control key settings */
1243 	    quiet = 1;
1244 	    break;
1245 	case 'q':		/* display term only */
1246 	    noset = 1;
1247 	    break;
1248 	case 'r':		/* display term on stderr */
1249 	    showterm = 1;
1250 	    break;
1251 	case 'S':		/* OBSOLETE: output TERM & TERMCAP */
1252 	    Sflag = 1;
1253 	    break;
1254 	case 's':		/* output TERM set command */
1255 	    sflag = 1;
1256 	    break;
1257 	case 'V':		/* print curses-version */
1258 	    puts(curses_version());
1259 	    ExitProgram(EXIT_SUCCESS);
1260 	case 'w':		/* set window-size */
1261 	    opt_w = TRUE;
1262 	    break;
1263 	case '?':
1264 	default:
1265 	    usage();
1266 	}
1267     }
1268 
1269     _nc_progname = _nc_rootname(*argv);
1270     argc -= optind;
1271     argv += optind;
1272 
1273     if (argc > 1)
1274 	usage();
1275 
1276     if (!opt_c && !opt_w)
1277 	opt_c = opt_w = TRUE;
1278 
1279     if (GET_TTY(STDERR_FILENO, &mode) < 0)
1280 	failed("standard error");
1281     can_restore = TRUE;
1282     original = oldmode = mode;
1283 #ifdef TERMIOS
1284     ospeed = (NCURSES_OSPEED) cfgetospeed(&mode);
1285 #else
1286     ospeed = (NCURSES_OSPEED) mode.sg_ospeed;
1287 #endif
1288 
1289     if (same_program(_nc_progname, PROG_RESET)) {
1290 	isreset = TRUE;
1291 	reset_mode();
1292     }
1293 
1294     (void) get_termcap_entry(*argv);
1295 
1296     if (!noset) {
1297 #if HAVE_SIZECHANGE
1298 	tcolumns = columns;
1299 	tlines = lines;
1300 
1301 	if (opt_w) {
1302 	    STRUCT_WINSIZE win;
1303 	    /* Set window size if not set already */
1304 	    (void) ioctl(STDERR_FILENO, IOCTL_GET_WINSIZE, &win);
1305 	    if (WINSIZE_ROWS(win) == 0 &&
1306 		WINSIZE_COLS(win) == 0 &&
1307 		tlines > 0 && tcolumns > 0) {
1308 		WINSIZE_ROWS(win) = tlines;
1309 		WINSIZE_COLS(win) = tcolumns;
1310 		(void) ioctl(STDERR_FILENO, IOCTL_SET_WINSIZE, &win);
1311 	    }
1312 	}
1313 #endif
1314 	if (opt_c) {
1315 	    set_control_chars();
1316 	    set_conversions();
1317 
1318 	    if (!noinit)
1319 		set_init();
1320 
1321 	    /* Set the modes if they've changed. */
1322 	    if (memcmp(&mode, &oldmode, sizeof(mode))) {
1323 		SET_TTY(STDERR_FILENO, &mode);
1324 	    }
1325 	}
1326     }
1327 
1328     /* Get the terminal name from the entry. */
1329     ttype = _nc_first_name(cur_term->type.term_names);
1330 
1331     if (noset)
1332 	(void) printf("%s\n", ttype);
1333     else {
1334 	if (showterm)
1335 	    (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
1336 	/*
1337 	 * If erase, kill and interrupt characters could have been
1338 	 * modified and not -Q, display the changes.
1339 	 */
1340 #ifdef TERMIOS
1341 	if (!quiet) {
1342 	    report("Erase", VERASE, CERASE);
1343 	    report("Kill", VKILL, CKILL);
1344 	    report("Interrupt", VINTR, CINTR);
1345 	}
1346 #endif
1347     }
1348 
1349     if (Sflag)
1350 	err("The -S option is not supported under terminfo.");
1351 
1352     if (sflag) {
1353 	int len;
1354 	char *var;
1355 	char *leaf;
1356 	/*
1357 	 * Figure out what shell we're using.  A hack, we look for an
1358 	 * environmental variable SHELL ending in "csh".
1359 	 */
1360 	if ((var = getenv("SHELL")) != 0
1361 	    && ((len = (int) strlen(leaf = _nc_basename(var))) >= 3)
1362 	    && !strcmp(leaf + len - 3, "csh"))
1363 	    p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
1364 	else
1365 	    p = "TERM=%s;\n";
1366 	(void) printf(p, ttype);
1367     }
1368 
1369     ExitProgram(EXIT_SUCCESS);
1370 }
1371