1 /* stty.c - Get/set terminal configuration.
3 * Copyright 2017 The Android Open Source Project.
5 * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/stty.html
7 USE_STTY(NEWTOY(stty, "?aF:g[!ag]", TOYFLAG_BIN))
13 usage: stty [-ag] [-F device] SETTING...
15 Get/set terminal configuration.
17 -a Show all current settings (default differences from "sane").
18 -g Show all current settings usable as input to stty.
20 Special characters (syntax ^c or undef): intr quit erase kill eof eol eol2
21 swtch start stop susp rprnt werase lnext discard
23 Control/input/output/local settings as shown by -a, '-' prefix to disable
25 Combo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane
27 N set input and output speed (ispeed N or ospeed N for just one)
28 cols N set number of columns
29 rows N set number of rows
30 line N set line discipline
31 min N set minimum chars per read
32 time N set read timeout
40 #include <linux/tty.h>
49 static const int bauds[] = {
50 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
51 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600,
52 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
55 static int baud(speed_t speed)
57 if (speed&CBAUDEX) speed=(speed&~CBAUDEX)+15;
61 static speed_t speed(int baud)
65 for (i=0;i<ARRAY_LEN(bauds);i++) if (bauds[i] == baud) break;
66 if (i == ARRAY_LEN(bauds)) error_exit("unknown speed: %d", baud);
76 static const struct flag chars[] = {
88 { "rprnt", VREPRINT },
89 { "werase", VWERASE },
91 { "discard", VDISCARD },
96 static const struct flag cflags[] = {
100 { "cs5", CS5, CSIZE },
101 { "cs6", CS6, CSIZE },
102 { "cs7", CS7, CSIZE },
103 { "cs8", CS8, CSIZE },
105 { "cstopb", CSTOPB },
107 { "clocal", CLOCAL },
108 { "crtscts", CRTSCTS },
111 static const struct flag iflags[] = {
112 { "ignbrk", IGNBRK },
113 { "brkint", BRKINT },
114 { "ignpar", IGNPAR },
115 { "parmrk", PARMRK },
117 { "istrip", ISTRIP },
125 { "imaxbel", IMAXBEL },
129 static const struct flag oflags[] = {
135 { "onlret", ONLRET },
138 { "nl0", NL0, NLDLY },
139 { "nl1", NL1, NLDLY },
140 { "cr0", CR0, CRDLY },
141 { "cr1", CR1, CRDLY },
142 { "cr2", CR2, CRDLY },
143 { "cr3", CR3, CRDLY },
144 { "tab0", TAB0, TABDLY },
145 { "tab1", TAB1, TABDLY },
146 { "tab2", TAB2, TABDLY },
147 { "tab3", TAB3, TABDLY },
148 { "bs0", BS0, BSDLY },
149 { "bs1", BS1, BSDLY },
150 { "vt0", VT0, VTDLY },
151 { "vt1", VT1, VTDLY },
152 { "ff0", FF0, FFDLY },
153 { "ff1", FF1, FFDLY },
156 static const struct flag lflags[] = {
158 { "icanon", ICANON },
159 { "iexten", IEXTEN },
163 { "echonl", ECHONL },
164 { "noflsh", NOFLSH },
166 { "tostop", TOSTOP },
167 { "echoprt", ECHOPRT },
168 { "echoctl", ECHOCTL },
169 { "echoke", ECHOKE },
170 { "flusho", FLUSHO },
171 { "extproc", EXTPROC },
174 static const struct synonym {
178 { "cbreak", "-icanon" },
179 { "-cbreak", "icanon" },
180 { "-cooked", "raw" },
181 { "crterase", "echoe" },
182 { "-crterase", "-echoe" },
183 { "crtkill", "echoke" },
184 { "-crtkill", "-echoke" },
185 { "ctlecho", "echoctl" },
186 { "-ctlecho", "-echoctl" },
188 { "-hup", "-hupcl" },
189 { "prterase", "echoprt" },
190 { "-prterase", "-echoprt" },
191 { "-raw", "cooked" },
194 { "tandem", "ixoff" },
195 { "-tandem", "-ixoff" },
198 static void out(const char *fmt, ...)
205 len = vsnprintf(toybuf, sizeof(toybuf), fmt, va);
208 if (TT.output_cols == 0) {
210 terminal_size(&TT.output_cols, NULL);
213 if (TT.col == 0 || *fmt == '\n') prefix = "";
214 else if (TT.col + 1 + len >= TT.output_cols) {
218 xprintf("%s%s", prefix, toybuf);
220 if (toybuf[len-1] == '\n') TT.col = 0;
221 else TT.col += strlen(prefix) + len;
224 static void show_flags(tcflag_t actual, tcflag_t sane,
225 const struct flag *flags, int len)
227 int i, j, value, mask;
229 // Implement -a by ensuring that sane != actual so we'll show everything.
230 if (toys.optflags&FLAG_a) sane = ~actual;
232 for (i=j=0;i<len;i++) {
233 value = flags[i].value;
234 if ((mask = flags[i].mask)) {
235 if ((actual&mask)==value && (sane&mask)!=value) {
236 out("%s", flags[i].name);
240 if ((actual&value) != (sane&value)) {
241 out("%s%s", actual&value?"":"-", flags[i].name);
249 static void show_size(int verbose)
253 if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
254 out(verbose ? "rows %d; columns %d;" : "%d %d\n", ws.ws_row, ws.ws_col);
257 static void show_speed(struct termios *t, int verbose)
259 int ispeed = baud(cfgetispeed(t)), ospeed = baud(cfgetospeed(t));
260 char *fmt = verbose ? "ispeed %d baud; ospeed %d baud;" : "%d %d\n";
262 if (ispeed == ospeed) fmt += (verbose ? 17 : 3);
263 out(fmt, ispeed, ospeed);
266 static int get_arg(int *i, long long low, long long high)
269 if (!toys.optargs[*i]) error_exit("missing arg");
270 return atolx_range(toys.optargs[*i], low, high);
273 static int set_flag(tcflag_t *f, const struct flag *flags, int len,
278 for (i=0;i<len;i++) {
279 if (!strcmp(flags[i].name, name)) {
281 *f &= ~flags[i].mask;
282 *f |= flags[i].value;
284 if (flags[i].mask) error_exit("%s isn't a boolean", name);
285 *f &= ~flags[i].value;
293 static void set_option(struct termios *new, char *option)
295 int on = (*option != '-');
298 if (!set_flag(&new->c_cflag, cflags, ARRAY_LEN(cflags), option, on) &&
299 !set_flag(&new->c_iflag, iflags, ARRAY_LEN(iflags), option, on) &&
300 !set_flag(&new->c_oflag, oflags, ARRAY_LEN(oflags), option, on) &&
301 !set_flag(&new->c_lflag, lflags, ARRAY_LEN(lflags), option, on))
302 error_exit("unknown option: %s", option);
305 static void set_options(struct termios* new, ...)
311 while ((option = va_arg(va, char *))) {
312 set_option(new, option);
317 static void set_size(int is_rows, unsigned short value)
321 if (ioctl(TT.fd, TIOCGWINSZ, &ws)) perror_exit("TIOCGWINSZ %s", TT.device);
322 if (is_rows) ws.ws_row = value;
323 else ws.ws_col = value;
324 if (ioctl(TT.fd, TIOCSWINSZ, &ws)) perror_exit("TIOCSWINSZ %s", TT.device);
327 static int set_special_character(struct termios *new, int *i, char *char_name)
331 // The -2 is to ignore VMIN and VTIME, which are just unsigned integers.
332 for (j=0;j<ARRAY_LEN(chars)-2;j++) {
333 if (!strcmp(chars[j].name, char_name)) {
334 char *arg = toys.optargs[++(*i)];
337 if (!arg) error_exit("missing arg");
338 if (!strcmp(arg, "^-") || !strcmp(arg, "undef")) ch = _POSIX_VDISABLE;
339 else if (!strcmp(arg, "^?")) ch = 0x7f;
340 else if (arg[0] == '^' && arg[2] == 0) ch = (toupper(arg[1])-'@');
341 else if (!arg[1]) ch = arg[0];
342 else error_exit("invalid arg: %s", arg);
343 xprintf("setting %s to %s (%02x)\n", char_name, arg, ch);
344 new->c_cc[chars[j].value] = ch;
351 static void make_sane(struct termios *t)
353 // POSIX has no opinion about what "sane" means. From "man stty".
354 // "cs8" is missing from the man page, but needed to get identical results.
355 set_options(t, "cread", "-ignbrk", "brkint", "-inlcr", "-igncr", "icrnl",
356 "icanon", "iexten", "echo", "echoe", "echok", "-echonl", "-noflsh",
357 "-ixoff", "-iutf8", "-iuclc", "-ixany", "imaxbel", "-xcase", "-olcuc",
358 "-ocrnl", "opost", "-ofill", "onlcr", "-onocr", "-onlret", "nl0", "cr0",
359 "tab0", "bs0", "vt0", "ff0", "isig", "-tostop", "-ofdel", "-echoprt",
360 "echoctl", "echoke", "-extproc", "-flusho", "cs8", NULL);
361 memset(t->c_cc, 0, NCCS);
362 t->c_cc[VINTR] = 0x3;
363 t->c_cc[VQUIT] = 0x1c;
364 t->c_cc[VERASE] = 0x7f;
365 t->c_cc[VKILL] = 0x15;
370 t->c_cc[VSTART] = 0x11;
371 t->c_cc[VSTOP] = 0x13;
372 t->c_cc[VSUSP] = 0x1a;
374 t->c_cc[VREPRINT] = 0x12;
375 t->c_cc[VDISCARD] = 0xf;
376 t->c_cc[VWERASE] = 0x17;
377 t->c_cc[VLNEXT] = 0x16;
381 static void do_stty()
383 struct termios old, sane;
386 if (tcgetattr(TT.fd, &old)) perror_exit("tcgetattr %s", TT.device);
389 struct termios new = old;
391 for (i=0; toys.optargs[i]; i++) {
392 char *arg = toys.optargs[i];
394 if (!strcmp(arg, "size")) show_size(0);
395 else if (!strcmp(arg, "speed")) show_speed(&old, 0);
396 else if (!strcmp(arg, "line")) new.c_line = get_arg(&i, N_TTY, NR_LDISCS);
397 else if (!strcmp(arg, "min")) new.c_cc[VMIN] = get_arg(&i, 0, 255);
398 else if (!strcmp(arg, "time")) new.c_cc[VTIME] = get_arg(&i, 0, 255);
399 else if (atoi(arg) > 0) {
400 int new_speed = speed(atolx_range(arg, 0, 4000000));
402 cfsetispeed(&new, new_speed);
403 cfsetospeed(&new, new_speed);
404 } else if (!strcmp(arg, "ispeed")) {
405 cfsetispeed(&new, speed(get_arg(&i, 0, 4000000)));
406 } else if (!strcmp(arg, "ospeed")) {
407 cfsetospeed(&new, speed(get_arg(&i, 0, 4000000)));
408 } else if (!strcmp(arg, "rows")) {
409 set_size(1, get_arg(&i, 0, USHRT_MAX));
410 } else if (!strcmp(arg, "cols") || !strcmp(arg, "columns")) {
411 set_size(0, get_arg(&i, 0, USHRT_MAX));
412 } else if (sscanf(arg, "%x:%x:%x:%x:%n", &new.c_iflag, &new.c_oflag,
413 &new.c_cflag, &new.c_lflag, &n) == 4) {
417 for (j=0;j<NCCS;j++) {
418 if (sscanf(arg, "%x%n", &value, &n) != 1) error_exit("bad -g string");
422 } else if (set_special_character(&new, &i, arg)) {
423 // Already done as a side effect.
424 } else if (!strcmp(arg, "cooked")) {
425 set_options(&new, "brkint", "ignpar", "istrip", "icrnl", "ixon",
426 "opost", "isig", "icanon", NULL);
427 } else if (!strcmp(arg, "evenp") || !strcmp(arg, "parity")) {
428 set_options(&new, "parenb", "cs7", "-parodd", NULL);
429 } else if (!strcmp(arg, "oddp")) {
430 set_options(&new, "parenb", "cs7", "parodd", NULL);
431 } else if (!strcmp(arg, "-parity") || !strcmp(arg, "-evenp") ||
432 !strcmp(arg, "-oddp")) {
433 set_options(&new, "-parenb", "cs8", NULL);
434 } else if (!strcmp(arg, "raw")) {
435 // POSIX and "man stty" differ wildly. This is "man stty".
436 set_options(&new, "-ignbrk", "-brkint", "-ignpar", "-parmrk", "-inpck",
437 "-istrip", "-inlcr", "-igncr", "-icrnl", "-ixon", "-ixoff", "-iuclc",
438 "-ixany", "-imaxbel", "-opost", "-isig", "-icanon", "-xcase", NULL);
441 } else if (!strcmp(arg, "nl")) {
442 set_options(&new, "-icrnl", "-ocrnl", NULL);
443 } else if (!strcmp(arg, "-nl")) {
444 set_options(&new, "icrnl", "ocrnl", "-inlcr", "-igncr", NULL);
445 } else if (!strcmp(arg, "ek")) {
446 new.c_cc[VERASE] = 0x7f;
447 new.c_cc[VKILL] = 0x15;
448 } else if (!strcmp(arg, "sane")) {
451 // Translate historical cruft into canonical forms.
452 for (j=0;j<ARRAY_LEN(synonyms);j++) {
453 if (!strcmp(synonyms[j].from, arg)) {
454 arg = synonyms[j].to;
458 set_option(&new, arg);
461 if (tcsetattr(TT.fd,TCSAFLUSH,&new)) perror_exit("tcsetattr %s",TT.device);
463 // tcsetattr returns success if *any* change is made, so double-check...
465 if (tcgetattr(TT.fd,&new)) perror_exit("tcgetattr %s",TT.device);
466 if (memcmp(&old, &new, sizeof(old)))
467 error_exit("unable to perform all requested operations on %s", TT.device);
471 if (toys.optflags&FLAG_g) {
472 xprintf("%x:%x:%x:%x:", old.c_iflag, old.c_oflag, old.c_cflag, old.c_lflag);
473 for (i=0;i<NCCS;i++) xprintf("%x%c", old.c_cc[i], i==NCCS-1?'\n':':');
477 // Without arguments, "stty" only shows the speed, the line discipline,
478 // special characters and any flags that differ from the "sane" settings.
481 if (toys.optflags&FLAG_a) show_size(1);
482 out("line = %d;\n", old.c_line);
484 for (i=j=0;i<ARRAY_LEN(chars);i++) {
486 cc_t ch = old.c_cc[chars[i].value];
488 if (ch == sane.c_cc[chars[i].value] && (toys.optflags&FLAG_a)==0)
491 if (chars[i].value == VMIN || chars[i].value == VTIME) {
492 snprintf(vis, sizeof(vis), "%u", ch);
493 } else if (ch == _POSIX_VDISABLE) {
494 strcat(vis, "<undef>");
500 if (ch < ' ') sprintf(vis+strlen(vis), "^%c", (ch+'@'));
501 else if (ch == 0x7f) strcat(vis, "^?");
502 else sprintf(vis+strlen(vis), "%c", ch);
504 out("%s = %s;", chars[i].name, vis);
509 show_flags(old.c_cflag, sane.c_cflag, cflags, ARRAY_LEN(cflags));
510 show_flags(old.c_iflag, sane.c_iflag, iflags, ARRAY_LEN(iflags));
511 show_flags(old.c_oflag, sane.c_oflag, oflags, ARRAY_LEN(oflags));
512 show_flags(old.c_lflag, sane.c_lflag, lflags, ARRAY_LEN(lflags));
517 if (toys.optflags&(FLAG_a|FLAG_g) && *toys.optargs)
518 error_exit("can't make settings with -a/-g");
521 TT.fd=xopen(TT.device,(*toys.optargs?O_RDWR:O_RDONLY)|O_NOCTTY|O_NONBLOCK);
525 TT.device = "standard input";