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