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