Serenity Operating System
at master 608 lines 21 kB view raw
1/* 2 * Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev> 3 * Copyright (c) 2022, Alex Major 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#define __USE_MISC 9#define TTYDEFCHARS 10#include <AK/DeprecatedString.h> 11#include <AK/Optional.h> 12#include <AK/Result.h> 13#include <AK/ScopeGuard.h> 14#include <AK/StringView.h> 15#include <AK/Vector.h> 16#include <LibCore/System.h> 17#include <LibMain/Main.h> 18#include <ctype.h> 19#include <fcntl.h> 20#include <getopt.h> 21#include <stdio.h> 22#include <sys/ioctl.h> 23#include <sys/ttydefaults.h> 24#include <termios.h> 25#include <unistd.h> 26 27constexpr option long_options[] = { 28 { "all", no_argument, 0, 'a' }, 29 { "save", no_argument, 0, 'g' }, 30 { "file", required_argument, 0, 'F' }, 31 { 0, 0, 0, 0 } 32}; 33 34struct TermiosFlag { 35 StringView name; 36 tcflag_t value; 37 tcflag_t mask; 38}; 39 40struct BaudRate { 41 speed_t speed; 42 unsigned long numeric_value; 43}; 44 45struct ControlCharacter { 46 StringView name; 47 unsigned index; 48}; 49 50constexpr TermiosFlag all_iflags[] = { 51 { "ignbrk"sv, IGNBRK, IGNBRK }, 52 { "brkint"sv, BRKINT, BRKINT }, 53 { "ignpar"sv, IGNPAR, IGNPAR }, 54 { "parmer"sv, PARMRK, PARMRK }, 55 { "inpck"sv, INPCK, INPCK }, 56 { "istrip"sv, ISTRIP, ISTRIP }, 57 { "inlcr"sv, INLCR, INLCR }, 58 { "igncr"sv, IGNCR, IGNCR }, 59 { "icrnl"sv, ICRNL, ICRNL }, 60 { "iuclc"sv, IUCLC, IUCLC }, 61 { "ixon"sv, IXON, IXON }, 62 { "ixany"sv, IXANY, IXANY }, 63 { "ixoff"sv, IXOFF, IXOFF }, 64 { "imaxbel"sv, IMAXBEL, IMAXBEL }, 65 { "iutf8"sv, IUTF8, IUTF8 } 66}; 67 68constexpr TermiosFlag all_oflags[] = { 69 { "opost"sv, OPOST, OPOST }, 70 { "olcuc"sv, OLCUC, OPOST }, 71 { "onlcr"sv, ONLCR, ONLCR }, 72 { "onlret"sv, ONLRET, ONLRET }, 73 { "ofill"sv, OFILL, OFILL }, 74 { "ofdel"sv, OFDEL, OFDEL }, 75}; 76 77constexpr TermiosFlag all_cflags[] = { 78 { "cs5"sv, CS5, CSIZE }, 79 { "cs6"sv, CS6, CSIZE }, 80 { "cs7"sv, CS7, CSIZE }, 81 { "cs8"sv, CS8, CSIZE }, 82 { "cstopb"sv, CSTOPB, CSTOPB }, 83 { "cread"sv, CREAD, CREAD }, 84 { "parenb"sv, PARENB, PARENB }, 85 { "parodd"sv, PARODD, PARODD }, 86 { "hupcl"sv, HUPCL, HUPCL }, 87 { "clocal"sv, CLOCAL, CLOCAL }, 88}; 89 90constexpr TermiosFlag all_lflags[] = { 91 { "isig"sv, ISIG, ISIG }, 92 { "icanon"sv, ICANON, ICANON }, 93 { "echo"sv, ECHO, ECHO }, 94 { "echoe"sv, ECHOE, ECHOE }, 95 { "echok"sv, ECHOK, ECHOK }, 96 { "echonl"sv, ECHONL, ECHONL }, 97 { "noflsh"sv, NOFLSH, NOFLSH }, 98 { "tostop"sv, TOSTOP, TOSTOP }, 99 { "iexten"sv, IEXTEN, IEXTEN } 100}; 101 102constexpr BaudRate baud_rates[] = { 103 { B0, 0 }, 104 { B50, 50 }, 105 { B75, 75 }, 106 { B110, 110 }, 107 { B134, 134 }, 108 { B150, 150 }, 109 { B200, 200 }, 110 { B300, 300 }, 111 { B600, 600 }, 112 { B1200, 1200 }, 113 { B1800, 1800 }, 114 { B2400, 2400 }, 115 { B4800, 4800 }, 116 { B9600, 9600 }, 117 { B19200, 19200 }, 118 { B38400, 38400 }, 119 { B57600, 57600 }, 120 { B115200, 115200 }, 121 { B230400, 230400 }, 122 { B460800, 460800 }, 123 { B500000, 500000 }, 124 { B576000, 576000 }, 125 { B921600, 921600 }, 126 { B1000000, 1000000 }, 127 { B1152000, 1152000 }, 128 { B1500000, 1500000 }, 129 { B2000000, 2000000 }, 130 { B2500000, 2500000 }, 131 { B3000000, 3000000 }, 132 { B3500000, 3500000 }, 133 { B4000000, 4000000 } 134}; 135 136constexpr ControlCharacter control_characters[] = { 137 { "intr"sv, VINTR }, 138 { "quit"sv, VQUIT }, 139 { "erase"sv, VERASE }, 140 { "kill"sv, VKILL }, 141 { "eof"sv, VEOF }, 142 /* time and min are handled separately */ 143 { "swtc"sv, VSWTC }, 144 { "start"sv, VSTART }, 145 { "stop"sv, VSTOP }, 146 { "susp"sv, VSUSP }, 147 { "eol"sv, VEOL }, 148 { "reprint"sv, VREPRINT }, 149 { "discard"sv, VDISCARD }, 150 { "werase"sv, VWERASE }, 151 { "lnext"sv, VLNEXT }, 152 { "eol2"sv, VEOL2 } 153}; 154 155Optional<speed_t> numeric_value_to_speed(unsigned long); 156Optional<unsigned long> speed_to_numeric_value(speed_t); 157 158void print_stty_readable(termios const&); 159void print_human_readable(termios const&, winsize const&, bool); 160Result<void, int> apply_stty_readable_modes(StringView, termios&); 161Result<void, int> apply_modes(size_t, char**, termios&, winsize&); 162 163Optional<speed_t> numeric_value_to_speed(unsigned long numeric_value) 164{ 165 for (auto rate : baud_rates) { 166 if (rate.numeric_value == numeric_value) 167 return rate.speed; 168 } 169 return {}; 170} 171 172Optional<unsigned long> speed_to_numeric_value(speed_t speed) 173{ 174 for (auto rate : baud_rates) { 175 if (rate.speed == speed) 176 return rate.numeric_value; 177 } 178 return {}; 179} 180 181void print_stty_readable(termios const& modes) 182{ 183 out("{:x}:{:x}:{:x}:{:x}", modes.c_iflag, modes.c_oflag, modes.c_cflag, modes.c_lflag); 184 for (size_t i = 0; i < NCCS; ++i) 185 out(":{:x}", modes.c_cc[i]); 186 out(":{:x}:{:x}\n", modes.c_ispeed, modes.c_ospeed); 187} 188 189void print_human_readable(termios const& modes, winsize const& ws, bool verbose_mode) 190{ 191 auto print_speed = [&] { 192 if (verbose_mode && modes.c_ispeed != modes.c_ospeed) { 193 out("ispeed {} baud; ospeed {} baud;", speed_to_numeric_value(modes.c_ispeed).value(), speed_to_numeric_value(modes.c_ospeed).value()); 194 195 } else { 196 out("speed {} baud;", speed_to_numeric_value(modes.c_ispeed).value()); 197 } 198 }; 199 200 auto print_winsize = [&] { 201 out("rows {}; columns {};", ws.ws_row, ws.ws_col); 202 }; 203 204 auto escape_character = [&](u8 ch) { 205 StringBuilder sb; 206 if (ch <= 0x20) { 207 sb.append('^'); 208 sb.append(ch + 0x40); 209 } else if (ch == 0x7f) { 210 sb.append("^?"sv); 211 } else { 212 sb.append(ch); 213 } 214 return sb.to_deprecated_string(); 215 }; 216 217 auto print_control_characters = [&] { 218 bool first_in_line = true; 219 for (auto cc : control_characters) { 220 if (verbose_mode || modes.c_cc[cc.index] != ttydefchars[cc.index]) { 221 out("{}{} = {};", (first_in_line) ? "" : " ", cc.name, escape_character(modes.c_cc[cc.index])); 222 first_in_line = false; 223 } 224 } 225 if (!first_in_line) 226 out("\n"); 227 }; 228 229 auto print_flags_of_type = [&](const TermiosFlag flags[], size_t flag_count, tcflag_t field_value, tcflag_t field_default) { 230 bool first_in_line = true; 231 for (size_t i = 0; i < flag_count; ++i) { 232 auto& flag = flags[i]; 233 if (verbose_mode || (field_value & flag.mask) != (field_default & flag.mask)) { 234 bool set = (field_value & flag.mask) == flag.value; 235 out("{}{}{}", first_in_line ? "" : " ", set ? "" : "-", flag.name); 236 first_in_line = false; 237 } 238 } 239 if (!first_in_line) 240 out("\n"); 241 }; 242 243 auto print_flags = [&] { 244 print_flags_of_type(all_cflags, sizeof(all_cflags) / sizeof(TermiosFlag), modes.c_cflag, TTYDEF_CFLAG); 245 print_flags_of_type(all_oflags, sizeof(all_oflags) / sizeof(TermiosFlag), modes.c_oflag, TTYDEF_OFLAG); 246 print_flags_of_type(all_iflags, sizeof(all_iflags) / sizeof(TermiosFlag), modes.c_iflag, TTYDEF_IFLAG); 247 print_flags_of_type(all_lflags, sizeof(all_lflags) / sizeof(TermiosFlag), modes.c_lflag, TTYDEF_LFLAG); 248 }; 249 250 print_speed(); 251 out(" "); 252 print_winsize(); 253 out("\n"); 254 print_control_characters(); 255 print_flags(); 256} 257 258Result<void, int> apply_stty_readable_modes(StringView mode_string, termios& t) 259{ 260 auto split = mode_string.split_view(':'); 261 if (split.size() != 4 + NCCS + 2) { 262 warnln("Save string has an incorrect number of parameters"); 263 return 1; 264 } 265 auto parse_hex = [&](StringView v) { 266 tcflag_t ret = 0; 267 for (auto c : v) { 268 c = tolower(c); 269 ret *= 16; 270 if (isdigit(c)) { 271 ret += c - '0'; 272 } else { 273 VERIFY(c >= 'a' && c <= 'f'); 274 ret += c - 'a'; 275 } 276 } 277 return ret; 278 }; 279 280 t.c_iflag = parse_hex(split[0]); 281 t.c_oflag = parse_hex(split[1]); 282 t.c_cflag = parse_hex(split[2]); 283 t.c_lflag = parse_hex(split[3]); 284 for (size_t i = 0; i < NCCS; ++i) { 285 t.c_cc[i] = (cc_t)parse_hex(split[4 + i]); 286 } 287 t.c_ispeed = parse_hex(split[4 + NCCS]); 288 t.c_ospeed = parse_hex(split[4 + NCCS + 1]); 289 return {}; 290} 291 292Result<void, int> apply_modes(size_t parameter_count, char** raw_parameters, termios& t, winsize& w) 293{ 294 Vector<StringView> parameters; 295 parameters.ensure_capacity(parameter_count); 296 for (size_t i = 0; i < parameter_count; ++i) 297 parameters.append(StringView { raw_parameters[i], strlen(raw_parameters[i]) }); 298 299 auto parse_baud = [&](size_t idx) -> Optional<speed_t> { 300 auto maybe_numeric_value = parameters[idx].to_uint<uint32_t>(); 301 if (maybe_numeric_value.has_value()) 302 return numeric_value_to_speed(maybe_numeric_value.value()); 303 return {}; 304 }; 305 306 auto parse_number = [&](size_t idx) -> Optional<cc_t> { 307 return parameters[idx].to_uint<cc_t>(); 308 }; 309 310 auto looks_like_stty_readable = [&](size_t idx) { 311 bool contains_colon = false; 312 for (auto c : parameters[idx]) { 313 c = tolower(c); 314 if (!isdigit(c) && !(c >= 'a' && c <= 'f') && c != ':') 315 return false; 316 if (c == ':') 317 contains_colon = true; 318 } 319 return contains_colon; 320 }; 321 322 auto parse_control_character = [&](size_t idx) -> Optional<cc_t> { 323 VERIFY(!parameters[idx].is_empty()); 324 if (parameters[idx] == "^-" || parameters[idx] == "undef") { 325 // FIXME: disabling characters is a bit wonky right now in TTY. 326 // We should add the _POSIX_VDISABLE macro. 327 return 0; 328 } else if (parameters[idx][0] == '^' && parameters[idx].length() == 2) { 329 return toupper(parameters[idx][1]) - 0x40; 330 } else if (parameters[idx].starts_with("0x"sv)) { 331 cc_t value = 0; 332 if (parameters[idx].length() == 2) { 333 warnln("Invalid hexadecimal character code {}", parameters[idx]); 334 return {}; 335 } 336 for (size_t i = 2; i < parameters[idx].length(); ++i) { 337 char ch = tolower(parameters[idx][i]); 338 if (!isdigit(ch) && !(ch >= 'a' && ch <= 'f')) { 339 warnln("Invalid hexadecimal character code {}", parameters[idx]); 340 return {}; 341 } 342 value = 16 * value + (isdigit(ch)) ? (ch - '0') : (ch - 'a'); 343 } 344 return value; 345 } else if (parameters[idx].starts_with("0"sv)) { 346 cc_t value = 0; 347 for (size_t i = 1; i < parameters[idx].length(); ++i) { 348 char ch = parameters[idx][i]; 349 if (!(ch >= '0' && ch <= '7')) { 350 warnln("Invalid octal character code {}", parameters[idx]); 351 return {}; 352 } 353 value = 8 * value + (ch - '0'); 354 } 355 return value; 356 } else if (isdigit(parameters[idx][0])) { 357 auto maybe_value = parameters[idx].to_uint<cc_t>(); 358 if (!maybe_value.has_value()) { 359 warnln("Invalid decimal character code {}", parameters[idx]); 360 return {}; 361 } 362 return maybe_value.value(); 363 } else if (parameters[idx].length() == 1) { 364 return parameters[idx][0]; 365 } 366 warnln("Invalid control character {}", parameters[idx]); 367 return {}; 368 }; 369 370 size_t parameter_idx = 0; 371 372 auto parse_flag_or_char = [&]() -> Result<void, int> { 373 if (parameters[parameter_idx][0] != '-') { 374 if (parameters[parameter_idx] == "min") { 375 auto maybe_number = parse_number(++parameter_idx); 376 if (!maybe_number.has_value()) { 377 warnln("Error parsing min: {} is not a number", parameters[parameter_idx]); 378 return 1; 379 } 380 return {}; 381 } else if (parameters[parameter_idx] == "time") { 382 auto maybe_number = parse_number(++parameter_idx); 383 if (!maybe_number.has_value()) { 384 warnln("Error parsing time: {} is not a number", parameters[parameter_idx]); 385 return 1; 386 } 387 return {}; 388 } else { 389 for (auto cc : control_characters) { 390 if (cc.name == parameters[parameter_idx]) { 391 if (parameter_idx == parameter_count - 1) { 392 warnln("No control character specified for {}", cc.name); 393 return 1; 394 } 395 auto maybe_control_character = parse_control_character(++parameter_idx); 396 if (!maybe_control_character.has_value()) 397 return 1; 398 t.c_cc[cc.index] = maybe_control_character.value(); 399 return {}; 400 } 401 } 402 } 403 } 404 405 // We fall through to here if what we are setting is not a control character. 406 bool negate = false; 407 if (parameters[parameter_idx][0] == '-') { 408 negate = true; 409 parameters[parameter_idx] = parameters[parameter_idx].substring_view(1); 410 } 411 412 auto perform_masking = [&](tcflag_t value, tcflag_t mask, tcflag_t& dest) { 413 if (negate) 414 dest &= ~mask; 415 else 416 dest = (dest & (~mask)) | value; 417 }; 418 419 for (auto flag : all_iflags) { 420 if (flag.name == parameters[parameter_idx]) { 421 perform_masking(flag.value, flag.mask, t.c_iflag); 422 return {}; 423 } 424 } 425 for (auto flag : all_oflags) { 426 if (flag.name == parameters[parameter_idx]) { 427 perform_masking(flag.value, flag.mask, t.c_oflag); 428 return {}; 429 } 430 } 431 for (auto flag : all_cflags) { 432 if (flag.name == parameters[parameter_idx]) { 433 perform_masking(flag.value, flag.mask, t.c_cflag); 434 return {}; 435 } 436 } 437 for (auto flag : all_lflags) { 438 if (flag.name == parameters[parameter_idx]) { 439 perform_masking(flag.value, flag.mask, t.c_lflag); 440 return {}; 441 } 442 } 443 warnln("Invalid control flag or control character name {}", parameters[parameter_idx]); 444 return 1; 445 }; 446 447 while (parameter_idx < parameter_count) { 448 if (looks_like_stty_readable(parameter_idx)) { 449 auto maybe_error = apply_stty_readable_modes(parameters[parameter_idx], t); 450 if (maybe_error.is_error()) 451 return maybe_error.error(); 452 } else if (isdigit(parameters[parameter_idx][0])) { 453 auto new_baud = parse_baud(parameter_idx); 454 if (!new_baud.has_value()) { 455 warnln("Invalid baud rate {}", parameters[parameter_idx]); 456 return 1; 457 } 458 t.c_ispeed = t.c_ospeed = new_baud.value(); 459 } else if (parameters[parameter_idx] == "ispeed") { 460 if (parameter_idx == parameter_count - 1) { 461 warnln("No baud rate specified for ispeed"); 462 return 1; 463 } 464 auto new_baud = parse_baud(++parameter_idx); 465 if (!new_baud.has_value()) { 466 warnln("Invalid input baud rate {}", parameters[parameter_idx]); 467 return 1; 468 } 469 t.c_ispeed = new_baud.value(); 470 } else if (parameters[parameter_idx] == "ospeed") { 471 if (parameter_idx == parameter_count - 1) { 472 warnln("No baud rate specified for ospeed"); 473 return 1; 474 } 475 auto new_baud = parse_baud(++parameter_idx); 476 if (!new_baud.has_value()) { 477 warnln("Invalid output baud rate {}", parameters[parameter_idx]); 478 return 1; 479 } 480 t.c_ospeed = new_baud.value(); 481 } else if (parameters[parameter_idx] == "columns" || parameters[parameter_idx] == "cols") { 482 auto maybe_number = parse_number(++parameter_idx); 483 if (!maybe_number.has_value()) { 484 warnln("Invalid column count {}", parameters[parameter_idx]); 485 return 1; 486 } 487 w.ws_col = maybe_number.value(); 488 } else if (parameters[parameter_idx] == "rows") { 489 auto maybe_number = parse_number(++parameter_idx); 490 if (!maybe_number.has_value()) { 491 warnln("Invalid row count {}", parameters[parameter_idx]); 492 return 1; 493 } 494 w.ws_row = maybe_number.value(); 495 } else if (parameters[parameter_idx] == "evenp" || parameters[parameter_idx] == "parity") { 496 t.c_cflag &= ~(CSIZE | PARODD); 497 t.c_cflag |= CS7 | PARENB; 498 } else if (parameters[parameter_idx] == "oddp") { 499 t.c_cflag &= ~(CSIZE); 500 t.c_cflag |= CS7 | PARENB | PARODD; 501 } else if (parameters[parameter_idx] == "-parity" || parameters[parameter_idx] == "-evenp" || parameters[parameter_idx] == "-oddp") { 502 t.c_cflag &= ~(PARENB | CSIZE); 503 t.c_cflag |= CS8; 504 } else if (parameters[parameter_idx] == "raw") { 505 cfmakeraw(&t); 506 } else if (parameters[parameter_idx] == "nl") { 507 t.c_iflag &= ~ICRNL; 508 } else if (parameters[parameter_idx] == "-nl") { 509 t.c_cflag &= ~(INLCR & IGNCR); 510 t.c_iflag |= ICRNL; 511 } else if (parameters[parameter_idx] == "ek") { 512 t.c_cc[VERASE] = CERASE; 513 t.c_cc[VKILL] = CKILL; 514 } else if (parameters[parameter_idx] == "sane") { 515 t.c_iflag = TTYDEF_IFLAG; 516 t.c_oflag = TTYDEF_OFLAG; 517 t.c_cflag = TTYDEF_CFLAG; 518 t.c_lflag = TTYDEF_LFLAG; 519 for (size_t i = 0; i < NCCS; ++i) 520 t.c_cc[i] = ttydefchars[i]; 521 t.c_ispeed = t.c_ospeed = TTYDEF_SPEED; 522 } else { 523 auto maybe_error = parse_flag_or_char(); 524 if (maybe_error.is_error()) 525 return maybe_error.error(); 526 } 527 ++parameter_idx; 528 } 529 return {}; 530} 531 532ErrorOr<int> serenity_main(Main::Arguments arguments) 533{ 534 TRY(Core::System::pledge("stdio tty rpath")); 535 TRY(Core::System::unveil("/dev", "r")); 536 TRY(Core::System::unveil(nullptr, nullptr)); 537 538 DeprecatedString device_file; 539 bool stty_readable = false; 540 bool all_settings = false; 541 542 // Core::ArgsParser can't handle the weird syntax of stty, so we use getopt_long instead. 543 int argc = arguments.argc; 544 char** argv = arguments.argv; 545 opterr = 0; // We handle unknown flags gracefully by starting to parse the arguments in `apply_modes`. 546 int optc; 547 bool should_quit = false; 548 while (!should_quit && ((optc = getopt_long(argc, argv, "-agF:", long_options, nullptr)) != -1)) { 549 switch (optc) { 550 case 'a': 551 all_settings = true; 552 break; 553 case 'g': 554 stty_readable = true; 555 break; 556 case 'F': 557 if (!device_file.is_empty()) { 558 warnln("Only one device may be specified"); 559 exit(1); 560 } 561 device_file = optarg; 562 break; 563 default: 564 should_quit = true; 565 break; 566 } 567 } 568 569 if (stty_readable && all_settings) { 570 warnln("Save mode and all-settings mode are mutually exclusive"); 571 exit(1); 572 } 573 574 int terminal_fd = STDIN_FILENO; 575 if (!device_file.is_empty()) { 576 if ((terminal_fd = open(device_file.characters(), O_RDONLY, 0)) < 0) { 577 perror("open"); 578 exit(1); 579 } 580 } 581 582 ScopeGuard file_close_guard = [&] { close(terminal_fd); }; 583 584 termios initial_termios = TRY(Core::System::tcgetattr(terminal_fd)); 585 586 winsize initial_winsize; 587 TRY(Core::System::ioctl(terminal_fd, TIOCGWINSZ, &initial_winsize)); 588 589 if (optind < argc) { 590 if (stty_readable || all_settings) { 591 warnln("Modes cannot be set when printing settings"); 592 exit(1); 593 } 594 595 auto result = apply_modes(argc - optind, argv + optind, initial_termios, initial_winsize); 596 if (result.is_error()) 597 return result.error(); 598 599 TRY(Core::System::tcsetattr(terminal_fd, TCSADRAIN, initial_termios)); 600 TRY(Core::System::ioctl(terminal_fd, TIOCSWINSZ, &initial_winsize)); 601 602 } else if (stty_readable) { 603 print_stty_readable(initial_termios); 604 } else { 605 print_human_readable(initial_termios, initial_winsize, all_settings); 606 } 607 return 0; 608}