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