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