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 7 USE_STTY(NEWTOY(stty, "?aF:g[!ag]", TOYFLAG_BIN)) 8 9 config 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 43 GLOBALS( 44 char *device; 45 46 int fd, col; 47 unsigned output_cols; 48 ) 49 50 static 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 56 static int baud(speed_t speed) 57 { 58 if (speed&CBAUDEX) speed=(speed&~CBAUDEX)+15; 59 return bauds[speed]; 60 } 61 62 static 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 71 struct flag { 72 char *name; 73 int value; 74 int mask; 75 }; 76 77 static 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 85 static 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 92 static 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 100 static 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 111 static 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 119 static 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 131 static 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 157 static 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 182 static 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 190 static 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 199 static 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 206 static 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 226 static 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 238 static 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 248 static 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 258 static 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 282 static 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 312 static void xtcgetattr(struct termios *t) 313 { 314 if (tcgetattr(TT.fd, t)) perror_exit("tcgetattr %s", TT.device); 315 } 316 317 static 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 448 void 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