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