diff options
Diffstat (limited to 'toys/pending/stty.c')
-rw-r--r-- | toys/pending/stty.c | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/toys/pending/stty.c b/toys/pending/stty.c new file mode 100644 index 00000000..9ad353aa --- /dev/null +++ b/toys/pending/stty.c @@ -0,0 +1,528 @@ +/* stty.c - Get/set terminal configuration. + * + * Copyright 2017 The Android Open Source Project. + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/stty.html + +USE_STTY(NEWTOY(stty, "?aF:g[!ag]", TOYFLAG_BIN)) + +config STTY + bool "stty" + default y + help + usage: stty [-ag] [-F device] SETTING... + + Get/set terminal configuration. + + -a Show all current settings (default differences from "sane"). + -g Show all current settings usable as input to stty. + + Special characters (syntax ^c or undef): intr quit erase kill eof eol eol2 + swtch start stop susp rprnt werase lnext discard + + Control/input/output/local settings as shown by -a, '-' prefix to disable + + Combo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane + + N set input and output speed (ispeed N or ospeed N for just one) + cols N set number of columns + rows N set number of rows + line N set line discipline + min N set minimum chars per read + time N set read timeout + speed show speed only + size show size only +*/ + +#define FOR_stty +#include "toys.h" + +#include <linux/tty.h> + +GLOBALS( + char *device; + + int fd, col; + unsigned output_cols; +) + +static const int bauds[] = { + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, + 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, + 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000 +}; + +static int baud(speed_t speed) +{ + if (speed&CBAUDEX) speed=(speed&~CBAUDEX)+15; + return bauds[speed]; +} + +static speed_t speed(int baud) +{ + int i; + + for (i=0;i<ARRAY_LEN(bauds);i++) if (bauds[i] == baud) break; + if (i == ARRAY_LEN(bauds)) error_exit("unknown speed: %d", baud); + return i+4081*(i>16); +} + +struct flag { + char *name; + int value; + int mask; +}; + +static const struct flag chars[] = { + { "intr", VINTR }, + { "quit", VQUIT }, + { "erase", VERASE }, + { "kill", VKILL }, + { "eof", VEOF }, + { "eol", VEOL }, + { "eol2", VEOL2 }, + { "swtch", VSWTC }, + { "start", VSTART }, + { "stop", VSTOP }, + { "susp", VSUSP }, + { "rprnt", VREPRINT }, + { "werase", VWERASE }, + { "lnext", VLNEXT }, + { "discard", VDISCARD }, + { "min", VMIN }, + { "time", VTIME }, +}; + +static const struct flag cflags[] = { + { "parenb", PARENB }, + { "parodd", PARODD }, + { "cmspar", CMSPAR }, + { "cs5", CS5, CSIZE }, + { "cs6", CS6, CSIZE }, + { "cs7", CS7, CSIZE }, + { "cs8", CS8, CSIZE }, + { "hupcl", HUPCL }, + { "cstopb", CSTOPB }, + { "cread", CREAD }, + { "clocal", CLOCAL }, + { "crtscts", CRTSCTS }, +}; + +static const struct flag iflags[] = { + { "ignbrk", IGNBRK }, + { "brkint", BRKINT }, + { "ignpar", IGNPAR }, + { "parmrk", PARMRK }, + { "inpck", INPCK }, + { "istrip", ISTRIP }, + { "inlcr", INLCR }, + { "igncr", IGNCR }, + { "icrnl", ICRNL }, + { "ixon", IXON }, + { "ixoff", IXOFF }, + { "iuclc", IUCLC }, + { "ixany", IXANY }, + { "imaxbel", IMAXBEL }, + { "iutf8", IUTF8 }, +}; + +static const struct flag oflags[] = { + { "opost", OPOST }, + { "olcuc", OLCUC }, + { "ocrnl", OCRNL }, + { "onlcr", ONLCR }, + { "onocr", ONOCR }, + { "onlret", ONLRET }, + { "ofill", OFILL }, + { "ofdel", OFDEL }, + { "nl0", NL0, NLDLY }, + { "nl1", NL1, NLDLY }, + { "cr0", CR0, CRDLY }, + { "cr1", CR1, CRDLY }, + { "cr2", CR2, CRDLY }, + { "cr3", CR3, CRDLY }, + { "tab0", TAB0, TABDLY }, + { "tab1", TAB1, TABDLY }, + { "tab2", TAB2, TABDLY }, + { "tab3", TAB3, TABDLY }, + { "bs0", BS0, BSDLY }, + { "bs1", BS1, BSDLY }, + { "vt0", VT0, VTDLY }, + { "vt1", VT1, VTDLY }, + { "ff0", FF0, FFDLY }, + { "ff1", FF1, FFDLY }, +}; + +static const struct flag lflags[] = { + { "isig", ISIG }, + { "icanon", ICANON }, + { "iexten", IEXTEN }, + { "echo", ECHO }, + { "echoe", ECHOE }, + { "echok", ECHOK }, + { "echonl", ECHONL }, + { "noflsh", NOFLSH }, + { "xcase", XCASE }, + { "tostop", TOSTOP }, + { "echoprt", ECHOPRT }, + { "echoctl", ECHOCTL }, + { "echoke", ECHOKE }, + { "flusho", FLUSHO }, + { "extproc", EXTPROC }, +}; + +static const struct synonym { + char *from; + char *to; +} synonyms[] = { + { "cbreak", "-icanon" }, + { "-cbreak", "icanon" }, + { "-cooked", "raw" }, + { "crterase", "echoe" }, + { "-crterase", "-echoe" }, + { "crtkill", "echoke" }, + { "-crtkill", "-echoke" }, + { "ctlecho", "echoctl" }, + { "-ctlecho", "-echoctl" }, + { "hup", "hupcl" }, + { "-hup", "-hupcl" }, + { "prterase", "echoprt" }, + { "-prterase", "-echoprt" }, + { "-raw", "cooked" }, + { "tabs", "tab0" }, + { "-tabs", "tab3" }, + { "tandem", "ixoff" }, + { "-tandem", "-ixoff" }, +}; + +static void out(const char *fmt, ...) +{ + va_list va; + int len; + char *prefix = " "; + + va_start(va, fmt); + len = vsnprintf(toybuf, sizeof(toybuf), fmt, va); + va_end(va); + + if (TT.output_cols == 0) { + TT.output_cols = 80; + terminal_size(&TT.output_cols, NULL); + } + + if (TT.col == 0 || *fmt == '\n') prefix = ""; + else if (TT.col + 1 + len >= TT.output_cols) { + prefix = "\n"; + TT.col = 0; + } + xprintf("%s%s", prefix, toybuf); + + if (toybuf[len-1] == '\n') TT.col = 0; + else TT.col += strlen(prefix) + len; +} + +static void show_flags(tcflag_t actual, tcflag_t sane, + const struct flag *flags, int len) +{ + int i, j, value, mask; + + // Implement -a by ensuring that sane != actual so we'll show everything. + if (toys.optflags&FLAG_a) sane = ~actual; + + for (i=j=0;i<len;i++) { + value = flags[i].value; + if ((mask = flags[i].mask)) { + if ((actual&mask)==value && (sane&mask)!=value) { + out("%s", flags[i].name); + j++; + } + } else { + if ((actual&value) != (sane&value)) { + out("%s%s", actual&value?"":"-", flags[i].name); + j++; + } + } + } + if (j) out("\n"); +} + +static void show_size(int verbose) +{ + struct winsize ws; + + if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device); + out(verbose ? "rows %d; columns %d;" : "%d %d\n", ws.ws_row, ws.ws_col); +} + +static void show_speed(struct termios *t, int verbose) +{ + int ispeed = baud(cfgetispeed(t)), ospeed = baud(cfgetospeed(t)); + char *fmt = verbose ? "ispeed %d baud; ospeed %d baud;" : "%d %d\n"; + + if (ispeed == ospeed) fmt += (verbose ? 17 : 3); + out(fmt, ispeed, ospeed); +} + +static int get_arg(int *i, long long low, long long high) +{ + (*i)++; + if (!toys.optargs[*i]) error_exit("missing arg"); + return atolx_range(toys.optargs[*i], low, high); +} + +static int set_flag(tcflag_t *f, const struct flag *flags, int len, + char *name, int on) +{ + int i; + + for (i=0;i<len;i++) { + if (!strcmp(flags[i].name, name)) { + if (on) { + *f &= ~flags[i].mask; + *f |= flags[i].value; + } else { + if (flags[i].mask) error_exit("%s isn't a boolean", name); + *f &= ~flags[i].value; + } + return 1; + } + } + return 0; +} + +static void set_option(struct termios *new, char *option) +{ + int on = (*option != '-'); + + if (!on) option++; + if (!set_flag(&new->c_cflag, cflags, ARRAY_LEN(cflags), option, on) && + !set_flag(&new->c_iflag, iflags, ARRAY_LEN(iflags), option, on) && + !set_flag(&new->c_oflag, oflags, ARRAY_LEN(oflags), option, on) && + !set_flag(&new->c_lflag, lflags, ARRAY_LEN(lflags), option, on)) + error_exit("unknown option: %s", option); +} + +static void set_options(struct termios* new, ...) +{ + va_list va; + char *option; + + va_start(va, new); + while ((option = va_arg(va, char *))) { + set_option(new, option); + } + va_end(va); +} + +static void set_size(int is_rows, unsigned short value) +{ + struct winsize ws; + + if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device); + if (is_rows) ws.ws_row = value; + else ws.ws_col = value; + if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.device); +} + +static int set_special_character(struct termios *new, int *i, char *char_name) +{ + int j; + + // The -2 is to ignore VMIN and VTIME, which are just unsigned integers. + for (j=0;j<ARRAY_LEN(chars)-2;j++) { + if (!strcmp(chars[j].name, char_name)) { + char *arg = toys.optargs[++(*i)]; + cc_t ch; + + if (!arg) error_exit("missing arg"); + if (!strcmp(arg, "^-") || !strcmp(arg, "undef")) ch = _POSIX_VDISABLE; + else if (!strcmp(arg, "^?")) ch = 0x7f; + else if (arg[0] == '^' && arg[2] == 0) ch = (toupper(arg[1])-'@'); + else if (!arg[1]) ch = arg[0]; + else error_exit("invalid arg: %s", arg); + xprintf("setting %s to %s (%02x)\n", char_name, arg, ch); + new->c_cc[chars[j].value] = ch; + return 1; + } + } + return 0; +} + +static void make_sane(struct termios *t) +{ + // POSIX has no opinion about what "sane" means. From "man stty". + // "cs8" is missing from the man page, but needed to get identical results. + set_options(t, "cread", "-ignbrk", "brkint", "-inlcr", "-igncr", "icrnl", + "icanon", "iexten", "echo", "echoe", "echok", "-echonl", "-noflsh", + "-ixoff", "-iutf8", "-iuclc", "-ixany", "imaxbel", "-xcase", "-olcuc", + "-ocrnl", "opost", "-ofill", "onlcr", "-onocr", "-onlret", "nl0", "cr0", + "tab0", "bs0", "vt0", "ff0", "isig", "-tostop", "-ofdel", "-echoprt", + "echoctl", "echoke", "-extproc", "-flusho", "cs8", 0); + memset(t->c_cc, 0, NCCS); + t->c_cc[VINTR] = 0x3; + t->c_cc[VQUIT] = 0x1c; + t->c_cc[VERASE] = 0x7f; + t->c_cc[VKILL] = 0x15; + t->c_cc[VEOF] = 0x4; + t->c_cc[VTIME] = 0; + t->c_cc[VMIN] = 1; + t->c_cc[VSWTC] = 0; + t->c_cc[VSTART] = 0x11; + t->c_cc[VSTOP] = 0x13; + t->c_cc[VSUSP] = 0x1a; + t->c_cc[VEOL] = 0; + t->c_cc[VREPRINT] = 0x12; + t->c_cc[VDISCARD] = 0xf; + t->c_cc[VWERASE] = 0x17; + t->c_cc[VLNEXT] = 0x16; + t->c_cc[VEOL2] = 0; +} + +static void do_stty() +{ + struct termios old, sane; + int i, j, n; + + if (tcgetattr(TT.fd, &old)) perror_exit("tcgetattr %s", TT.device); + + if (*toys.optargs) { + struct termios new = old; + + for (i=0; toys.optargs[i]; i++) { + char *arg = toys.optargs[i]; + + if (!strcmp(arg, "size")) show_size(0); + else if (!strcmp(arg, "speed")) show_speed(&old, 0); + else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, N_TTY, NR_LDISCS); + else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 0, 255); + else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 0, 255); + else if (atoi(arg) > 0) { + int new_speed = speed(atolx_range(arg, 0, 4000000)); + + cfsetispeed(&new, new_speed); + cfsetospeed(&new, new_speed); + } else if (!strcmp(arg, "ispeed")) { + cfsetispeed(&new, speed(get_arg(&i, 0, 4000000))); + } else if (!strcmp(arg, "ospeed")) { + cfsetospeed(&new, speed(get_arg(&i, 0, 4000000))); + } else if (!strcmp(arg, "rows")) { + set_size(1, get_arg(&i, 0, USHRT_MAX)); + } else if (!strcmp(arg, "cols") || !strcmp(arg, "columns")) { + set_size(0, get_arg(&i, 0, USHRT_MAX)); + } else if (sscanf(arg, "%x:%x:%x:%x:%n", &new.c_iflag, &new.c_oflag, + &new.c_cflag, &new.c_lflag, &n) == 4) { + int value; + + arg += n; + for (j=0;j<NCCS;j++) { + if (sscanf(arg, "%x%n", &value, &n) != 1) error_exit("bad -g string"); + new.c_cc[j] = value; + arg += n+1; + } + } else if (set_special_character(&new, &i, arg)) { + // Already done as a side effect. + } else if (!strcmp(arg, "cooked")) { + set_options(&new, "brkint", "ignpar", "istrip", "icrnl", "ixon", + "opost", "isig", "icanon", 0); + } else if (!strcmp(arg, "evenp") || !strcmp(arg, "parity")) { + set_options(&new, "parenb", "cs7", "-parodd", 0); + } else if (!strcmp(arg, "oddp")) { + set_options(&new, "parenb", "cs7", "parodd", 0); + } else if (!strcmp(arg, "-parity") || !strcmp(arg, "-evenp") || + !strcmp(arg, "-oddp")) { + set_options(&new, "-parenb", "cs8", 0); + } else if (!strcmp(arg, "raw")) { + // POSIX and "man stty" differ wildly. This is "man stty". + set_options(&new, "-ignbrk", "-brkint", "-ignpar", "-parmrk", "-inpck", + "-istrip", "-inlcr", "-igncr", "-icrnl", "-ixon", "-ixoff", "-iuclc", + "-ixany", "-imaxbel", "-opost", "-isig", "-icanon", "-xcase", 0); + new.c_cc[VMIN] = 1; + new.c_cc[VTIME] = 0; + } else if (!strcmp(arg, "nl")) { + set_options(&new, "-icrnl", "-ocrnl", 0); + } else if (!strcmp(arg, "-nl")) { + set_options(&new, "icrnl", "ocrnl", "-inlcr", "-igncr", 0); + } else if (!strcmp(arg, "ek")) { + new.c_cc[VERASE] = 0x7f; + new.c_cc[VKILL] = 0x15; + } else if (!strcmp(arg, "sane")) { + make_sane(&new); + } else { + // Translate historical cruft into canonical forms. + for (j=0;j<ARRAY_LEN(synonyms);j++) { + if (!strcmp(synonyms[j].from, arg)) { + arg = synonyms[j].to; + break; + } + } + set_option(&new, arg); + } + } + if (tcsetattr(TT.fd,TCSAFLUSH,&new)) perror_exit("tcsetattr %s",TT.device); + + // tcsetattr returns success if *any* change is made, so double-check... + old = new; + if (tcgetattr(TT.fd,&new)) perror_exit("tcgetattr %s",TT.device); + if (memcmp(&old, &new, sizeof(old))) + error_exit("unable to perform all requested operations on %s", TT.device); + return; + } + + if (toys.optflags&FLAG_g) { + xprintf("%x:%x:%x:%x:", old.c_iflag, old.c_oflag, old.c_cflag, old.c_lflag); + for (i=0;i<NCCS;i++) xprintf("%x%c", old.c_cc[i], i==NCCS-1?'\n':':'); + return; + } + + // Without arguments, "stty" only shows the speed, the line discipline, + // special characters and any flags that differ from the "sane" settings. + make_sane(&sane); + show_speed(&old, 1); + if (toys.optflags&FLAG_a) show_size(1); + out("line = %d;\n", old.c_line); + + for (i=j=0;i<ARRAY_LEN(chars);i++) { + char vis[16] = {}; + cc_t ch = old.c_cc[chars[i].value]; + + if (ch == sane.c_cc[chars[i].value] && (toys.optflags&FLAG_a)==0) + continue; + + if (chars[i].value == VMIN || chars[i].value == VTIME) { + snprintf(vis, sizeof(vis), "%u", ch); + } else if (ch == _POSIX_VDISABLE) { + strcat(vis, "<undef>"); + } else { + if (ch > 0x7f) { + strcat(vis, "M-"); + ch -= 128; + } + if (ch < ' ') sprintf(vis+strlen(vis), "^%c", (ch+'@')); + else if (ch == 0x7f) strcat(vis, "^?"); + else sprintf(vis+strlen(vis), "%c", ch); + } + out("%s = %s;", chars[i].name, vis); + j++; + } + if (j) out("\n"); + + show_flags(old.c_cflag, sane.c_cflag, cflags, ARRAY_LEN(cflags)); + show_flags(old.c_iflag, sane.c_iflag, iflags, ARRAY_LEN(iflags)); + show_flags(old.c_oflag, sane.c_oflag, oflags, ARRAY_LEN(oflags)); + show_flags(old.c_lflag, sane.c_lflag, lflags, ARRAY_LEN(lflags)); +} + +void stty_main(void) +{ + if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs) + error_exit("can't make settings with -a/-g"); + + if (TT.device) { + TT.fd=xopen(TT.device,(*toys.optargs?O_RDWR:O_RDONLY)|O_NOCTTY|O_NONBLOCK); + do_stty(); + close(TT.fd); + } else { + TT.device = "standard input"; + do_stty(); + } +} |