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