From ef213b3d92febeb92e530f708e1647ca5459403c Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Fri, 1 Dec 2017 22:43:38 -0800 Subject: Add stty(1). Full POSIX stty with Linux extensions. Output and behavior match coreutils 8.26 as far as I can tell. For some reason busybox 1.22 stty always shows all the special characters, even when they match "sane". I've matched coreutils, since "shows differences from sane" is easy to describe and obviously useful. Flags in the various arrays are not in the order they're introduced in POSIX or in the Linux header file: they're in the order that they're output by coreutils' stty. The -g output matches coreutils and busybox. I implemented iuclc, xcase, and olcuc even though they've been removed from POSIX because the others implement them, and "man stty" defines "raw" and "sane" in terms of them (where POSIX doesn't define "sane" in any useful sense). This builds fine against glibc 2.24, and as far as I can tell all the constants used were in Linux 2.6 so I'm assuming that there shouldn't be any #ifdef nonsense needed for any reasonable vintage of C library. --- toys/pending/stty.c | 528 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 528 insertions(+) create mode 100644 toys/pending/stty.c 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 + +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;i16); +} + +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;ic_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;jc_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"); + } 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(); + } +} -- cgit v1.2.3