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