xref: /dragonfly/contrib/ncurses/progs/tset.c (revision 9348a738)
1 /****************************************************************************
2  * Copyright (c) 1998-2013,2015 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.97 2015/11/08 01:45:47 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 #define DATA(name,value) { { name }, value }
311 
312 typedef struct speeds {
313     const char string[7];
314     int speed;
315 } SPEEDS;
316 
317 static const SPEEDS speeds[] =
318 {
319     DATA("0", B0),
320     DATA("50", B50),
321     DATA("75", B75),
322     DATA("110", B110),
323     DATA("134", B134),
324     DATA("134.5", B134),
325     DATA("150", B150),
326     DATA("200", B200),
327     DATA("300", B300),
328     DATA("600", B600),
329     DATA("1200", B1200),
330     DATA("1800", B1800),
331     DATA("2400", B2400),
332     DATA("4800", B4800),
333     DATA("9600", B9600),
334     /* sgttyb may define up to this point */
335 #ifdef B19200
336     DATA("19200", B19200),
337 #endif
338 #ifdef B38400
339     DATA("38400", B38400),
340 #endif
341 #ifdef B19200
342     DATA("19200", B19200),
343 #endif
344 #ifdef B38400
345     DATA("38400", B38400),
346 #endif
347 #ifdef B19200
348     DATA("19200", B19200),
349 #else
350 #ifdef EXTA
351     DATA("19200", EXTA),
352 #endif
353 #endif
354 #ifdef B38400
355     DATA("38400", B38400),
356 #else
357 #ifdef EXTB
358     DATA("38400", EXTB),
359 #endif
360 #endif
361 #ifdef B57600
362     DATA("57600", B57600),
363 #endif
364 #ifdef B115200
365     DATA("115200", B115200),
366 #endif
367 #ifdef B230400
368     DATA("230400", B230400),
369 #endif
370 #ifdef B460800
371     DATA("460800", B460800),
372 #endif
373 };
374 #undef DATA
375 
376 static int
377 tbaudrate(char *rate)
378 {
379     const SPEEDS *sp = 0;
380     size_t n;
381 
382     /* The baudrate number can be preceded by a 'B', which is ignored. */
383     if (*rate == 'B')
384 	++rate;
385 
386     for (n = 0; n < SIZEOF(speeds); ++n) {
387 	if (!CaselessCmp(rate, speeds[n].string)) {
388 	    sp = speeds + n;
389 	    break;
390 	}
391     }
392     if (sp == 0)
393 	err("unknown baud rate %s", rate);
394     return (sp->speed);
395 }
396 
397 /*
398  * Syntax for -m:
399  * [port-type][test baudrate]:terminal-type
400  * The baud rate tests are: >, <, @, =, !
401  */
402 static void
403 add_mapping(const char *port, char *arg)
404 {
405     MAP *mapp;
406     char *copy, *p;
407     const char *termp;
408     char *base = 0;
409 
410     copy = strdup(arg);
411     mapp = typeMalloc(MAP, 1);
412     if (copy == 0 || mapp == 0)
413 	failed("malloc");
414 
415     assert(copy != 0);
416     assert(mapp != 0);
417 
418     mapp->next = 0;
419     if (maplist == 0)
420 	cur = maplist = mapp;
421     else {
422 	cur->next = mapp;
423 	cur = mapp;
424     }
425 
426     mapp->porttype = arg;
427     mapp->conditional = 0;
428 
429     arg = strpbrk(arg, "><@=!:");
430 
431     if (arg == 0) {		/* [?]term */
432 	mapp->type = mapp->porttype;
433 	mapp->porttype = 0;
434 	goto done;
435     }
436 
437     if (arg == mapp->porttype)	/* [><@=! baud]:term */
438 	termp = mapp->porttype = 0;
439     else
440 	termp = base = arg;
441 
442     for (;; ++arg) {		/* Optional conditionals. */
443 	switch (*arg) {
444 	case '<':
445 	    if (mapp->conditional & GT)
446 		goto badmopt;
447 	    mapp->conditional |= LT;
448 	    break;
449 	case '>':
450 	    if (mapp->conditional & LT)
451 		goto badmopt;
452 	    mapp->conditional |= GT;
453 	    break;
454 	case '@':
455 	case '=':		/* Not documented. */
456 	    mapp->conditional |= EQ;
457 	    break;
458 	case '!':
459 	    mapp->conditional |= NOT;
460 	    break;
461 	default:
462 	    goto next;
463 	}
464     }
465 
466   next:
467     if (*arg == ':') {
468 	if (mapp->conditional)
469 	    goto badmopt;
470 	++arg;
471     } else {			/* Optional baudrate. */
472 	arg = strchr(p = arg, ':');
473 	if (arg == 0)
474 	    goto badmopt;
475 	*arg++ = '\0';
476 	mapp->speed = tbaudrate(p);
477     }
478 
479     mapp->type = arg;
480 
481     /* Terminate porttype, if specified. */
482     if (termp != 0)
483 	*base = '\0';
484 
485     /* If a NOT conditional, reverse the test. */
486     if (mapp->conditional & NOT)
487 	mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
488 
489     /* If user specified a port with an option flag, set it. */
490   done:
491     if (port) {
492 	if (mapp->porttype) {
493 	  badmopt:
494 	    err("illegal -m option format: %s", copy);
495 	}
496 	mapp->porttype = port;
497     }
498     free(copy);
499 #ifdef MAPDEBUG
500     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
501     (void) printf("type: %s\n", mapp->type);
502     (void) printf("conditional: ");
503     p = "";
504     if (mapp->conditional & GT) {
505 	(void) printf("GT");
506 	p = "/";
507     }
508     if (mapp->conditional & EQ) {
509 	(void) printf("%sEQ", p);
510 	p = "/";
511     }
512     if (mapp->conditional & LT)
513 	(void) printf("%sLT", p);
514     (void) printf("\nspeed: %d\n", mapp->speed);
515 #endif
516 }
517 
518 /*
519  * Return the type of terminal to use for a port of type 'type', as specified
520  * by the first applicable mapping in 'map'.  If no mappings apply, return
521  * 'type'.
522  */
523 static const char *
524 mapped(const char *type)
525 {
526     MAP *mapp;
527     int match;
528 
529     for (mapp = maplist; mapp; mapp = mapp->next)
530 	if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
531 	    switch (mapp->conditional) {
532 	    case 0:		/* No test specified. */
533 		match = TRUE;
534 		break;
535 	    case EQ:
536 		match = ((int) ospeed == mapp->speed);
537 		break;
538 	    case GE:
539 		match = ((int) ospeed >= mapp->speed);
540 		break;
541 	    case GT:
542 		match = ((int) ospeed > mapp->speed);
543 		break;
544 	    case LE:
545 		match = ((int) ospeed <= mapp->speed);
546 		break;
547 	    case LT:
548 		match = ((int) ospeed < mapp->speed);
549 		break;
550 	    default:
551 		match = FALSE;
552 	    }
553 	    if (match)
554 		return (mapp->type);
555 	}
556     /* No match found; return given type. */
557     return (type);
558 }
559 
560 /**************************************************************************
561  *
562  * Entry fetching
563  *
564  **************************************************************************/
565 
566 /*
567  * Figure out what kind of terminal we're dealing with, and then read in
568  * its termcap entry.
569  */
570 static const char *
571 get_termcap_entry(char *userarg)
572 {
573     int errret;
574     char *p;
575     const char *ttype;
576 #if HAVE_GETTTYNAM
577     struct ttyent *t;
578 #else
579     FILE *fp;
580 #endif
581     char *ttypath;
582 
583     if (userarg) {
584 	ttype = userarg;
585 	goto found;
586     }
587 
588     /* Try the environment. */
589     if ((ttype = getenv("TERM")) != 0)
590 	goto map;
591 
592     if ((ttypath = ttyname(STDERR_FILENO)) != 0) {
593 	p = _nc_basename(ttypath);
594 #if HAVE_GETTTYNAM
595 	/*
596 	 * We have the 4.3BSD library call getttynam(3); that means
597 	 * there's an /etc/ttys to look up device-to-type mappings in.
598 	 * Try ttyname(3); check for dialup or other mapping.
599 	 */
600 	if ((t = getttynam(p))) {
601 	    ttype = t->ty_type;
602 	    goto map;
603 	}
604 #else
605 	if ((fp = fopen("/etc/ttytype", "r")) != 0
606 	    || (fp = fopen("/etc/ttys", "r")) != 0) {
607 	    char buffer[BUFSIZ];
608 	    char *s, *t, *d;
609 
610 	    while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
611 		for (s = buffer, t = d = 0; *s; s++) {
612 		    if (isspace(UChar(*s)))
613 			*s = '\0';
614 		    else if (t == 0)
615 			t = s;
616 		    else if (d == 0 && s != buffer && s[-1] == '\0')
617 			d = s;
618 		}
619 		if (t != 0 && d != 0 && !strcmp(d, p)) {
620 		    ttype = strdup(t);
621 		    fclose(fp);
622 		    goto map;
623 		}
624 	    }
625 	    fclose(fp);
626 	}
627 #endif /* HAVE_GETTTYNAM */
628     }
629 
630     /* If still undefined, use "unknown". */
631     ttype = "unknown";
632 
633   map:ttype = mapped(ttype);
634 
635     /*
636      * If not a path, remove TERMCAP from the environment so we get a
637      * real entry from /etc/termcap.  This prevents us from being fooled
638      * by out of date stuff in the environment.
639      */
640   found:
641     if ((p = getenv("TERMCAP")) != 0 && !_nc_is_abs_path(p)) {
642 	/* 'unsetenv("TERMCAP")' is not portable.
643 	 * The 'environ' array is better.
644 	 */
645 	int n;
646 	for (n = 0; environ[n] != 0; n++) {
647 	    if (!strncmp("TERMCAP=", environ[n], (size_t) 8)) {
648 		while ((environ[n] = environ[n + 1]) != 0) {
649 		    n++;
650 		}
651 		break;
652 	    }
653 	}
654     }
655 
656     /*
657      * ttype now contains a pointer to the type of the terminal.
658      * If the first character is '?', ask the user.
659      */
660     if (ttype[0] == '?') {
661 	if (ttype[1] != '\0')
662 	    ttype = askuser(ttype + 1);
663 	else
664 	    ttype = askuser(0);
665     }
666     /* Find the terminfo entry.  If it doesn't exist, ask the user. */
667     while (setupterm((NCURSES_CONST char *) ttype, STDOUT_FILENO, &errret)
668 	   != OK) {
669 	if (errret == 0) {
670 	    (void) fprintf(stderr, "%s: unknown terminal type %s\n",
671 			   _nc_progname, ttype);
672 	    ttype = 0;
673 	} else {
674 	    (void) fprintf(stderr,
675 			   "%s: can't initialize terminal type %s (error %d)\n",
676 			   _nc_progname, ttype, errret);
677 	    ttype = 0;
678 	}
679 	ttype = askuser(ttype);
680     }
681 #if BROKEN_LINKER
682     tgetflag("am");		/* force lib_termcap.o to be linked for 'ospeed' */
683 #endif
684     return (ttype);
685 }
686 
687 /**************************************************************************
688  *
689  * Mode-setting logic
690  *
691  **************************************************************************/
692 
693 /* some BSD systems have these built in, some systems are missing
694  * one or more definitions. The safest solution is to override unless the
695  * commonly-altered ones are defined.
696  */
697 #if !(defined(CERASE) && defined(CINTR) && defined(CKILL) && defined(CQUIT))
698 #undef CEOF
699 #undef CERASE
700 #undef CINTR
701 #undef CKILL
702 #undef CLNEXT
703 #undef CRPRNT
704 #undef CQUIT
705 #undef CSTART
706 #undef CSTOP
707 #undef CSUSP
708 #endif
709 
710 /* control-character defaults */
711 #ifndef CEOF
712 #define CEOF	CTRL('D')
713 #endif
714 #ifndef CERASE
715 #define CERASE	CTRL('H')
716 #endif
717 #ifndef CINTR
718 #define CINTR	127		/* ^? */
719 #endif
720 #ifndef CKILL
721 #define CKILL	CTRL('U')
722 #endif
723 #ifndef CLNEXT
724 #define CLNEXT  CTRL('v')
725 #endif
726 #ifndef CRPRNT
727 #define CRPRNT  CTRL('r')
728 #endif
729 #ifndef CQUIT
730 #define CQUIT	CTRL('\\')
731 #endif
732 #ifndef CSTART
733 #define CSTART	CTRL('Q')
734 #endif
735 #ifndef CSTOP
736 #define CSTOP	CTRL('S')
737 #endif
738 #ifndef CSUSP
739 #define CSUSP	CTRL('Z')
740 #endif
741 
742 #if defined(_POSIX_VDISABLE)
743 #define DISABLED(val)   (((_POSIX_VDISABLE != -1) \
744 		       && ((val) == _POSIX_VDISABLE)) \
745 		      || ((val) <= 0))
746 #else
747 #define DISABLED(val)   ((int)(val) <= 0)
748 #endif
749 
750 #define CHK(val, dft)   (unsigned char) (DISABLED(val) ? dft : val)
751 
752 static bool set_tabs(void);
753 
754 /*
755  * Reset the terminal mode bits to a sensible state.  Very useful after
756  * a child program dies in raw mode.
757  */
758 static void
759 reset_mode(void)
760 {
761 #ifdef TERMIOS
762     tcgetattr(STDERR_FILENO, &mode);
763 #else
764     stty(STDERR_FILENO, &mode);
765 #endif
766 
767 #ifdef TERMIOS
768 #if defined(VDISCARD) && defined(CDISCARD)
769     mode.c_cc[VDISCARD] = CHK(mode.c_cc[VDISCARD], CDISCARD);
770 #endif
771     mode.c_cc[VEOF] = CHK(mode.c_cc[VEOF], CEOF);
772     mode.c_cc[VERASE] = CHK(mode.c_cc[VERASE], CERASE);
773 #if defined(VFLUSH) && defined(CFLUSH)
774     mode.c_cc[VFLUSH] = CHK(mode.c_cc[VFLUSH], CFLUSH);
775 #endif
776     mode.c_cc[VINTR] = CHK(mode.c_cc[VINTR], CINTR);
777     mode.c_cc[VKILL] = CHK(mode.c_cc[VKILL], CKILL);
778 #if defined(VLNEXT) && defined(CLNEXT)
779     mode.c_cc[VLNEXT] = CHK(mode.c_cc[VLNEXT], CLNEXT);
780 #endif
781     mode.c_cc[VQUIT] = CHK(mode.c_cc[VQUIT], CQUIT);
782 #if defined(VREPRINT) && defined(CRPRNT)
783     mode.c_cc[VREPRINT] = CHK(mode.c_cc[VREPRINT], CRPRNT);
784 #endif
785 #if defined(VSTART) && defined(CSTART)
786     mode.c_cc[VSTART] = CHK(mode.c_cc[VSTART], CSTART);
787 #endif
788 #if defined(VSTOP) && defined(CSTOP)
789     mode.c_cc[VSTOP] = CHK(mode.c_cc[VSTOP], CSTOP);
790 #endif
791 #if defined(VSUSP) && defined(CSUSP)
792     mode.c_cc[VSUSP] = CHK(mode.c_cc[VSUSP], CSUSP);
793 #endif
794 #if defined(VWERASE) && defined(CWERASE)
795     mode.c_cc[VWERASE] = CHK(mode.c_cc[VWERASE], CWERASE);
796 #endif
797 
798     mode.c_iflag &= ~((unsigned) (IGNBRK | PARMRK | INPCK | ISTRIP | INLCR | IGNCR
799 #ifdef IUCLC
800 				  | IUCLC
801 #endif
802 #ifdef IXANY
803 				  | IXANY
804 #endif
805 				  | IXOFF));
806 
807     mode.c_iflag |= (BRKINT | IGNPAR | ICRNL | IXON
808 #ifdef IMAXBEL
809 		     | IMAXBEL
810 #endif
811 	);
812 
813     mode.c_oflag &= ~((unsigned) (0
814 #ifdef OLCUC
815 				  | OLCUC
816 #endif
817 #ifdef OCRNL
818 				  | OCRNL
819 #endif
820 #ifdef ONOCR
821 				  | ONOCR
822 #endif
823 #ifdef ONLRET
824 				  | ONLRET
825 #endif
826 #ifdef OFILL
827 				  | OFILL
828 #endif
829 #ifdef OFDEL
830 				  | OFDEL
831 #endif
832 #ifdef NLDLY
833 				  | NLDLY
834 #endif
835 #ifdef CRDLY
836 				  | CRDLY
837 #endif
838 #ifdef TABDLY
839 				  | TABDLY
840 #endif
841 #ifdef BSDLY
842 				  | BSDLY
843 #endif
844 #ifdef VTDLY
845 				  | VTDLY
846 #endif
847 #ifdef FFDLY
848 				  | FFDLY
849 #endif
850 		      ));
851 
852     mode.c_oflag |= (OPOST
853 #ifdef ONLCR
854 		     | ONLCR
855 #endif
856 	);
857 
858     mode.c_cflag &= ~((unsigned) (CSIZE | CSTOPB | PARENB | PARODD | CLOCAL));
859     mode.c_cflag |= (CS8 | CREAD);
860     mode.c_lflag &= ~((unsigned) (ECHONL | NOFLSH
861 #ifdef TOSTOP
862 				  | TOSTOP
863 #endif
864 #ifdef ECHOPTR
865 				  | ECHOPRT
866 #endif
867 #ifdef XCASE
868 				  | XCASE
869 #endif
870 		      ));
871 
872     mode.c_lflag |= (ISIG | ICANON | ECHO | ECHOE | ECHOK
873 #ifdef ECHOCTL
874 		     | ECHOCTL
875 #endif
876 #ifdef ECHOKE
877 		     | ECHOKE
878 #endif
879 	);
880 #endif
881 
882     SET_TTY(STDERR_FILENO, &mode);
883 }
884 
885 /*
886  * Returns a "good" value for the erase character.  This is loosely based on
887  * the BSD4.4 logic.
888  */
889 #ifdef TERMIOS
890 static int
891 default_erase(void)
892 {
893     int result;
894 
895     if (over_strike
896 	&& key_backspace != 0
897 	&& strlen(key_backspace) == 1)
898 	result = key_backspace[0];
899     else
900 	result = CERASE;
901 
902     return result;
903 }
904 #endif
905 
906 /*
907  * Update the values of the erase, interrupt, and kill characters in 'mode'.
908  *
909  * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
910  * characters if they're unset, or if we specify them as options.  This differs
911  * from BSD 4.4 tset, which always sets erase.
912  */
913 static void
914 set_control_chars(void)
915 {
916 #ifdef TERMIOS
917     if (DISABLED(mode.c_cc[VERASE]) || terasechar >= 0) {
918 	mode.c_cc[VERASE] = UChar((terasechar >= 0)
919 				  ? terasechar
920 				  : default_erase());
921     }
922 
923     if (DISABLED(mode.c_cc[VINTR]) || intrchar >= 0) {
924 	mode.c_cc[VINTR] = UChar((intrchar >= 0)
925 				 ? intrchar
926 				 : CINTR);
927     }
928 
929     if (DISABLED(mode.c_cc[VKILL]) || tkillchar >= 0) {
930 	mode.c_cc[VKILL] = UChar((tkillchar >= 0)
931 				 ? tkillchar
932 				 : CKILL);
933     }
934 #endif
935 }
936 
937 /*
938  * Set up various conversions in 'mode', including parity, tabs, returns,
939  * echo, and case, according to the termcap entry.  If the program we're
940  * running was named with a leading upper-case character, map external
941  * uppercase to internal lowercase.
942  */
943 static void
944 set_conversions(void)
945 {
946 #ifdef __OBSOLETE__
947     /*
948      * Conversion logic for some *really* ancient terminal glitches,
949      * not supported in terminfo.  Left here for succeeding generations
950      * to marvel at.
951      */
952     if (tgetflag("UC")) {
953 #ifdef IUCLC
954 	mode.c_iflag |= IUCLC;
955 	mode.c_oflag |= OLCUC;
956 #endif
957     } else if (tgetflag("LC")) {
958 #ifdef IUCLC
959 	mode.c_iflag &= ~IUCLC;
960 	mode.c_oflag &= ~OLCUC;
961 #endif
962     }
963     mode.c_iflag &= ~(PARMRK | INPCK);
964     mode.c_lflag |= ICANON;
965     if (tgetflag("EP")) {
966 	mode.c_cflag |= PARENB;
967 	mode.c_cflag &= ~PARODD;
968     }
969     if (tgetflag("OP")) {
970 	mode.c_cflag |= PARENB;
971 	mode.c_cflag |= PARODD;
972     }
973 #endif /* __OBSOLETE__ */
974 
975 #ifdef TERMIOS
976 #ifdef ONLCR
977     mode.c_oflag |= ONLCR;
978 #endif
979     mode.c_iflag |= ICRNL;
980     mode.c_lflag |= ECHO;
981 #ifdef OXTABS
982     mode.c_oflag |= OXTABS;
983 #endif /* OXTABS */
984 
985     /* test used to be tgetflag("NL") */
986     if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
987 	/* Newline, not linefeed. */
988 #ifdef ONLCR
989 	mode.c_oflag &= ~((unsigned) ONLCR);
990 #endif
991 	mode.c_iflag &= ~((unsigned) ICRNL);
992     }
993 #ifdef __OBSOLETE__
994     if (tgetflag("HD"))		/* Half duplex. */
995 	mode.c_lflag &= ~ECHO;
996 #endif /* __OBSOLETE__ */
997 #ifdef OXTABS
998     /* test used to be tgetflag("pt") */
999     if (has_hardware_tabs)	/* Print tabs. */
1000 	mode.c_oflag &= ~OXTABS;
1001 #endif /* OXTABS */
1002     mode.c_lflag |= (ECHOE | ECHOK);
1003 #endif
1004 }
1005 
1006 /* Output startup string. */
1007 static void
1008 set_init(void)
1009 {
1010     char *p;
1011     bool settle;
1012 
1013 #ifdef __OBSOLETE__
1014     if (pad_char != (char *) 0)	/* Get/set pad character. */
1015 	PC = pad_char[0];
1016 #endif /* OBSOLETE */
1017 
1018 #ifdef TAB3
1019     if (oldmode.c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
1020 	oldmode.c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
1021 	SET_TTY(STDERR_FILENO, &oldmode);
1022     }
1023 #endif
1024     settle = set_tabs();
1025 
1026     if (isreset) {
1027 	if ((p = reset_1string) != 0) {
1028 	    tputs(p, 0, outc);
1029 	    settle = TRUE;
1030 	}
1031 	if ((p = reset_2string) != 0) {
1032 	    tputs(p, 0, outc);
1033 	    settle = TRUE;
1034 	}
1035 	/* What about rf, rs3, as per terminfo man page? */
1036 	/* also might be nice to send rmacs, rmul, rmm */
1037 	if ((p = reset_file) != 0
1038 	    || (p = init_file) != 0) {
1039 	    cat(p);
1040 	    settle = TRUE;
1041 	}
1042     }
1043 
1044     if (settle) {
1045 	(void) putc('\r', stderr);
1046 	(void) fflush(stderr);
1047 	(void) napms(1000);	/* Settle the terminal. */
1048     }
1049 }
1050 
1051 /*
1052  * Set the hardware tabs on the terminal, using the ct (clear all tabs),
1053  * st (set one tab) and ch (horizontal cursor addressing) capabilities.
1054  * This is done before if and is, so they can patch in case we blow this.
1055  * Return TRUE if we set any tab stops, FALSE if not.
1056  */
1057 static bool
1058 set_tabs(void)
1059 {
1060     if (set_tab && clear_all_tabs) {
1061 	int c;
1062 	int lim =
1063 #if HAVE_SIZECHANGE
1064 	tcolumns
1065 #else
1066 	columns
1067 #endif
1068 	 ;
1069 
1070 	(void) putc('\r', stderr);	/* Force to left margin. */
1071 	tputs(clear_all_tabs, 0, outc);
1072 
1073 	for (c = 8; c < lim; c += 8) {
1074 	    /* Get to the right column.  In BSD tset, this
1075 	     * used to try a bunch of half-clever things
1076 	     * with cup and hpa, for an average saving of
1077 	     * somewhat less than two character times per
1078 	     * tab stop, less than .01 sec at 2400cps. We
1079 	     * lost all this cruft because it seemed to be
1080 	     * introducing some odd bugs.
1081 	     * -----------12345678----------- */
1082 	    (void) fputs("        ", stderr);
1083 	    tputs(set_tab, 0, outc);
1084 	}
1085 	putc('\r', stderr);
1086 	return (TRUE);
1087     }
1088     return (FALSE);
1089 }
1090 
1091 /**************************************************************************
1092  *
1093  * Main sequence
1094  *
1095  **************************************************************************/
1096 
1097 /*
1098  * Tell the user if a control key has been changed from the default value.
1099  */
1100 #ifdef TERMIOS
1101 static void
1102 report(const char *name, int which, unsigned def)
1103 {
1104     unsigned older, newer;
1105     char *p;
1106 
1107     newer = mode.c_cc[which];
1108     older = oldmode.c_cc[which];
1109 
1110     if (older == newer && older == def)
1111 	return;
1112 
1113     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
1114 
1115     if (DISABLED(newer))
1116 	(void) fprintf(stderr, "undef.\n");
1117     /*
1118      * Check 'delete' before 'backspace', since the key_backspace value
1119      * is ambiguous.
1120      */
1121     else if (newer == 0177)
1122 	(void) fprintf(stderr, "delete.\n");
1123     else if ((p = key_backspace) != 0
1124 	     && newer == (unsigned char) p[0]
1125 	     && p[1] == '\0')
1126 	(void) fprintf(stderr, "backspace.\n");
1127     else if (newer < 040) {
1128 	newer ^= 0100;
1129 	(void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
1130     } else
1131 	(void) fprintf(stderr, "%c.\n", UChar(newer));
1132 }
1133 #endif
1134 
1135 /*
1136  * Convert the obsolete argument forms into something that getopt can handle.
1137  * This means that -e, -i and -k get default arguments supplied for them.
1138  */
1139 static void
1140 obsolete(char **argv)
1141 {
1142     for (; *argv; ++argv) {
1143 	char *parm = argv[0];
1144 
1145 	if (parm[0] == '-' && parm[1] == '\0') {
1146 	    argv[0] = strdup("-q");
1147 	    continue;
1148 	}
1149 
1150 	if ((parm[0] != '-')
1151 	    || (argv[1] && argv[1][0] != '-')
1152 	    || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
1153 	    || (parm[2] != '\0'))
1154 	    continue;
1155 	switch (argv[0][1]) {
1156 	case 'e':
1157 	    argv[0] = strdup("-e^H");
1158 	    break;
1159 	case 'i':
1160 	    argv[0] = strdup("-i^C");
1161 	    break;
1162 	case 'k':
1163 	    argv[0] = strdup("-k^U");
1164 	    break;
1165 	}
1166     }
1167 }
1168 
1169 static void
1170 usage(void)
1171 {
1172 #define DATA(s) s "\n"
1173     static const char msg[] =
1174     {
1175 	DATA("")
1176 	DATA("Options:")
1177 	DATA("  -c          set control characters")
1178 	DATA("  -e ch       erase character")
1179 	DATA("  -I          no initialization strings")
1180 	DATA("  -i ch       interrupt character")
1181 	DATA("  -k ch       kill character")
1182 	DATA("  -m mapping  map identifier to type")
1183 	DATA("  -Q          do not output control key settings")
1184 	DATA("  -r          display term on stderr")
1185 	DATA("  -s          output TERM set command")
1186 	DATA("  -V          print curses-version")
1187 	DATA("  -w          set window-size")
1188     };
1189 #undef DATA
1190     (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname);
1191     fputs(msg, stderr);
1192     exit_error();
1193     /* NOTREACHED */
1194 }
1195 
1196 static char
1197 arg_to_char(void)
1198 {
1199     return (char) ((optarg[0] == '^' && optarg[1] != '\0')
1200 		   ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
1201 		   : optarg[0]);
1202 }
1203 
1204 int
1205 main(int argc, char **argv)
1206 {
1207     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
1208     const char *p;
1209     const char *ttype;
1210 
1211     obsolete(argv);
1212     noinit = noset = quiet = Sflag = sflag = showterm = 0;
1213     while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:np:qQSrsVw")) != -1) {
1214 	switch (ch) {
1215 	case 'c':		/* set control-chars */
1216 	    opt_c = TRUE;
1217 	    break;
1218 	case 'a':		/* OBSOLETE: map identifier to type */
1219 	    add_mapping("arpanet", optarg);
1220 	    break;
1221 	case 'd':		/* OBSOLETE: map identifier to type */
1222 	    add_mapping("dialup", optarg);
1223 	    break;
1224 	case 'e':		/* erase character */
1225 	    terasechar = arg_to_char();
1226 	    break;
1227 	case 'I':		/* no initialization strings */
1228 	    noinit = 1;
1229 	    break;
1230 	case 'i':		/* interrupt character */
1231 	    intrchar = arg_to_char();
1232 	    break;
1233 	case 'k':		/* kill character */
1234 	    tkillchar = arg_to_char();
1235 	    break;
1236 	case 'm':		/* map identifier to type */
1237 	    add_mapping(0, optarg);
1238 	    break;
1239 	case 'n':		/* OBSOLETE: set new tty driver */
1240 	    break;
1241 	case 'p':		/* OBSOLETE: map identifier to type */
1242 	    add_mapping("plugboard", optarg);
1243 	    break;
1244 	case 'Q':		/* don't output control key settings */
1245 	    quiet = 1;
1246 	    break;
1247 	case 'q':		/* display term only */
1248 	    noset = 1;
1249 	    break;
1250 	case 'r':		/* display term on stderr */
1251 	    showterm = 1;
1252 	    break;
1253 	case 'S':		/* OBSOLETE: output TERM & TERMCAP */
1254 	    Sflag = 1;
1255 	    break;
1256 	case 's':		/* output TERM set command */
1257 	    sflag = 1;
1258 	    break;
1259 	case 'V':		/* print curses-version */
1260 	    puts(curses_version());
1261 	    ExitProgram(EXIT_SUCCESS);
1262 	case 'w':		/* set window-size */
1263 	    opt_w = TRUE;
1264 	    break;
1265 	case '?':
1266 	default:
1267 	    usage();
1268 	}
1269     }
1270 
1271     _nc_progname = _nc_rootname(*argv);
1272     argc -= optind;
1273     argv += optind;
1274 
1275     if (argc > 1)
1276 	usage();
1277 
1278     if (!opt_c && !opt_w)
1279 	opt_c = opt_w = TRUE;
1280 
1281     if (GET_TTY(STDERR_FILENO, &mode) < 0)
1282 	failed("standard error");
1283     can_restore = TRUE;
1284     original = oldmode = mode;
1285 #ifdef TERMIOS
1286     ospeed = (NCURSES_OSPEED) cfgetospeed(&mode);
1287 #else
1288     ospeed = (NCURSES_OSPEED) mode.sg_ospeed;
1289 #endif
1290 
1291     if (same_program(_nc_progname, PROG_RESET)) {
1292 	isreset = TRUE;
1293 	reset_mode();
1294     }
1295 
1296     ttype = get_termcap_entry(*argv);
1297 
1298     if (!noset) {
1299 #if HAVE_SIZECHANGE
1300 	tcolumns = columns;
1301 	tlines = lines;
1302 
1303 	if (opt_w) {
1304 	    STRUCT_WINSIZE win;
1305 	    /* Set window size if not set already */
1306 	    (void) ioctl(STDERR_FILENO, IOCTL_GET_WINSIZE, &win);
1307 	    if (WINSIZE_ROWS(win) == 0 &&
1308 		WINSIZE_COLS(win) == 0 &&
1309 		tlines > 0 && tcolumns > 0) {
1310 		WINSIZE_ROWS(win) = (unsigned short) tlines;
1311 		WINSIZE_COLS(win) = (unsigned short) tcolumns;
1312 		(void) ioctl(STDERR_FILENO, IOCTL_SET_WINSIZE, &win);
1313 	    }
1314 	}
1315 #endif
1316 	if (opt_c) {
1317 	    set_control_chars();
1318 	    set_conversions();
1319 
1320 	    if (!noinit)
1321 		set_init();
1322 
1323 	    /* Set the modes if they've changed. */
1324 	    if (memcmp(&mode, &oldmode, sizeof(mode))) {
1325 		SET_TTY(STDERR_FILENO, &mode);
1326 	    }
1327 	}
1328     }
1329 
1330     if (noset)
1331 	(void) printf("%s\n", ttype);
1332     else {
1333 	if (showterm)
1334 	    (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
1335 	/*
1336 	 * If erase, kill and interrupt characters could have been
1337 	 * modified and not -Q, display the changes.
1338 	 */
1339 #ifdef TERMIOS
1340 	if (!quiet) {
1341 	    report("Erase", VERASE, CERASE);
1342 	    report("Kill", VKILL, CKILL);
1343 	    report("Interrupt", VINTR, CINTR);
1344 	}
1345 #endif
1346     }
1347 
1348     if (Sflag)
1349 	err("The -S option is not supported under terminfo.");
1350 
1351     if (sflag) {
1352 	int len;
1353 	char *var;
1354 	char *leaf;
1355 	/*
1356 	 * Figure out what shell we're using.  A hack, we look for an
1357 	 * environmental variable SHELL ending in "csh".
1358 	 */
1359 	if ((var = getenv("SHELL")) != 0
1360 	    && ((len = (int) strlen(leaf = _nc_basename(var))) >= 3)
1361 	    && !strcmp(leaf + len - 3, "csh"))
1362 	    p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
1363 	else
1364 	    p = "TERM=%s;\n";
1365 	(void) printf(p, ttype);
1366     }
1367 
1368     ExitProgram(EXIT_SUCCESS);
1369 }
1370