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