/**************************************************************************** * Copyright (c) 1998-2016,2017 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, distribute with modifications, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ /**************************************************************************** * Author: Zeyd M. Ben-Halim 1992,1995 * * and: Eric S. Raymond * * and: Thomas E. Dickey 1996-on * ****************************************************************************/ /* * Notes: * The initial adaptation from 4.4BSD Lite sources in September 1995 used 686 * lines from that version, and made changes/additions for 150 lines. There * was no reformatting, so with/without ignoring whitespace, the amount of * change is the same. * * Comparing with current (2009) source, excluding this comment: * a) 209 lines match identically to the 4.4BSD Lite sources, with 771 lines * changed/added. * a) Ignoring whitespace, the current version still uses 516 lines from the * 4.4BSD Lite sources, with 402 lines changed/added. * * Raymond's original comment on this follows... */ /* * tset.c - terminal initialization utility * * This code was mostly swiped from 4.4BSD tset, with some obsolescent * cruft removed and substantial portions rewritten. A Regents of the * University of California copyright applies to some portions of the * code, and is reproduced below: */ /*- * Copyright (c) 1980, 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #if HAVE_GETTTYNAM && HAVE_TTYENT_H #include #endif #ifdef NeXT char *ttyname(int fd); #endif MODULE_ID("$Id: tset.c,v 1.120 2017/10/08 00:01:29 tom Exp $") #ifndef environ extern char **environ; #endif const char *_nc_progname = "tset"; #define LOWERCASE(c) ((isalpha(UChar(c)) && isupper(UChar(c))) ? tolower(UChar(c)) : (c)) static void exit_error(void) GCC_NORETURN; static int CaselessCmp(const char *a, const char *b) { /* strcasecmp isn't portable */ while (*a && *b) { int cmp = LOWERCASE(*a) - LOWERCASE(*b); if (cmp != 0) break; a++, b++; } return LOWERCASE(*a) - LOWERCASE(*b); } static void exit_error(void) { restore_tty_settings(); (void) fprintf(stderr, "\n"); fflush(stderr); ExitProgram(EXIT_FAILURE); /* NOTREACHED */ } static void err(const char *fmt,...) { va_list ap; va_start(ap, fmt); (void) fprintf(stderr, "%s: ", _nc_progname); (void) vfprintf(stderr, fmt, ap); va_end(ap); exit_error(); /* NOTREACHED */ } static void failed(const char *msg) { char temp[BUFSIZ]; size_t len = strlen(_nc_progname) + 2; if ((int) len < (int) sizeof(temp) - 12) { _nc_STRCPY(temp, _nc_progname, sizeof(temp)); _nc_STRCAT(temp, ": ", sizeof(temp)); } else { _nc_STRCPY(temp, "tset: ", sizeof(temp)); } _nc_STRNCAT(temp, msg, sizeof(temp), sizeof(temp) - strlen(temp) - 2); perror(temp); exit_error(); /* NOTREACHED */ } /* Prompt the user for a terminal type. */ static const char * askuser(const char *dflt) { static char answer[256]; char *p; /* We can get recalled; if so, don't continue uselessly. */ clearerr(stdin); if (feof(stdin) || ferror(stdin)) { (void) fprintf(stderr, "\n"); exit_error(); /* NOTREACHED */ } for (;;) { if (dflt) (void) fprintf(stderr, "Terminal type? [%s] ", dflt); else (void) fprintf(stderr, "Terminal type? "); (void) fflush(stderr); if (fgets(answer, sizeof(answer), stdin) == 0) { if (dflt == 0) { exit_error(); /* NOTREACHED */ } return (dflt); } if ((p = strchr(answer, '\n')) != 0) *p = '\0'; if (answer[0]) return (answer); if (dflt != 0) return (dflt); } } /************************************************************************** * * Mapping logic begins here * **************************************************************************/ /* Baud rate conditionals for mapping. */ #define GT 0x01 #define EQ 0x02 #define LT 0x04 #define NOT 0x08 #define GE (GT | EQ) #define LE (LT | EQ) typedef struct map { struct map *next; /* Linked list of maps. */ const char *porttype; /* Port type, or "" for any. */ const char *type; /* Terminal type to select. */ int conditional; /* Baud rate conditionals bitmask. */ int speed; /* Baud rate to compare against. */ } MAP; static MAP *cur, *maplist; #define DATA(name,value) { { name }, value } typedef struct speeds { const char string[7]; int speed; } SPEEDS; static const SPEEDS speeds[] = { DATA("0", B0), DATA("50", B50), DATA("75", B75), DATA("110", B110), DATA("134", B134), DATA("134.5", B134), DATA("150", B150), DATA("200", B200), DATA("300", B300), DATA("600", B600), DATA("1200", B1200), DATA("1800", B1800), DATA("2400", B2400), DATA("4800", B4800), DATA("9600", B9600), /* sgttyb may define up to this point */ #ifdef B19200 DATA("19200", B19200), #endif #ifdef B38400 DATA("38400", B38400), #endif #ifdef B19200 DATA("19200", B19200), #endif #ifdef B38400 DATA("38400", B38400), #endif #ifdef B19200 DATA("19200", B19200), #else #ifdef EXTA DATA("19200", EXTA), #endif #endif #ifdef B38400 DATA("38400", B38400), #else #ifdef EXTB DATA("38400", EXTB), #endif #endif #ifdef B57600 DATA("57600", B57600), #endif #ifdef B76800 DATA("76800", B57600), #endif #ifdef B115200 DATA("115200", B115200), #endif #ifdef B153600 DATA("153600", B153600), #endif #ifdef B230400 DATA("230400", B230400), #endif #ifdef B307200 DATA("307200", B307200), #endif #ifdef B460800 DATA("460800", B460800), #endif #ifdef B500000 DATA("500000", B500000), #endif #ifdef B576000 DATA("576000", B576000), #endif #ifdef B921600 DATA("921600", B921600), #endif #ifdef B1000000 DATA("1000000", B1000000), #endif #ifdef B1152000 DATA("1152000", B1152000), #endif #ifdef B1500000 DATA("1500000", B1500000), #endif #ifdef B2000000 DATA("2000000", B2000000), #endif #ifdef B2500000 DATA("2500000", B2500000), #endif #ifdef B3000000 DATA("3000000", B3000000), #endif #ifdef B3500000 DATA("3500000", B3500000), #endif #ifdef B4000000 DATA("4000000", B4000000), #endif }; #undef DATA static int tbaudrate(char *rate) { const SPEEDS *sp = 0; size_t n; /* The baudrate number can be preceded by a 'B', which is ignored. */ if (*rate == 'B') ++rate; for (n = 0; n < SIZEOF(speeds); ++n) { if (n > 0 && (speeds[n].speed <= speeds[n - 1].speed)) { /* if the speeds are not increasing, likely a numeric overflow */ break; } if (!CaselessCmp(rate, speeds[n].string)) { sp = speeds + n; break; } } if (sp == 0) err("unknown baud rate %s", rate); return (sp->speed); } /* * Syntax for -m: * [port-type][test baudrate]:terminal-type * The baud rate tests are: >, <, @, =, ! */ static void add_mapping(const char *port, char *arg) { MAP *mapp; char *copy, *p; const char *termp; char *base = 0; copy = strdup(arg); mapp = typeMalloc(MAP, 1); if (copy == 0 || mapp == 0) failed("malloc"); assert(copy != 0); assert(mapp != 0); mapp->next = 0; if (maplist == 0) cur = maplist = mapp; else { cur->next = mapp; cur = mapp; } mapp->porttype = arg; mapp->conditional = 0; arg = strpbrk(arg, "><@=!:"); if (arg == 0) { /* [?]term */ mapp->type = mapp->porttype; mapp->porttype = 0; goto done; } if (arg == mapp->porttype) /* [><@=! baud]:term */ termp = mapp->porttype = 0; else termp = base = arg; for (;; ++arg) { /* Optional conditionals. */ switch (*arg) { case '<': if (mapp->conditional & GT) goto badmopt; mapp->conditional |= LT; break; case '>': if (mapp->conditional & LT) goto badmopt; mapp->conditional |= GT; break; case '@': case '=': /* Not documented. */ mapp->conditional |= EQ; break; case '!': mapp->conditional |= NOT; break; default: goto next; } } next: if (*arg == ':') { if (mapp->conditional) goto badmopt; ++arg; } else { /* Optional baudrate. */ arg = strchr(p = arg, ':'); if (arg == 0) goto badmopt; *arg++ = '\0'; mapp->speed = tbaudrate(p); } mapp->type = arg; /* Terminate porttype, if specified. */ if (termp != 0) *base = '\0'; /* If a NOT conditional, reverse the test. */ if (mapp->conditional & NOT) mapp->conditional = ~mapp->conditional & (EQ | GT | LT); /* If user specified a port with an option flag, set it. */ done: if (port) { if (mapp->porttype) { badmopt: err("illegal -m option format: %s", copy); } mapp->porttype = port; } free(copy); #ifdef MAPDEBUG (void) printf("port: %s\n", mapp->porttype ? mapp->porttype : "ANY"); (void) printf("type: %s\n", mapp->type); (void) printf("conditional: "); p = ""; if (mapp->conditional & GT) { (void) printf("GT"); p = "/"; } if (mapp->conditional & EQ) { (void) printf("%sEQ", p); p = "/"; } if (mapp->conditional & LT) (void) printf("%sLT", p); (void) printf("\nspeed: %d\n", mapp->speed); #endif } /* * Return the type of terminal to use for a port of type 'type', as specified * by the first applicable mapping in 'map'. If no mappings apply, return * 'type'. */ static const char * mapped(const char *type) { MAP *mapp; int match; for (mapp = maplist; mapp; mapp = mapp->next) if (mapp->porttype == 0 || !strcmp(mapp->porttype, type)) { switch (mapp->conditional) { case 0: /* No test specified. */ match = TRUE; break; case EQ: match = ((int) ospeed == mapp->speed); break; case GE: match = ((int) ospeed >= mapp->speed); break; case GT: match = ((int) ospeed > mapp->speed); break; case LE: match = ((int) ospeed <= mapp->speed); break; case LT: match = ((int) ospeed < mapp->speed); break; default: match = FALSE; } if (match) return (mapp->type); } /* No match found; return given type. */ return (type); } /************************************************************************** * * Entry fetching * **************************************************************************/ /* * Figure out what kind of terminal we're dealing with, and then read in * its termcap entry. */ static const char * get_termcap_entry(int fd, char *userarg) { int errret; char *p; const char *ttype; #if HAVE_GETTTYNAM struct ttyent *t; #else FILE *fp; #endif char *ttypath; (void) fd; if (userarg) { ttype = userarg; goto found; } /* Try the environment. */ if ((ttype = getenv("TERM")) != 0) goto map; if ((ttypath = ttyname(fd)) != 0) { p = _nc_basename(ttypath); #if HAVE_GETTTYNAM /* * We have the 4.3BSD library call getttynam(3); that means * there's an /etc/ttys to look up device-to-type mappings in. * Try ttyname(3); check for dialup or other mapping. */ if ((t = getttynam(p))) { ttype = t->ty_type; goto map; } #else if ((fp = fopen("/etc/ttytype", "r")) != 0 || (fp = fopen("/etc/ttys", "r")) != 0) { char buffer[BUFSIZ]; char *s, *t, *d; while (fgets(buffer, sizeof(buffer) - 1, fp) != 0) { for (s = buffer, t = d = 0; *s; s++) { if (isspace(UChar(*s))) *s = '\0'; else if (t == 0) t = s; else if (d == 0 && s != buffer && s[-1] == '\0') d = s; } if (t != 0 && d != 0 && !strcmp(d, p)) { ttype = strdup(t); fclose(fp); goto map; } } fclose(fp); } #endif /* HAVE_GETTTYNAM */ } /* If still undefined, use "unknown". */ ttype = "unknown"; map:ttype = mapped(ttype); /* * If not a path, remove TERMCAP from the environment so we get a * real entry from /etc/termcap. This prevents us from being fooled * by out of date stuff in the environment. */ found: if ((p = getenv("TERMCAP")) != 0 && !_nc_is_abs_path(p)) { /* 'unsetenv("TERMCAP")' is not portable. * The 'environ' array is better. */ int n; for (n = 0; environ[n] != 0; n++) { if (!strncmp("TERMCAP=", environ[n], (size_t) 8)) { while ((environ[n] = environ[n + 1]) != 0) { n++; } break; } } } /* * ttype now contains a pointer to the type of the terminal. * If the first character is '?', ask the user. */ if (ttype[0] == '?') { if (ttype[1] != '\0') ttype = askuser(ttype + 1); else ttype = askuser(0); } /* Find the terminfo entry. If it doesn't exist, ask the user. */ while (setupterm((NCURSES_CONST char *) ttype, fd, &errret) != OK) { if (errret == 0) { (void) fprintf(stderr, "%s: unknown terminal type %s\n", _nc_progname, ttype); ttype = 0; } else { (void) fprintf(stderr, "%s: can't initialize terminal type %s (error %d)\n", _nc_progname, ttype, errret); ttype = 0; } ttype = askuser(ttype); } #if BROKEN_LINKER tgetflag("am"); /* force lib_termcap.o to be linked for 'ospeed' */ #endif return (ttype); } /************************************************************************** * * Main sequence * **************************************************************************/ /* * Convert the obsolete argument forms into something that getopt can handle. * This means that -e, -i and -k get default arguments supplied for them. */ static void obsolete(char **argv) { for (; *argv; ++argv) { char *parm = argv[0]; if (parm[0] == '-' && parm[1] == '\0') { argv[0] = strdup("-q"); continue; } if ((parm[0] != '-') || (argv[1] && argv[1][0] != '-') || (parm[1] != 'e' && parm[1] != 'i' && parm[1] != 'k') || (parm[2] != '\0')) continue; switch (argv[0][1]) { case 'e': argv[0] = strdup("-e^H"); break; case 'i': argv[0] = strdup("-i^C"); break; case 'k': argv[0] = strdup("-k^U"); break; } } } static void print_shell_commands(const char *ttype) { const char *p; int len; char *var; char *leaf; /* * Figure out what shell we're using. A hack, we look for an * environmental variable SHELL ending in "csh". */ if ((var = getenv("SHELL")) != 0 && ((len = (int) strlen(leaf = _nc_basename(var))) >= 3) && !strcmp(leaf + len - 3, "csh")) p = "set noglob;\nsetenv TERM %s;\nunset noglob;\n"; else p = "TERM=%s;\n"; (void) printf(p, ttype); } static void usage(void) { #define SKIP(s) /* nothing */ #define KEEP(s) s "\n" static const char msg[] = { KEEP("") KEEP("Options:") SKIP(" -a arpanet (obsolete)") KEEP(" -c set control characters") SKIP(" -d dialup (obsolete)") KEEP(" -e ch erase character") KEEP(" -I no initialization strings") KEEP(" -i ch interrupt character") KEEP(" -k ch kill character") KEEP(" -m mapping map identifier to type") SKIP(" -p plugboard (obsolete)") KEEP(" -Q do not output control key settings") KEEP(" -q display term only, do no changes") KEEP(" -r display term on stderr") SKIP(" -S (obsolete)") KEEP(" -s output TERM set command") KEEP(" -V print curses-version") KEEP(" -w set window-size") KEEP("") KEEP("If neither -c/-w are given, both are assumed.") }; #undef KEEP #undef SKIP (void) fprintf(stderr, "Usage: %s [options] [terminal]\n", _nc_progname); fputs(msg, stderr); ExitProgram(EXIT_FAILURE); /* NOTREACHED */ } static char arg_to_char(void) { return (char) ((optarg[0] == '^' && optarg[1] != '\0') ? ((optarg[1] == '?') ? '\177' : CTRL(optarg[1])) : optarg[0]); } int main(int argc, char **argv) { int ch, noinit, noset, quiet, Sflag, sflag, showterm; const char *ttype; int terasechar = -1; /* new erase character */ int intrchar = -1; /* new interrupt character */ int tkillchar = -1; /* new kill character */ int my_fd; bool opt_c = FALSE; /* set control-chars */ bool opt_w = FALSE; /* set window-size */ TTY mode, oldmode; my_fd = STDERR_FILENO; obsolete(argv); noinit = noset = quiet = Sflag = sflag = showterm = 0; while ((ch = getopt(argc, argv, "a:cd:e:Ii:k:m:p:qQrSsVw")) != -1) { switch (ch) { case 'c': /* set control-chars */ opt_c = TRUE; break; case 'a': /* OBSOLETE: map identifier to type */ add_mapping("arpanet", optarg); break; case 'd': /* OBSOLETE: map identifier to type */ add_mapping("dialup", optarg); break; case 'e': /* erase character */ terasechar = arg_to_char(); break; case 'I': /* no initialization strings */ noinit = 1; break; case 'i': /* interrupt character */ intrchar = arg_to_char(); break; case 'k': /* kill character */ tkillchar = arg_to_char(); break; case 'm': /* map identifier to type */ add_mapping(0, optarg); break; case 'p': /* OBSOLETE: map identifier to type */ add_mapping("plugboard", optarg); break; case 'Q': /* don't output control key settings */ quiet = 1; break; case 'q': /* display term only */ noset = 1; break; case 'r': /* display term on stderr */ showterm = 1; break; case 'S': /* OBSOLETE: output TERM & TERMCAP */ Sflag = 1; break; case 's': /* output TERM set command */ sflag = 1; break; case 'V': /* print curses-version */ puts(curses_version()); ExitProgram(EXIT_SUCCESS); case 'w': /* set window-size */ opt_w = TRUE; break; case '?': default: usage(); } } _nc_progname = _nc_rootname(*argv); argc -= optind; argv += optind; if (argc > 1) usage(); if (!opt_c && !opt_w) opt_c = opt_w = TRUE; my_fd = save_tty_settings(&mode, TRUE); oldmode = mode; #ifdef TERMIOS ospeed = (NCURSES_OSPEED) cfgetospeed(&mode); #else ospeed = (NCURSES_OSPEED) mode.sg_ospeed; #endif if (same_program(_nc_progname, PROG_RESET)) { reset_start(stderr, TRUE, FALSE); reset_tty_settings(my_fd, &mode); } else { reset_start(stderr, FALSE, TRUE); } ttype = get_termcap_entry(my_fd, *argv); if (!noset) { #if HAVE_SIZECHANGE if (opt_w) { set_window_size(my_fd, &lines, &columns); } #endif if (opt_c) { set_control_chars(&mode, terasechar, intrchar, tkillchar); set_conversions(&mode); if (!noinit) { if (send_init_strings(my_fd, &oldmode)) { (void) putc('\r', stderr); (void) fflush(stderr); (void) napms(1000); /* Settle the terminal. */ } } update_tty_settings(&oldmode, &mode); } } if (noset) { (void) printf("%s\n", ttype); } else { if (showterm) (void) fprintf(stderr, "Terminal type is %s.\n", ttype); /* * If erase, kill and interrupt characters could have been * modified and not -Q, display the changes. */ if (!quiet) { print_tty_chars(&oldmode, &mode); } } if (Sflag) err("The -S option is not supported under terminfo."); if (sflag) { print_shell_commands(ttype); } ExitProgram(EXIT_SUCCESS); }