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 *F;
  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" },
 124  { "-cooked", "raw" }, { "-raw", "cooked" },
 125  { "crterase", "echoe" }, { "-crterase", "-echoe" },
 126  { "crtkill", "echoke" }, { "-crtkill", "-echoke" },
 127  { "ctlecho", "echoctl" }, { "-ctlecho", "-echoctl" },
 128  { "-tandem", "-ixoff" }, { "tandem", "ixoff" },
 129  { "hup", "hupcl" }, { "-hup", "-hupcl" },
 130  { "prterase", "echoprt" }, { "-prterase", "-echoprt" },
 131  { "tabs", "tab0" }, { "-tabs", "tab3" },
 132};
 133
 134static void out(const char *fmt, ...)
 135{
 136  va_list va;
 137  int len;
 138  char *prefix = " ";
 139
 140  va_start(va, fmt);
 141  len = vsnprintf(toybuf, sizeof(toybuf), fmt, va);
 142  va_end(va);
 143
 144  if (TT.output_cols == 0) {
 145    TT.output_cols = 80;
 146    terminal_size(&TT.output_cols, NULL);
 147  }
 148
 149  if (TT.col == 0 || *fmt == '\n') prefix = "";
 150  else if (TT.col + 1 + len >= TT.output_cols) {
 151    prefix = "\n";
 152    TT.col = 0;
 153  }
 154  xprintf("%s%s", prefix, toybuf);
 155
 156  if (toybuf[len-1] == '\n') TT.col = 0;
 157  else TT.col += strlen(prefix) + len;
 158}
 159
 160static void show_flags(tcflag_t actual, tcflag_t sane,
 161                       const struct flag *flags, int len)
 162{
 163  int i, j, value, mask;
 164
 165  // Implement -a by ensuring that sane != actual so we'll show everything.
 166  if (FLAG(a)) sane = ~actual;
 167
 168  for (i = j = 0; i<len; i++) {
 169    value = flags[i].value;
 170    if ((mask = flags[i].mask)) {
 171      if ((actual&mask)==value && (sane&mask)!=value) {
 172        out("%s", flags[i].name);
 173        j++;
 174      }
 175    } else {
 176      if ((actual&value) != (sane&value)) {
 177        out("%s%s", actual&value?"":"-", flags[i].name);
 178        j++;
 179      }
 180    }
 181  }
 182  if (j) out("\n");
 183}
 184
 185static void show_size(int verbose)
 186{
 187  struct winsize ws;
 188
 189  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.F);
 190  out(verbose ? "rows %d; columns %d;" : "%d %d\n", ws.ws_row, ws.ws_col);
 191}
 192
 193static void show_speed(struct termios *t, int verbose)
 194{
 195  int ispeed = baud(cfgetispeed(t)), ospeed = baud(cfgetospeed(t));
 196  char *fmt = verbose ? "ispeed %d baud; ospeed %d baud;" : "%d %d\n";
 197
 198  if (ispeed == ospeed) fmt += (verbose ? 17 : 3);
 199  out(fmt, ispeed, ospeed);
 200}
 201
 202static int get_arg(int *i, long long high)
 203{
 204  (*i)++;
 205  if (!toys.optargs[*i]) error_exit("missing arg");
 206  return atolx_range(toys.optargs[*i], 0, high);
 207}
 208
 209static int set_flag(tcflag_t *f, const struct flag *flags, int len,
 210                    char *name, int on)
 211{
 212  int i;
 213
 214  for (i=0;i<len;i++) {
 215    if (!strcmp(flags[i].name, name)) {
 216      if (on) {
 217        *f &= ~flags[i].mask;
 218        *f |= flags[i].value;
 219      } else {
 220        if (flags[i].mask) error_exit("%s isn't a boolean", name);
 221        *f &= ~flags[i].value;
 222      }
 223      return 1;
 224    }
 225  }
 226  return 0;
 227}
 228
 229static void set_option(struct termios *new, char *option)
 230{
 231  int on = (*option != '-');
 232
 233  if (!on) option++;
 234  if (!set_flag(&new->c_cflag, cflags, ARRAY_LEN(cflags), option, on) &&
 235      !set_flag(&new->c_iflag, iflags, ARRAY_LEN(iflags), option, on) &&
 236      !set_flag(&new->c_oflag, oflags, ARRAY_LEN(oflags), option, on) &&
 237      !set_flag(&new->c_lflag, lflags, ARRAY_LEN(lflags), option, on))
 238    error_exit("unknown option: %s", option);
 239}
 240
 241static void set_options(struct termios* new, ...)
 242{
 243  va_list va;
 244  char *option;
 245
 246  va_start(va, new);
 247  while ((option = va_arg(va, char *))) set_option(new, option);
 248  va_end(va);
 249}
 250
 251static void set_size(int is_rows, unsigned short value)
 252{
 253  struct winsize ws;
 254
 255  if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.F);
 256  if (is_rows) ws.ws_row = value;
 257  else ws.ws_col = value;
 258  if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.F);
 259}
 260
 261static int set_special_character(struct termios *new, int *i, char *char_name)
 262{
 263  int j;
 264
 265  // The -2 is to ignore VMIN and VTIME, which are just unsigned integers.
 266  for (j=0;j<ARRAY_LEN(chars)-2;j++) {
 267    if (!strcmp(chars[j].name, char_name)) {
 268      char *arg = toys.optargs[++(*i)];
 269      cc_t ch;
 270
 271      if (!arg) error_exit("missing arg");
 272      if (!strcmp(arg, "^-") || !strcmp(arg, "undef")) ch = _POSIX_VDISABLE;
 273      else if (!strcmp(arg, "^?")) ch = 0x7f;
 274      else if (arg[0] == '^' && arg[2] == 0) ch = (toupper(arg[1])-'@');
 275      else if (!arg[1]) ch = arg[0];
 276      else error_exit("invalid arg: %s", arg);
 277      new->c_cc[chars[j].value] = ch;
 278      return 1;
 279    }
 280  }
 281  return 0;
 282}
 283
 284static void make_sane(struct termios *t)
 285{
 286  // POSIX has no opinion about what "sane" means. From "man stty".
 287  // "cs8" is missing from the man page, but needed to get identical results.
 288  set_options(t, "cread", "-ignbrk", "brkint", "-inlcr", "-igncr", "icrnl",
 289    "icanon", "iexten", "echo", "echoe", "echok", "-echonl", "-noflsh",
 290    "-ixoff", "-iutf8", "-iuclc", "-ixany", "imaxbel", "-xcase", "-olcuc",
 291    "-ocrnl", "opost", "-ofill", "onlcr", "-onocr", "-onlret", "nl0", "cr0",
 292    "tab0", "bs0", "vt0", "ff0", "isig", "-tostop", "-ofdel", "-echoprt",
 293    "echoctl", "echoke", "-extproc", "-flusho", "cs8", NULL);
 294  memset(t->c_cc, 0, NCCS);
 295  t->c_cc[VINTR] = 0x3;
 296  t->c_cc[VQUIT] = 0x1c;
 297  t->c_cc[VERASE] = 0x7f;
 298  t->c_cc[VKILL] = 0x15;
 299  t->c_cc[VEOF] = 0x4;
 300  t->c_cc[VTIME] = 0;
 301  t->c_cc[VMIN] = 1;
 302  t->c_cc[VSWTC] = 0;
 303  t->c_cc[VSTART] = 0x11;
 304  t->c_cc[VSTOP] = 0x13;
 305  t->c_cc[VSUSP] = 0x1a;
 306  t->c_cc[VEOL] = 0;
 307  t->c_cc[VREPRINT] = 0x12;
 308  t->c_cc[VDISCARD] = 0xf;
 309  t->c_cc[VWERASE] = 0x17;
 310  t->c_cc[VLNEXT] = 0x16;
 311  t->c_cc[VEOL2] = 0;
 312}
 313
 314static void xtcgetattr(struct termios *t)
 315{
 316  if (tcgetattr(TT.fd, t)) perror_exit("tcgetattr %s", TT.F);
 317}
 318
 319void stty_main(void)
 320{
 321  struct termios old, sane;
 322  int i, j, n;
 323
 324  if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs)
 325    error_exit("no settings with -a/-g");
 326
 327  if (!TT.F) TT.F = "standard input";
 328  else TT.fd = xopen(TT.F, (O_RDWR*!!*toys.optargs)|O_NOCTTY|O_NONBLOCK);
 329
 330  xtcgetattr(&old);
 331
 332  if (*toys.optargs) {
 333    struct termios new = old, tmp;
 334
 335    for (i=0; toys.optargs[i]; i++) {
 336      char *arg = toys.optargs[i];
 337
 338      if (!strcmp(arg, "size")) show_size(0);
 339      else if (!strcmp(arg, "speed")) show_speed(&old, 0);
 340      else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, NR_LDISCS);
 341      else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 255);
 342      else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 255);
 343      else if (sscanf(arg, "%x:%x:%x:%x:%n", &tmp.c_iflag, &tmp.c_oflag,
 344                        &tmp.c_cflag, &tmp.c_lflag, &n) == 4)
 345      {
 346        int value;
 347
 348        new.c_iflag = tmp.c_iflag;
 349        new.c_oflag = tmp.c_oflag;
 350        new.c_cflag = tmp.c_cflag;
 351        new.c_lflag = tmp.c_lflag;
 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 (atoi(arg) > 0) {
 359        int new_speed = speed(atolx_range(arg, 0, 4000000));
 360
 361        cfsetispeed(&new, new_speed);
 362        cfsetospeed(&new, new_speed);
 363      } else if (!strcmp(arg, "ispeed"))
 364        cfsetispeed(&new, speed(get_arg(&i, 4000000)));
 365      else if (!strcmp(arg, "ospeed"))
 366        cfsetospeed(&new, speed(get_arg(&i, 4000000)));
 367      else if (!strcmp(arg, "rows")) set_size(1, get_arg(&i, USHRT_MAX));
 368      else if (!strcmp(arg, "cols") || !strcmp(arg, "columns"))
 369        set_size(0, get_arg(&i, USHRT_MAX));
 370      else if (set_special_character(&new, &i, arg));
 371        // Already done as a side effect.
 372      else if (!strcmp(arg, "cooked"))
 373        set_options(&new, "brkint", "ignpar", "istrip", "icrnl", "ixon",
 374          "opost", "isig", "icanon", NULL);
 375      else if (!strcmp(arg, "evenp") || !strcmp(arg, "parity"))
 376        set_options(&new, "parenb", "cs7", "-parodd", NULL);
 377      else if (!strcmp(arg, "oddp"))
 378        set_options(&new, "parenb", "cs7", "parodd", NULL);
 379      else if (!strcmp(arg, "-parity") || !strcmp(arg, "-evenp") ||
 380                 !strcmp(arg, "-oddp")) {
 381        set_options(&new, "-parenb", "cs8", NULL);
 382      } else if (!strcmp(arg, "raw")) {
 383        // POSIX and "man stty" differ wildly. This is "man stty".
 384        set_options(&new, "-ignbrk", "-brkint", "-ignpar", "-parmrk", "-inpck",
 385          "-istrip", "-inlcr", "-igncr", "-icrnl", "-ixon", "-ixoff", "-iuclc",
 386          "-ixany", "-imaxbel", "-opost", "-isig", "-icanon", "-xcase", NULL);
 387        new.c_cc[VMIN] = 1;
 388        new.c_cc[VTIME] = 0;
 389      } else if (!strcmp(arg, "nl"))
 390        set_options(&new, "-icrnl", "-ocrnl", NULL);
 391      else if (!strcmp(arg, "-nl"))
 392        set_options(&new, "icrnl", "ocrnl", "-inlcr", "-igncr", NULL);
 393      else if (!strcmp(arg, "ek")) {
 394        new.c_cc[VERASE] = 0x7f;
 395        new.c_cc[VKILL] = 0x15;
 396      } else if (!strcmp(arg, "sane")) make_sane(&new);
 397      else {
 398        // Translate historical cruft into canonical forms.
 399        for (j=0; j<ARRAY_LEN(synonyms); j++) {
 400          if (!strcmp(synonyms[j].from, arg)) {
 401            arg = synonyms[j].to;
 402            break;
 403          }
 404        }
 405        set_option(&new, arg);
 406      }
 407    }
 408
 409    tcsetattr(TT.fd, TCSAFLUSH, &new);
 410    xtcgetattr(&old);
 411    if (memcmp(&old, &new, sizeof(old)))
 412      error_exit("unable to perform all requested operations on %s", TT.F);
 413
 414    return;
 415  }
 416
 417  if (FLAG(g)) {
 418    xprintf("%x:%x:%x:%x:", old.c_iflag, old.c_oflag, old.c_cflag, old.c_lflag);
 419    for (i=0;i<NCCS;i++) xprintf("%x%c", old.c_cc[i], i==NCCS-1?'\n':':');
 420    return;
 421  }
 422
 423  // Without arguments, "stty" only shows the speed, the line discipline,
 424  // special characters and any flags that differ from the "sane" settings.
 425  make_sane(&sane);
 426  show_speed(&old, 1);
 427  if (FLAG(a)) show_size(1);
 428  out("line = %d;\n", old.c_line);
 429
 430  for (i=j=0; i<ARRAY_LEN(chars); i++) {
 431    char vis[16] = {};
 432    cc_t ch = old.c_cc[chars[i].value];
 433
 434    if (ch == sane.c_cc[chars[i].value] && !FLAG(a))
 435      continue;
 436
 437    if (chars[i].value == VMIN || chars[i].value == VTIME)
 438      snprintf(vis, sizeof(vis), "%u", ch);
 439    else if (ch == _POSIX_VDISABLE) strcat(vis, "<undef>");
 440    else {
 441      if (ch > 0x7f) {
 442        strcat(vis, "M-");
 443        ch -= 128;
 444      }
 445      if (ch < ' ') sprintf(vis+strlen(vis), "^%c", (ch+'@'));
 446      else if (ch == 0x7f) strcat(vis, "^?");
 447      else sprintf(vis+strlen(vis), "%c", ch);
 448    }
 449    out("%s = %s;", chars[i].name, vis);
 450    j++;
 451  }
 452  if (j) out("\n");
 453
 454  show_flags(old.c_cflag, sane.c_cflag, cflags, ARRAY_LEN(cflags));
 455  show_flags(old.c_iflag, sane.c_iflag, iflags, ARRAY_LEN(iflags));
 456  show_flags(old.c_oflag, sane.c_oflag, oflags, ARRAY_LEN(oflags));
 457  show_flags(old.c_lflag, sane.c_lflag, lflags, ARRAY_LEN(lflags));
 458
 459  if (TT.fd) close(TT.fd);
 460}
 461