xref: /third_party/toybox/toys/pending/stty.c (revision 0f66f451)
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