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