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