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