toybox/toys/pending/stty.c
<<
>>
Prefs
   1/* stty.c - Get/set terminal configuration.
   2 *
   3 * Copyright 2017 The Android Open Source Project.
   4 *
   5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/stty.html
   6
   7USE_STTY(NEWTOY(stty, "?aF:g[!ag]", TOYFLAG_BIN))
   8
   9config STTY
  10  bool "stty"
  11  default n
  12  help
  13    usage: stty [-ag] [-F device] SETTING...
  14
  15    Get/set terminal configuration.
  16
  17    -F  Open device instead of stdin
  18    -a  Show all current settings (default differences from "sane").
  19    -g  Show all current settings usable as input to stty.
  20
  21    Special characters (syntax ^c or undef): intr quit erase kill eof eol eol2
  22    swtch start stop susp rprnt werase lnext discard
  23
  24    Control/input/output/local settings as shown by -a, '-' prefix to disable
  25
  26    Combo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane
  27
  28    N   set input and output speed (ispeed N or ospeed N for just one)
  29    cols N      set number of columns
  30    rows N      set number of rows
  31    line N      set line discipline
  32    min N       set minimum chars per read
  33    time N      set read timeout
  34    speed       show speed only
  35    size        show size only
  36*/
  37
  38#define FOR_stty
  39#include "toys.h"
  40
  41#include <linux/tty.h>
  42
  43GLOBALS(
  44  char *device;
  45
  46  int fd, col;
  47  unsigned output_cols;
  48)
  49
  50static const int bauds[] = {
  51  0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
  52  19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600,
  53  1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
  54};
  55
  56static int baud(speed_t speed)
  57{
  58  if (speed&CBAUDEX) speed=(speed&~CBAUDEX)+15;
  59  return bauds[speed];
  60}
  61
  62static speed_t speed(int baud)
  63{
  64  int i;
  65
  66  for (i=0;i<ARRAY_LEN(bauds);i++) if (bauds[i] == baud) break;
  67  if (i == ARRAY_LEN(bauds)) error_exit("unknown speed: %d", baud);
  68  return i+4081*(i>16);
  69}
  70
  71struct flag {
  72  char *name;
  73  int value;
  74  int mask;
  75};
  76
  77static const struct flag chars[] = {
  78  { "intr", VINTR }, { "quit", VQUIT }, { "erase", VERASE }, { "kill", VKILL },
  79  { "eof", VEOF }, { "eol", VEOL }, { "eol2", VEOL2 }, { "swtch", VSWTC },
  80  { "start", VSTART }, { "stop", VSTOP }, { "susp", VSUSP },
  81  { "rprnt", VREPRINT }, { "werase", VWERASE }, { "lnext", VLNEXT },
  82  { "discard", VDISCARD }, { "min", VMIN }, { "time", VTIME },
  83};
  84
  85static const struct flag cflags[] = {
  86  { "parenb", PARENB }, { "parodd", PARODD }, { "cmspar", CMSPAR },
  87  { "cs5", CS5, CSIZE }, { "cs6", CS6, CSIZE }, { "cs7", CS7, CSIZE },
  88  { "cs8", CS8, CSIZE }, { "hupcl", HUPCL }, { "cstopb", CSTOPB },
  89  { "cread", CREAD }, { "clocal", CLOCAL }, { "crtscts", CRTSCTS },
  90};
  91
  92static const struct flag iflags[] = {
  93  { "ignbrk", IGNBRK }, { "brkint", BRKINT }, { "ignpar", IGNPAR },
  94  { "parmrk", PARMRK }, { "inpck", INPCK }, { "istrip", ISTRIP },
  95  { "inlcr", INLCR }, { "igncr", IGNCR }, { "icrnl", ICRNL }, { "ixon", IXON },
  96  { "ixoff", IXOFF }, { "iuclc", IUCLC }, { "ixany", IXANY },
  97  { "imaxbel", IMAXBEL }, { "iutf8", IUTF8 },
  98};
  99
 100static const struct flag oflags[] = {
 101  { "opost", OPOST }, { "olcuc", OLCUC }, { "ocrnl", OCRNL },
 102  { "onlcr", ONLCR }, { "onocr", ONOCR }, { "onlret", ONLRET },
 103  { "ofill", OFILL }, { "ofdel", OFDEL }, { "nl0", NL0, NLDLY },
 104  { "nl1", NL1, NLDLY }, { "cr0", CR0, CRDLY }, { "cr1", CR1, CRDLY },
 105  { "cr2", CR2, CRDLY }, { "cr3", CR3, CRDLY }, { "tab0", TAB0, TABDLY },
 106  { "tab1", TAB1, TABDLY }, { "tab2", TAB2, TABDLY }, { "tab3", TAB3, TABDLY },
 107  { "bs0", BS0, BSDLY }, { "bs1", BS1, BSDLY }, { "vt0", VT0, VTDLY },
 108  { "vt1", VT1, VTDLY }, { "ff0", FF0, FFDLY }, { "ff1", FF1, FFDLY },
 109};
 110
 111static const struct flag lflags[] = {
 112  { "isig", ISIG }, { "icanon", ICANON }, { "iexten", IEXTEN },
 113  { "echo", ECHO }, { "echoe", ECHOE }, { "echok", ECHOK },
 114  { "echonl", ECHONL }, { "noflsh", NOFLSH }, { "xcase", XCASE },
 115  { "tostop", TOSTOP }, { "echoprt", ECHOPRT }, { "echoctl", ECHOCTL },
 116  { "echoke", ECHOKE }, { "flusho", FLUSHO }, { "extproc", EXTPROC },
 117};
 118
 119static const struct synonym {
 120  char *from;
 121  char *to;
 122} synonyms[] = {
 123  { "cbreak", "-icanon" }, { "-cbreak", "icanon" }, { "-cooked", "raw" },
 124  { "crterase", "echoe" }, { "-crterase", "-echoe" }, { "crtkill", "echoke" },
 125  { "-crtkill", "-echoke" }, { "ctlecho", "echoctl" }, { "-tandem", "-ixoff" },
 126  { "-ctlecho", "-echoctl" }, { "hup", "hupcl" }, { "-hup", "-hupcl" },
 127  { "prterase", "echoprt" }, { "-prterase", "-echoprt" }, { "-raw", "cooked" },
 128  { "tabs", "tab0" }, { "-tabs", "tab3" }, { "tandem", "ixoff" },
 129};
 130
 131static void out(const char *fmt, ...)
 132{
 133  va_list va;
 134  int len;
 135  char *prefix = " ";
 136
 137  va_start(va, fmt);
 138  len = vsnprintf(toybuf, sizeof(toybuf), fmt, va);
 139  va_end(va);
 140
 141  if (TT.output_cols == 0) {
 142    TT.output_cols = 80;
 143    terminal_size(&TT.output_cols, NULL);
 144  }
 145
 146  if (TT.col == 0 || *fmt == '\n') prefix = "";
 147  else if (TT.col + 1 + len >= TT.output_cols) {
 148    prefix = "\n";
 149    TT.col = 0;
 150  }
 151  xprintf("%s%s", prefix, toybuf);
 152
 153  if (toybuf[len-1] == '\n') TT.col = 0;
 154  else TT.col += strlen(prefix) + len;
 155}
 156
 157static void show_flags(tcflag_t actual, tcflag_t sane,
 158                       const struct flag *flags, int len)
 159{
 160  int i, j, value, mask;
 161
 162  // Implement -a by ensuring that sane != actual so we'll show everything.
 163  if (toys.optflags&FLAG_a) sane = ~actual;
 164
 165  for (i=j=0;i<len;i++) {
 166    value = flags[i].value;
 167    if ((mask = flags[i].mask)) {
 168      if ((actual&mask)==value && (sane&mask)!=value) {
 169        out("%s", flags[i].name);
 170        j++;
 171      }
 172    } else {
 173      if ((actual&value) != (sane&value)) {
 174        out("%s%s", actual&value?"":"-", flags[i].name);
 175        j++;
 176      }
 177    }
 178  }
 179  if (j) out("\n");
 180}
 181
 182static void show_size(int verbose)
 183{
 184  struct winsize ws;
 185
 186  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
 187  out(verbose ? "rows %d; columns %d;" : "%d %d\n", ws.ws_row, ws.ws_col);
 188}
 189
 190static void show_speed(struct termios *t, int verbose)
 191{
 192  int ispeed = baud(cfgetispeed(t)), ospeed = baud(cfgetospeed(t));
 193  char *fmt = verbose ? "ispeed %d baud; ospeed %d baud;" : "%d %d\n";
 194
 195  if (ispeed == ospeed) fmt += (verbose ? 17 : 3);
 196  out(fmt, ispeed, ospeed);
 197}
 198
 199static int get_arg(int *i, long long low, long long high)
 200{
 201  (*i)++;
 202  if (!toys.optargs[*i]) error_exit("missing arg");
 203  return atolx_range(toys.optargs[*i], low, high);
 204}
 205
 206static int set_flag(tcflag_t *f, const struct flag *flags, int len,
 207                    char *name, int on)
 208{
 209  int i;
 210
 211  for (i=0;i<len;i++) {
 212    if (!strcmp(flags[i].name, name)) {
 213      if (on) {
 214        *f &= ~flags[i].mask;
 215        *f |= flags[i].value;
 216      } else {
 217        if (flags[i].mask) error_exit("%s isn't a boolean", name);
 218        *f &= ~flags[i].value;
 219      }
 220      return 1;
 221    }
 222  }
 223  return 0;
 224}
 225
 226static void set_option(struct termios *new, char *option)
 227{
 228  int on = (*option != '-');
 229
 230  if (!on) option++;
 231  if (!set_flag(&new->c_cflag, cflags, ARRAY_LEN(cflags), option, on) &&
 232      !set_flag(&new->c_iflag, iflags, ARRAY_LEN(iflags), option, on) &&
 233      !set_flag(&new->c_oflag, oflags, ARRAY_LEN(oflags), option, on) &&
 234      !set_flag(&new->c_lflag, lflags, ARRAY_LEN(lflags), option, on))
 235    error_exit("unknown option: %s", option);
 236}
 237
 238static void set_options(struct termios* new, ...)
 239{
 240  va_list va;
 241  char *option;
 242
 243  va_start(va, new);
 244  while ((option = va_arg(va, char *))) set_option(new, option);
 245  va_end(va);
 246}
 247
 248static void set_size(int is_rows, unsigned short value)
 249{
 250  struct winsize ws;
 251
 252  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
 253  if (is_rows) ws.ws_row = value;
 254  else ws.ws_col = value;
 255  if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.device);
 256}
 257
 258static int set_special_character(struct termios *new, int *i, char *char_name)
 259{
 260  int j;
 261
 262  // The -2 is to ignore VMIN and VTIME, which are just unsigned integers.
 263  for (j=0;j<ARRAY_LEN(chars)-2;j++) {
 264    if (!strcmp(chars[j].name, char_name)) {
 265      char *arg = toys.optargs[++(*i)];
 266      cc_t ch;
 267
 268      if (!arg) error_exit("missing arg");
 269      if (!strcmp(arg, "^-") || !strcmp(arg, "undef")) ch = _POSIX_VDISABLE;
 270      else if (!strcmp(arg, "^?")) ch = 0x7f;
 271      else if (arg[0] == '^' && arg[2] == 0) ch = (toupper(arg[1])-'@');
 272      else if (!arg[1]) ch = arg[0];
 273      else error_exit("invalid arg: %s", arg);
 274      xprintf("setting %s to %s (%02x)\n", char_name, arg, ch);
 275      new->c_cc[chars[j].value] = ch;
 276      return 1;
 277    }
 278  }
 279  return 0;
 280}
 281
 282static void make_sane(struct termios *t)
 283{
 284  // POSIX has no opinion about what "sane" means. From "man stty".
 285  // "cs8" is missing from the man page, but needed to get identical results.
 286  set_options(t, "cread", "-ignbrk", "brkint", "-inlcr", "-igncr", "icrnl",
 287    "icanon", "iexten", "echo", "echoe", "echok", "-echonl", "-noflsh",
 288    "-ixoff", "-iutf8", "-iuclc", "-ixany", "imaxbel", "-xcase", "-olcuc",
 289    "-ocrnl", "opost", "-ofill", "onlcr", "-onocr", "-onlret", "nl0", "cr0",
 290    "tab0", "bs0", "vt0", "ff0", "isig", "-tostop", "-ofdel", "-echoprt",
 291    "echoctl", "echoke", "-extproc", "-flusho", "cs8", NULL);
 292  memset(t->c_cc, 0, NCCS);
 293  t->c_cc[VINTR] = 0x3;
 294  t->c_cc[VQUIT] = 0x1c;
 295  t->c_cc[VERASE] = 0x7f;
 296  t->c_cc[VKILL] = 0x15;
 297  t->c_cc[VEOF] = 0x4;
 298  t->c_cc[VTIME] = 0;
 299  t->c_cc[VMIN] = 1;
 300  t->c_cc[VSWTC] = 0;
 301  t->c_cc[VSTART] = 0x11;
 302  t->c_cc[VSTOP] = 0x13;
 303  t->c_cc[VSUSP] = 0x1a;
 304  t->c_cc[VEOL] = 0;
 305  t->c_cc[VREPRINT] = 0x12;
 306  t->c_cc[VDISCARD] = 0xf;
 307  t->c_cc[VWERASE] = 0x17;
 308  t->c_cc[VLNEXT] = 0x16;
 309  t->c_cc[VEOL2] = 0;
 310}
 311
 312static void xtcgetattr(struct termios *t)
 313{
 314  if (tcgetattr(TT.fd, t)) perror_exit("tcgetattr %s", TT.device);
 315}
 316
 317static void do_stty()
 318{
 319  struct termios old, sane;
 320  int i, j, n;
 321
 322  xtcgetattr(&old);
 323
 324  if (*toys.optargs) {
 325    struct termios new = old;
 326
 327    for (i=0; toys.optargs[i]; i++) {
 328      char *arg = toys.optargs[i];
 329
 330      if (!strcmp(arg, "size")) show_size(0);
 331      else if (!strcmp(arg, "speed")) show_speed(&old, 0);
 332      else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, N_TTY, NR_LDISCS);
 333      else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 0, 255);
 334      else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 0, 255);
 335      else if (atoi(arg) > 0) {
 336        int new_speed = speed(atolx_range(arg, 0, 4000000));
 337
 338        cfsetispeed(&new, new_speed);
 339        cfsetospeed(&new, new_speed);
 340      } else if (!strcmp(arg, "ispeed"))
 341        cfsetispeed(&new, speed(get_arg(&i, 0, 4000000)));
 342      else if (!strcmp(arg, "ospeed"))
 343        cfsetospeed(&new, speed(get_arg(&i, 0, 4000000)));
 344      else if (!strcmp(arg, "rows")) set_size(1, get_arg(&i, 0, USHRT_MAX));
 345      else if (!strcmp(arg, "cols") || !strcmp(arg, "columns"))
 346        set_size(0, get_arg(&i, 0, USHRT_MAX));
 347      else if (sscanf(arg, "%x:%x:%x:%x:%n", &new.c_iflag, &new.c_oflag,
 348                        &new.c_cflag, &new.c_lflag, &n) == 4)
 349      {
 350        int value;
 351
 352        arg += n;
 353        for (j=0;j<NCCS;j++) {
 354          if (sscanf(arg, "%x%n", &value, &n) != 1) error_exit("bad -g string");
 355          new.c_cc[j] = value;
 356          arg += n+1;
 357        }
 358      } else if (set_special_character(&new, &i, arg));
 359        // Already done as a side effect.
 360      else if (!strcmp(arg, "cooked"))
 361        set_options(&new, "brkint", "ignpar", "istrip", "icrnl", "ixon",
 362          "opost", "isig", "icanon", NULL);
 363      else if (!strcmp(arg, "evenp") || !strcmp(arg, "parity"))
 364        set_options(&new, "parenb", "cs7", "-parodd", NULL);
 365      else if (!strcmp(arg, "oddp"))
 366        set_options(&new, "parenb", "cs7", "parodd", NULL);
 367      else if (!strcmp(arg, "-parity") || !strcmp(arg, "-evenp") ||
 368                 !strcmp(arg, "-oddp")) {
 369        set_options(&new, "-parenb", "cs8", NULL);
 370      } else if (!strcmp(arg, "raw")) {
 371        // POSIX and "man stty" differ wildly. This is "man stty".
 372        set_options(&new, "-ignbrk", "-brkint", "-ignpar", "-parmrk", "-inpck",
 373          "-istrip", "-inlcr", "-igncr", "-icrnl", "-ixon", "-ixoff", "-iuclc",
 374          "-ixany", "-imaxbel", "-opost", "-isig", "-icanon", "-xcase", NULL);
 375        new.c_cc[VMIN] = 1;
 376        new.c_cc[VTIME] = 0;
 377      } else if (!strcmp(arg, "nl"))
 378        set_options(&new, "-icrnl", "-ocrnl", NULL);
 379      else if (!strcmp(arg, "-nl"))
 380        set_options(&new, "icrnl", "ocrnl", "-inlcr", "-igncr", NULL);
 381      else if (!strcmp(arg, "ek")) {
 382        new.c_cc[VERASE] = 0x7f;
 383        new.c_cc[VKILL] = 0x15;
 384      } else if (!strcmp(arg, "sane")) make_sane(&new);
 385      else {
 386        // Translate historical cruft into canonical forms.
 387        for (j=0;j<ARRAY_LEN(synonyms);j++) {
 388          if (!strcmp(synonyms[j].from, arg)) {
 389            arg = synonyms[j].to;
 390            break;
 391          }
 392        }
 393        set_option(&new, arg);
 394      }
 395    }
 396    tcsetattr(TT.fd, TCSAFLUSH, &new);
 397    xtcgetattr(&old);
 398    if (memcmp(&old, &new, sizeof(old)))
 399      error_exit("unable to perform all requested operations on %s", TT.device);
 400
 401    return;
 402  }
 403
 404  if (toys.optflags&FLAG_g) {
 405    xprintf("%x:%x:%x:%x:", old.c_iflag, old.c_oflag, old.c_cflag, old.c_lflag);
 406    for (i=0;i<NCCS;i++) xprintf("%x%c", old.c_cc[i], i==NCCS-1?'\n':':');
 407    return;
 408  }
 409
 410  // Without arguments, "stty" only shows the speed, the line discipline,
 411  // special characters and any flags that differ from the "sane" settings.
 412  make_sane(&sane);
 413  show_speed(&old, 1);
 414  if (toys.optflags&FLAG_a) show_size(1);
 415  out("line = %d;\n", old.c_line);
 416
 417  for (i=j=0;i<ARRAY_LEN(chars);i++) {
 418    char vis[16] = {};
 419    cc_t ch = old.c_cc[chars[i].value];
 420
 421    if (ch == sane.c_cc[chars[i].value] && (toys.optflags&FLAG_a)==0)
 422      continue;
 423
 424    if (chars[i].value == VMIN || chars[i].value == VTIME) {
 425      snprintf(vis, sizeof(vis), "%u", ch);
 426    } else if (ch == _POSIX_VDISABLE) {
 427      strcat(vis, "<undef>");
 428    } else {
 429      if (ch > 0x7f) {
 430        strcat(vis, "M-");
 431        ch -= 128;
 432      }
 433      if (ch < ' ') sprintf(vis+strlen(vis), "^%c", (ch+'@'));
 434      else if (ch == 0x7f) strcat(vis, "^?");
 435      else sprintf(vis+strlen(vis), "%c", ch);
 436    }
 437    out("%s = %s;", chars[i].name, vis);
 438    j++;
 439  }
 440  if (j) out("\n");
 441
 442  show_flags(old.c_cflag, sane.c_cflag, cflags, ARRAY_LEN(cflags));
 443  show_flags(old.c_iflag, sane.c_iflag, iflags, ARRAY_LEN(iflags));
 444  show_flags(old.c_oflag, sane.c_oflag, oflags, ARRAY_LEN(oflags));
 445  show_flags(old.c_lflag, sane.c_lflag, lflags, ARRAY_LEN(lflags));
 446}
 447
 448void stty_main(void)
 449{
 450  if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs)
 451    error_exit("can't make settings with -a/-g");
 452
 453  if (!TT.device) TT.device = "standard input";
 454  else TT.fd=xopen(TT.device, (O_RDWR*!!*toys.optargs)|O_NOCTTY|O_NONBLOCK);
 455
 456  do_stty();
 457
 458  if (CFG_TOYBOX_FREE && TT.device) close(TT.fd);
 459}
 460