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