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
From 093554932ebe30c613a78b764bd61d5c564f02be Mon Sep 17 00:00:00 2001
From: Elliott Hughes <e...@google.com>
Date: Fri, 1 Dec 2017 22:43:38 -0800
Subject: [PATCH] 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 0000000..9ad353a
--- /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();
+  }
+}
-- 
2.15.0.531.g2ccb3012c9-goog

_______________________________________________
Toybox mailing list
Toybox@lists.landley.net
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to