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