aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2017-12-01 22:43:38 -0800
committerRob Landley <rob@landley.net>2017-12-03 01:29:30 -0600
commitef213b3d92febeb92e530f708e1647ca5459403c (patch)
treef51555253cbd2938b429541259be31ef6eb107cc
parent7ce9c2d1f60cb36a2a33dcf420f62d220b28edb2 (diff)
downloadtoybox-ef213b3d92febeb92e530f708e1647ca5459403c.tar.gz
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.
-rw-r--r--toys/pending/stty.c528
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();
+ }
+}