2ee9c1190ba0aff9c7f0d8590ef27c757b6540d0
[ncurses.git] / progs / tset.c
1 /****************************************************************************
2  * Copyright (c) 1998-2011,2012 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.87 2012/01/21 23:55:05 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         strcpy(temp, _nc_progname);
217         strcat(temp, ": ");
218     } else {
219         strcpy(temp, "tset: ");
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 typedef struct speeds {
311     const char *string;
312     int speed;
313 } SPEEDS;
314
315 static 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
374 static int
375 tbaudrate(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  */
400 static void
401 add_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     if (arg == (char *) 0)      /* Non-optional type. */
478         goto badmopt;
479
480     mapp->type = arg;
481
482     /* Terminate porttype, if specified. */
483     if (termp != 0)
484         *base = '\0';
485
486     /* If a NOT conditional, reverse the test. */
487     if (mapp->conditional & NOT)
488         mapp->conditional = ~mapp->conditional & (EQ | GT | LT);
489
490     /* If user specified a port with an option flag, set it. */
491   done:
492     if (port) {
493         if (mapp->porttype) {
494           badmopt:
495             err("illegal -m option format: %s", copy);
496         }
497         mapp->porttype = port;
498     }
499     free(copy);
500 #ifdef MAPDEBUG
501     (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY");
502     (void) printf("type: %s\n", mapp->type);
503     (void) printf("conditional: ");
504     p = "";
505     if (mapp->conditional & GT) {
506         (void) printf("GT");
507         p = "/";
508     }
509     if (mapp->conditional & EQ) {
510         (void) printf("%sEQ", p);
511         p = "/";
512     }
513     if (mapp->conditional & LT)
514         (void) printf("%sLT", p);
515     (void) printf("\nspeed: %d\n", mapp->speed);
516 #endif
517 }
518
519 /*
520  * Return the type of terminal to use for a port of type 'type', as specified
521  * by the first applicable mapping in 'map'.  If no mappings apply, return
522  * 'type'.
523  */
524 static const char *
525 mapped(const char *type)
526 {
527     MAP *mapp;
528     int match;
529
530     for (mapp = maplist; mapp; mapp = mapp->next)
531         if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) {
532             switch (mapp->conditional) {
533             case 0:             /* No test specified. */
534                 match = TRUE;
535                 break;
536             case EQ:
537                 match = (ospeed == mapp->speed);
538                 break;
539             case GE:
540                 match = (ospeed >= mapp->speed);
541                 break;
542             case GT:
543                 match = (ospeed > mapp->speed);
544                 break;
545             case LE:
546                 match = (ospeed <= mapp->speed);
547                 break;
548             case LT:
549                 match = (ospeed < mapp->speed);
550                 break;
551             default:
552                 match = FALSE;
553             }
554             if (match)
555                 return (mapp->type);
556         }
557     /* No match found; return given type. */
558     return (type);
559 }
560
561 /**************************************************************************
562  *
563  * Entry fetching
564  *
565  **************************************************************************/
566
567 /*
568  * Figure out what kind of terminal we're dealing with, and then read in
569  * its termcap entry.
570  */
571 static const char *
572 get_termcap_entry(char *userarg)
573 {
574     int errret;
575     char *p;
576     const char *ttype;
577 #if HAVE_GETTTYNAM
578     struct ttyent *t;
579 #else
580     FILE *fp;
581 #endif
582     char *ttypath;
583
584     if (userarg) {
585         ttype = userarg;
586         goto found;
587     }
588
589     /* Try the environment. */
590     if ((ttype = getenv("TERM")) != 0)
591         goto map;
592
593     if ((ttypath = ttyname(STDERR_FILENO)) != 0) {
594         p = _nc_basename(ttypath);
595 #if HAVE_GETTTYNAM
596         /*
597          * We have the 4.3BSD library call getttynam(3); that means
598          * there's an /etc/ttys to look up device-to-type mappings in.
599          * Try ttyname(3); check for dialup or other mapping.
600          */
601         if ((t = getttynam(p))) {
602             ttype = t->ty_type;
603             goto map;
604         }
605 #else
606         if ((fp = fopen("/etc/ttytype", "r")) != 0
607             || (fp = fopen("/etc/ttys", "r")) != 0) {
608             char buffer[BUFSIZ];
609             char *s, *t, *d;
610
611             while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) {
612                 for (s = buffer, t = d = 0; *s; s++) {
613                     if (isspace(UChar(*s)))
614                         *s = '\0';
615                     else if (t == 0)
616                         t = s;
617                     else if (d == 0 && s != buffer && s[-1] == '\0')
618                         d = s;
619                 }
620                 if (t != 0 && d != 0 && !strcmp(d, p)) {
621                     ttype = strdup(t);
622                     fclose(fp);
623                     goto map;
624                 }
625             }
626             fclose(fp);
627         }
628 #endif /* HAVE_GETTTYNAM */
629     }
630
631     /* If still undefined, use "unknown". */
632     ttype = "unknown";
633
634   map:ttype = mapped(ttype);
635
636     /*
637      * If not a path, remove TERMCAP from the environment so we get a
638      * real entry from /etc/termcap.  This prevents us from being fooled
639      * by out of date stuff in the environment.
640      */
641   found: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], 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)   (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 &= ~(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 &= ~(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 &= ~(CSIZE | CSTOPB | PARENB | PARODD | CLOCAL);
859     mode.c_cflag |= (CS8 | CREAD);
860     mode.c_lflag &= ~(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] = (terasechar >= 0) ? terasechar : default_erase();
919
920     if (DISABLED(mode.c_cc[VINTR]) || intrchar >= 0)
921         mode.c_cc[VINTR] = (intrchar >= 0) ? intrchar : CINTR;
922
923     if (DISABLED(mode.c_cc[VKILL]) || tkillchar >= 0)
924         mode.c_cc[VKILL] = (tkillchar >= 0) ? tkillchar : CKILL;
925 #endif
926 }
927
928 /*
929  * Set up various conversions in 'mode', including parity, tabs, returns,
930  * echo, and case, according to the termcap entry.  If the program we're
931  * running was named with a leading upper-case character, map external
932  * uppercase to internal lowercase.
933  */
934 static void
935 set_conversions(void)
936 {
937 #ifdef __OBSOLETE__
938     /*
939      * Conversion logic for some *really* ancient terminal glitches,
940      * not supported in terminfo.  Left here for succeeding generations
941      * to marvel at.
942      */
943     if (tgetflag("UC")) {
944 #ifdef IUCLC
945         mode.c_iflag |= IUCLC;
946         mode.c_oflag |= OLCUC;
947 #endif
948     } else if (tgetflag("LC")) {
949 #ifdef IUCLC
950         mode.c_iflag &= ~IUCLC;
951         mode.c_oflag &= ~OLCUC;
952 #endif
953     }
954     mode.c_iflag &= ~(PARMRK | INPCK);
955     mode.c_lflag |= ICANON;
956     if (tgetflag("EP")) {
957         mode.c_cflag |= PARENB;
958         mode.c_cflag &= ~PARODD;
959     }
960     if (tgetflag("OP")) {
961         mode.c_cflag |= PARENB;
962         mode.c_cflag |= PARODD;
963     }
964 #endif /* __OBSOLETE__ */
965
966 #ifdef TERMIOS
967 #ifdef ONLCR
968     mode.c_oflag |= ONLCR;
969 #endif
970     mode.c_iflag |= ICRNL;
971     mode.c_lflag |= ECHO;
972 #ifdef OXTABS
973     mode.c_oflag |= OXTABS;
974 #endif /* OXTABS */
975
976     /* test used to be tgetflag("NL") */
977     if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
978         /* Newline, not linefeed. */
979 #ifdef ONLCR
980         mode.c_oflag &= ~ONLCR;
981 #endif
982         mode.c_iflag &= ~ICRNL;
983     }
984 #ifdef __OBSOLETE__
985     if (tgetflag("HD"))         /* Half duplex. */
986         mode.c_lflag &= ~ECHO;
987 #endif /* __OBSOLETE__ */
988 #ifdef OXTABS
989     /* test used to be tgetflag("pt") */
990     if (has_hardware_tabs)      /* Print tabs. */
991         mode.c_oflag &= ~OXTABS;
992 #endif /* OXTABS */
993     mode.c_lflag |= (ECHOE | ECHOK);
994 #endif
995 }
996
997 /* Output startup string. */
998 static void
999 set_init(void)
1000 {
1001     char *p;
1002     bool settle;
1003
1004 #ifdef __OBSOLETE__
1005     if (pad_char != (char *) 0) /* Get/set pad character. */
1006         PC = pad_char[0];
1007 #endif /* OBSOLETE */
1008
1009 #ifdef TAB3
1010     if (oldmode.c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
1011         oldmode.c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
1012         SET_TTY(STDERR_FILENO, &oldmode);
1013     }
1014 #endif
1015     settle = set_tabs();
1016
1017     if (isreset) {
1018         if ((p = reset_1string) != 0) {
1019             tputs(p, 0, outc);
1020             settle = TRUE;
1021         }
1022         if ((p = reset_2string) != 0) {
1023             tputs(p, 0, outc);
1024             settle = TRUE;
1025         }
1026         /* What about rf, rs3, as per terminfo man page? */
1027         /* also might be nice to send rmacs, rmul, rmm */
1028         if ((p = reset_file) != 0
1029             || (p = init_file) != 0) {
1030             cat(p);
1031             settle = TRUE;
1032         }
1033     }
1034
1035     if (settle) {
1036         (void) putc('\r', stderr);
1037         (void) fflush(stderr);
1038         (void) napms(1000);     /* Settle the terminal. */
1039     }
1040 }
1041
1042 /*
1043  * Set the hardware tabs on the terminal, using the ct (clear all tabs),
1044  * st (set one tab) and ch (horizontal cursor addressing) capabilities.
1045  * This is done before if and is, so they can patch in case we blow this.
1046  * Return TRUE if we set any tab stops, FALSE if not.
1047  */
1048 static bool
1049 set_tabs(void)
1050 {
1051     if (set_tab && clear_all_tabs) {
1052         int c;
1053         int lim =
1054 #if HAVE_SIZECHANGE
1055         tcolumns
1056 #else
1057         columns
1058 #endif
1059          ;
1060
1061         (void) putc('\r', stderr);      /* Force to left margin. */
1062         tputs(clear_all_tabs, 0, outc);
1063
1064         for (c = 8; c < lim; c += 8) {
1065             /* Get to the right column.  In BSD tset, this
1066              * used to try a bunch of half-clever things
1067              * with cup and hpa, for an average saving of
1068              * somewhat less than two character times per
1069              * tab stop, less than .01 sec at 2400cps. We
1070              * lost all this cruft because it seemed to be
1071              * introducing some odd bugs.
1072              * -----------12345678----------- */
1073             (void) fputs("        ", stderr);
1074             tputs(set_tab, 0, outc);
1075         }
1076         putc('\r', stderr);
1077         return (TRUE);
1078     }
1079     return (FALSE);
1080 }
1081
1082 /**************************************************************************
1083  *
1084  * Main sequence
1085  *
1086  **************************************************************************/
1087
1088 /*
1089  * Tell the user if a control key has been changed from the default value.
1090  */
1091 #ifdef TERMIOS
1092 static void
1093 report(const char *name, int which, unsigned def)
1094 {
1095     unsigned older, newer;
1096     char *p;
1097
1098     newer = mode.c_cc[which];
1099     older = oldmode.c_cc[which];
1100
1101     if (older == newer && older == def)
1102         return;
1103
1104     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
1105
1106     if (DISABLED(newer))
1107         (void) fprintf(stderr, "undef.\n");
1108     /*
1109      * Check 'delete' before 'backspace', since the key_backspace value
1110      * is ambiguous.
1111      */
1112     else if (newer == 0177)
1113         (void) fprintf(stderr, "delete.\n");
1114     else if ((p = key_backspace) != 0
1115              && newer == (unsigned char) p[0]
1116              && p[1] == '\0')
1117         (void) fprintf(stderr, "backspace.\n");
1118     else if (newer < 040) {
1119         newer ^= 0100;
1120         (void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
1121     } else
1122         (void) fprintf(stderr, "%c.\n", UChar(newer));
1123 }
1124 #endif
1125
1126 /*
1127  * Convert the obsolete argument forms into something that getopt can handle.
1128  * This means that -e, -i and -k get default arguments supplied for them.
1129  */
1130 static void
1131 obsolete(char **argv)
1132 {
1133     for (; *argv; ++argv) {
1134         char *parm = argv[0];
1135
1136         if (parm[0] == '-' && parm[1] == '\0') {
1137             argv[0] = strdup("-q");
1138             continue;
1139         }
1140
1141         if ((parm[0] != '-')
1142             || (argv[1] && argv[1][0] != '-')
1143             || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k')
1144             || (parm[2] != '\0'))
1145             continue;
1146         switch (argv[0][1]) {
1147         case 'e':
1148             argv[0] = strdup("-e^H");
1149             break;
1150         case 'i':
1151             argv[0] = strdup("-i^C");
1152             break;
1153         case 'k':
1154             argv[0] = strdup("-k^U");
1155             break;
1156         }
1157     }
1158 }
1159
1160 static void
1161 usage(void)
1162 {
1163     static const char *tbl[] =
1164     {
1165         ""
1166         ,"Options:"
1167         ,"  -c          set control characters"
1168         ,"  -e ch       erase character"
1169         ,"  -I          no initialization strings"
1170         ,"  -i ch       interrupt character"
1171         ,"  -k ch       kill character"
1172         ,"  -m mapping  map identifier to type"
1173         ,"  -Q          do not output control key settings"
1174         ,"  -r          display term on stderr"
1175         ,"  -s          output TERM set command"
1176         ,"  -V          print curses-version"
1177         ,"  -w          set window-size"
1178     };
1179     unsigned n;
1180     (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname);
1181     for (n = 0; n < sizeof(tbl) / sizeof(tbl[0]); ++n)
1182         fprintf(stderr, "%s\n", tbl[n]);
1183     exit_error();
1184     /* NOTREACHED */
1185 }
1186
1187 static char
1188 arg_to_char(void)
1189 {
1190     return (char) ((optarg[0] == '^' && optarg[1] != '\0')
1191                    ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1]))
1192                    : optarg[0]);
1193 }
1194
1195 int
1196 main(int argc, char **argv)
1197 {
1198     int ch, noinit, noset, quiet, Sflag, sflag, showterm;
1199     const char *p;
1200     const char *ttype;
1201
1202     obsolete(argv);
1203     noinit = noset = quiet = Sflag = sflag = showterm = 0;
1204     while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:np:qQSrsVw")) != -1) {
1205         switch (ch) {
1206         case 'c':               /* set control-chars */
1207             opt_c = TRUE;
1208             break;
1209         case 'a':               /* OBSOLETE: map identifier to type */
1210             add_mapping("arpanet", optarg);
1211             break;
1212         case 'd':               /* OBSOLETE: map identifier to type */
1213             add_mapping("dialup", optarg);
1214             break;
1215         case 'e':               /* erase character */
1216             terasechar = arg_to_char();
1217             break;
1218         case 'I':               /* no initialization strings */
1219             noinit = 1;
1220             break;
1221         case 'i':               /* interrupt character */
1222             intrchar = arg_to_char();
1223             break;
1224         case 'k':               /* kill character */
1225             tkillchar = arg_to_char();
1226             break;
1227         case 'm':               /* map identifier to type */
1228             add_mapping(0, optarg);
1229             break;
1230         case 'n':               /* OBSOLETE: set new tty driver */
1231             break;
1232         case 'p':               /* OBSOLETE: map identifier to type */
1233             add_mapping("plugboard", optarg);
1234             break;
1235         case 'Q':               /* don't output control key settings */
1236             quiet = 1;
1237             break;
1238         case 'q':               /* display term only */
1239             noset = 1;
1240             break;
1241         case 'r':               /* display term on stderr */
1242             showterm = 1;
1243             break;
1244         case 'S':               /* OBSOLETE: output TERM & TERMCAP */
1245             Sflag = 1;
1246             break;
1247         case 's':               /* output TERM set command */
1248             sflag = 1;
1249             break;
1250         case 'V':               /* print curses-version */
1251             puts(curses_version());
1252             ExitProgram(EXIT_SUCCESS);
1253         case 'w':               /* set window-size */
1254             opt_w = TRUE;
1255             break;
1256         case '?':
1257         default:
1258             usage();
1259         }
1260     }
1261
1262     _nc_progname = _nc_rootname(*argv);
1263     argc -= optind;
1264     argv += optind;
1265
1266     if (argc > 1)
1267         usage();
1268
1269     if (!opt_c && !opt_w)
1270         opt_c = opt_w = TRUE;
1271
1272     if (GET_TTY(STDERR_FILENO, &mode) < 0)
1273         failed("standard error");
1274     can_restore = TRUE;
1275     original = oldmode = mode;
1276 #ifdef TERMIOS
1277     ospeed = (NCURSES_OSPEED) cfgetospeed(&mode);
1278 #else
1279     ospeed = (NCURSES_OSPEED) mode.sg_ospeed;
1280 #endif
1281
1282     if (same_program(_nc_progname, PROG_RESET)) {
1283         isreset = TRUE;
1284         reset_mode();
1285     }
1286
1287     (void) get_termcap_entry(*argv);
1288
1289     if (!noset) {
1290 #if HAVE_SIZECHANGE
1291         tcolumns = columns;
1292         tlines = lines;
1293
1294         if (opt_w) {
1295             STRUCT_WINSIZE win;
1296             /* Set window size if not set already */
1297             (void) ioctl(STDERR_FILENO, IOCTL_GET_WINSIZE, &win);
1298             if (WINSIZE_ROWS(win) == 0 &&
1299                 WINSIZE_COLS(win) == 0 &&
1300                 tlines > 0 && tcolumns > 0) {
1301                 WINSIZE_ROWS(win) = tlines;
1302                 WINSIZE_COLS(win) = tcolumns;
1303                 (void) ioctl(STDERR_FILENO, IOCTL_SET_WINSIZE, &win);
1304             }
1305         }
1306 #endif
1307         if (opt_c) {
1308             set_control_chars();
1309             set_conversions();
1310
1311             if (!noinit)
1312                 set_init();
1313
1314             /* Set the modes if they've changed. */
1315             if (memcmp(&mode, &oldmode, sizeof(mode))) {
1316                 SET_TTY(STDERR_FILENO, &mode);
1317             }
1318         }
1319     }
1320
1321     /* Get the terminal name from the entry. */
1322     ttype = _nc_first_name(cur_term->type.term_names);
1323
1324     if (noset)
1325         (void) printf("%s\n", ttype);
1326     else {
1327         if (showterm)
1328             (void) fprintf(stderr, "Terminal type is %s.\n", ttype);
1329         /*
1330          * If erase, kill and interrupt characters could have been
1331          * modified and not -Q, display the changes.
1332          */
1333 #ifdef TERMIOS
1334         if (!quiet) {
1335             report("Erase", VERASE, CERASE);
1336             report("Kill", VKILL, CKILL);
1337             report("Interrupt", VINTR, CINTR);
1338         }
1339 #endif
1340     }
1341
1342     if (Sflag)
1343         err("The -S option is not supported under terminfo.");
1344
1345     if (sflag) {
1346         int len;
1347         char *var;
1348         char *leaf;
1349         /*
1350          * Figure out what shell we're using.  A hack, we look for an
1351          * environmental variable SHELL ending in "csh".
1352          */
1353         if ((var = getenv("SHELL")) != 0
1354             && ((len = (int) strlen(leaf = _nc_basename(var))) >= 3)
1355             && !strcmp(leaf + len - 3, "csh"))
1356             p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n";
1357         else
1358             p = "TERM=%s;\n";
1359         (void) printf(p, ttype);
1360     }
1361
1362     ExitProgram(EXIT_SUCCESS);
1363 }