123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609 |
- /*
- * Copyright (c) 2021, Daniel Bertalan <dani@danielbertalan.dev>
- * Copyright (c) 2022, Alex Major
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #define __USE_MISC
- #define TTYDEFCHARS
- #include <AK/Array.h>
- #include <AK/Optional.h>
- #include <AK/Result.h>
- #include <AK/ScopeGuard.h>
- #include <AK/String.h>
- #include <AK/StringView.h>
- #include <AK/Vector.h>
- #include <LibCore/System.h>
- #include <LibMain/Main.h>
- #include <ctype.h>
- #include <fcntl.h>
- #include <getopt.h>
- #include <stdio.h>
- #include <sys/ioctl.h>
- #include <sys/ttydefaults.h>
- #include <termios.h>
- #include <unistd.h>
- constexpr option long_options[] = {
- { "all", no_argument, 0, 'a' },
- { "save", no_argument, 0, 'g' },
- { "file", required_argument, 0, 'F' },
- { 0, 0, 0, 0 }
- };
- struct TermiosFlag {
- StringView name;
- tcflag_t value;
- tcflag_t mask;
- };
- struct BaudRate {
- speed_t speed;
- unsigned long numeric_value;
- };
- struct ControlCharacter {
- StringView name;
- unsigned index;
- };
- constexpr TermiosFlag all_iflags[] = {
- { "ignbrk", IGNBRK, IGNBRK },
- { "brkint", BRKINT, BRKINT },
- { "ignpar", IGNPAR, IGNPAR },
- { "parmer", PARMRK, PARMRK },
- { "inpck", INPCK, INPCK },
- { "istrip", ISTRIP, ISTRIP },
- { "inlcr", INLCR, INLCR },
- { "igncr", IGNCR, IGNCR },
- { "icrnl", ICRNL, ICRNL },
- { "iuclc", IUCLC, IUCLC },
- { "ixon", IXON, IXON },
- { "ixany", IXANY, IXANY },
- { "ixoff", IXOFF, IXOFF },
- { "imaxbel", IMAXBEL, IMAXBEL },
- { "iutf8", IUTF8, IUTF8 }
- };
- constexpr TermiosFlag all_oflags[] = {
- { "opost", OPOST, OPOST },
- { "olcuc", OLCUC, OPOST },
- { "onlcr", ONLCR, ONLCR },
- { "onlret", ONLRET, ONLRET },
- { "ofill", OFILL, OFILL },
- { "ofdel", OFDEL, OFDEL },
- };
- constexpr TermiosFlag all_cflags[] = {
- { "cs5", CS5, CSIZE },
- { "cs6", CS6, CSIZE },
- { "cs7", CS7, CSIZE },
- { "cs8", CS8, CSIZE },
- { "cstopb", CSTOPB, CSTOPB },
- { "cread", CREAD, CREAD },
- { "parenb", PARENB, PARENB },
- { "parodd", PARODD, PARODD },
- { "hupcl", HUPCL, HUPCL },
- { "clocal", CLOCAL, CLOCAL },
- };
- constexpr TermiosFlag all_lflags[] = {
- { "isig", ISIG, ISIG },
- { "icanon", ICANON, ICANON },
- { "echo", ECHO, ECHO },
- { "echoe", ECHOE, ECHOE },
- { "echok", ECHOK, ECHOK },
- { "echonl", ECHONL, ECHONL },
- { "noflsh", NOFLSH, NOFLSH },
- { "tostop", TOSTOP, TOSTOP },
- { "iexten", IEXTEN, IEXTEN }
- };
- constexpr BaudRate baud_rates[] = {
- { B0, 0 },
- { B50, 50 },
- { B75, 75 },
- { B110, 110 },
- { B134, 134 },
- { B150, 150 },
- { B200, 200 },
- { B300, 300 },
- { B600, 600 },
- { B1200, 1200 },
- { B1800, 1800 },
- { B2400, 2400 },
- { B4800, 4800 },
- { B9600, 9600 },
- { B19200, 19200 },
- { B38400, 38400 },
- { B57600, 57600 },
- { B115200, 115200 },
- { B230400, 230400 },
- { B460800, 460800 },
- { B500000, 500000 },
- { B576000, 576000 },
- { B921600, 921600 },
- { B1000000, 1000000 },
- { B1152000, 1152000 },
- { B1500000, 1500000 },
- { B2000000, 2000000 },
- { B2500000, 2500000 },
- { B3000000, 3000000 },
- { B3500000, 3500000 },
- { B4000000, 4000000 }
- };
- constexpr ControlCharacter control_characters[] = {
- { "intr", VINTR },
- { "quit", VQUIT },
- { "erase", VERASE },
- { "kill", VKILL },
- { "eof", VEOF },
- /* time and min are handled separately */
- { "swtc", VSWTC },
- { "start", VSTART },
- { "stop", VSTOP },
- { "susp", VSUSP },
- { "eol", VEOL },
- { "reprint", VREPRINT },
- { "discard", VDISCARD },
- { "werase", VWERASE },
- { "lnext", VLNEXT },
- { "eol2", VEOL2 }
- };
- Optional<speed_t> numeric_value_to_speed(unsigned long);
- Optional<unsigned long> speed_to_numeric_value(speed_t);
- void print_stty_readable(termios const&);
- void print_human_readable(termios const&, winsize const&, bool);
- Result<void, int> apply_stty_readable_modes(StringView, termios&);
- Result<void, int> apply_modes(size_t, char**, termios&, winsize&);
- Optional<speed_t> numeric_value_to_speed(unsigned long numeric_value)
- {
- for (auto rate : baud_rates) {
- if (rate.numeric_value == numeric_value)
- return rate.speed;
- }
- return {};
- }
- Optional<unsigned long> speed_to_numeric_value(speed_t speed)
- {
- for (auto rate : baud_rates) {
- if (rate.speed == speed)
- return rate.numeric_value;
- }
- return {};
- }
- void print_stty_readable(termios const& modes)
- {
- out("{:x}:{:x}:{:x}:{:x}", modes.c_iflag, modes.c_oflag, modes.c_cflag, modes.c_lflag);
- for (size_t i = 0; i < NCCS; ++i)
- out(":{:x}", modes.c_cc[i]);
- out(":{:x}:{:x}\n", modes.c_ispeed, modes.c_ospeed);
- }
- void print_human_readable(termios const& modes, winsize const& ws, bool verbose_mode)
- {
- auto print_speed = [&] {
- if (verbose_mode && modes.c_ispeed != modes.c_ospeed) {
- out("ispeed {} baud; ospeed {} baud;", speed_to_numeric_value(modes.c_ispeed).value(), speed_to_numeric_value(modes.c_ospeed).value());
- } else {
- out("speed {} baud;", speed_to_numeric_value(modes.c_ispeed).value());
- }
- };
- auto print_winsize = [&] {
- out("rows {}; columns {};", ws.ws_row, ws.ws_col);
- };
- auto escape_character = [&](u8 ch) {
- StringBuilder sb;
- if (ch <= 0x20) {
- sb.append("^");
- sb.append(ch + 0x40);
- } else if (ch == 0x7f) {
- sb.append("^?");
- } else {
- sb.append(ch);
- }
- return sb.to_string();
- };
- auto print_control_characters = [&] {
- bool first_in_line = true;
- for (auto cc : control_characters) {
- if (verbose_mode || modes.c_cc[cc.index] != ttydefchars[cc.index]) {
- out("{}{} = {};", (first_in_line) ? "" : " ", cc.name, escape_character(modes.c_cc[cc.index]));
- first_in_line = false;
- }
- }
- if (!first_in_line)
- out("\n");
- };
- auto print_flags_of_type = [&](const TermiosFlag flags[], size_t flag_count, tcflag_t field_value, tcflag_t field_default) {
- bool first_in_line = true;
- for (size_t i = 0; i < flag_count; ++i) {
- auto& flag = flags[i];
- if (verbose_mode || (field_value & flag.mask) != (field_default & flag.mask)) {
- bool set = (field_value & flag.mask) == flag.value;
- out("{}{}{}", first_in_line ? "" : " ", set ? "" : "-", flag.name);
- first_in_line = false;
- }
- }
- if (!first_in_line)
- out("\n");
- };
- auto print_flags = [&] {
- print_flags_of_type(all_cflags, sizeof(all_cflags) / sizeof(TermiosFlag), modes.c_cflag, TTYDEF_CFLAG);
- print_flags_of_type(all_oflags, sizeof(all_oflags) / sizeof(TermiosFlag), modes.c_oflag, TTYDEF_OFLAG);
- print_flags_of_type(all_iflags, sizeof(all_iflags) / sizeof(TermiosFlag), modes.c_iflag, TTYDEF_IFLAG);
- print_flags_of_type(all_lflags, sizeof(all_lflags) / sizeof(TermiosFlag), modes.c_lflag, TTYDEF_LFLAG);
- };
- print_speed();
- out(" ");
- print_winsize();
- out("\n");
- print_control_characters();
- print_flags();
- }
- Result<void, int> apply_stty_readable_modes(StringView mode_string, termios& t)
- {
- auto split = mode_string.split_view(':');
- if (split.size() != 4 + NCCS + 2) {
- warnln("Save string has an incorrect number of parameters");
- return 1;
- }
- auto parse_hex = [&](StringView v) {
- tcflag_t ret = 0;
- for (auto c : v) {
- c = tolower(c);
- ret *= 16;
- if (isdigit(c)) {
- ret += c - '0';
- } else {
- VERIFY(c >= 'a' && c <= 'f');
- ret += c - 'a';
- }
- }
- return ret;
- };
- t.c_iflag = parse_hex(split[0]);
- t.c_oflag = parse_hex(split[1]);
- t.c_cflag = parse_hex(split[2]);
- t.c_lflag = parse_hex(split[3]);
- for (size_t i = 0; i < NCCS; ++i) {
- t.c_cc[i] = (cc_t)parse_hex(split[4 + i]);
- }
- t.c_ispeed = parse_hex(split[4 + NCCS]);
- t.c_ospeed = parse_hex(split[4 + NCCS + 1]);
- return {};
- }
- Result<void, int> apply_modes(size_t parameter_count, char** raw_parameters, termios& t, winsize& w)
- {
- Vector<StringView> parameters;
- parameters.ensure_capacity(parameter_count);
- for (size_t i = 0; i < parameter_count; ++i)
- parameters.append(StringView { raw_parameters[i], strlen(raw_parameters[i]) });
- auto parse_baud = [&](size_t idx) -> Optional<speed_t> {
- auto maybe_numeric_value = parameters[idx].to_uint<uint32_t>();
- if (maybe_numeric_value.has_value())
- return numeric_value_to_speed(maybe_numeric_value.value());
- return {};
- };
- auto parse_number = [&](size_t idx) -> Optional<cc_t> {
- return parameters[idx].to_uint<cc_t>();
- };
- auto looks_like_stty_readable = [&](size_t idx) {
- bool contains_colon = false;
- for (auto c : parameters[idx]) {
- c = tolower(c);
- if (!isdigit(c) && !(c >= 'a' && c <= 'f') && c != ':')
- return false;
- if (c == ':')
- contains_colon = true;
- }
- return contains_colon;
- };
- auto parse_control_character = [&](size_t idx) -> Optional<cc_t> {
- VERIFY(!parameters[idx].is_empty());
- if (parameters[idx] == "^-" || parameters[idx] == "undef") {
- // FIXME: disabling characters is a bit wonky right now in TTY.
- // We should add the _POSIX_VDISABLE macro.
- return 0;
- } else if (parameters[idx][0] == '^' && parameters[idx].length() == 2) {
- return toupper(parameters[idx][1]) - 0x40;
- } else if (parameters[idx].starts_with("0x")) {
- cc_t value = 0;
- if (parameters[idx].length() == 2) {
- warnln("Invalid hexadecimal character code {}", parameters[idx]);
- return {};
- }
- for (size_t i = 2; i < parameters[idx].length(); ++i) {
- char ch = tolower(parameters[idx][i]);
- if (!isdigit(ch) && !(ch >= 'a' && ch <= 'f')) {
- warnln("Invalid hexadecimal character code {}", parameters[idx]);
- return {};
- }
- value = 16 * value + (isdigit(ch)) ? (ch - '0') : (ch - 'a');
- }
- return value;
- } else if (parameters[idx].starts_with("0")) {
- cc_t value = 0;
- for (size_t i = 1; i < parameters[idx].length(); ++i) {
- char ch = parameters[idx][i];
- if (!(ch >= '0' && ch <= '7')) {
- warnln("Invalid octal character code {}", parameters[idx]);
- return {};
- }
- value = 8 * value + (ch - '0');
- }
- return value;
- } else if (isdigit(parameters[idx][0])) {
- auto maybe_value = parameters[idx].to_uint<cc_t>();
- if (!maybe_value.has_value()) {
- warnln("Invalid decimal character code {}", parameters[idx]);
- return {};
- }
- return maybe_value.value();
- } else if (parameters[idx].length() == 1) {
- return parameters[idx][0];
- }
- warnln("Invalid control character {}", parameters[idx]);
- return {};
- };
- size_t parameter_idx = 0;
- auto parse_flag_or_char = [&]() -> Result<void, int> {
- if (parameters[parameter_idx][0] != '-') {
- if (parameters[parameter_idx] == "min") {
- auto maybe_number = parse_number(++parameter_idx);
- if (!maybe_number.has_value()) {
- warnln("Error parsing min: {} is not a number", parameters[parameter_idx]);
- return 1;
- }
- return {};
- } else if (parameters[parameter_idx] == "time") {
- auto maybe_number = parse_number(++parameter_idx);
- if (!maybe_number.has_value()) {
- warnln("Error parsing time: {} is not a number", parameters[parameter_idx]);
- return 1;
- }
- return {};
- } else {
- for (auto cc : control_characters) {
- if (cc.name == parameters[parameter_idx]) {
- if (parameter_idx == parameter_count - 1) {
- warnln("No control character specified for {}", cc.name);
- return 1;
- }
- auto maybe_control_character = parse_control_character(++parameter_idx);
- if (!maybe_control_character.has_value())
- return 1;
- t.c_cc[cc.index] = maybe_control_character.value();
- return {};
- }
- }
- }
- }
- // We fall through to here if what we are setting is not a control character.
- bool negate = false;
- if (parameters[parameter_idx][0] == '-') {
- negate = true;
- parameters[parameter_idx] = parameters[parameter_idx].substring_view(1);
- }
- auto perform_masking = [&](tcflag_t value, tcflag_t mask, tcflag_t& dest) {
- if (negate)
- dest &= ~mask;
- else
- dest = (dest & (~mask)) | value;
- };
- for (auto flag : all_iflags) {
- if (flag.name == parameters[parameter_idx]) {
- perform_masking(flag.value, flag.mask, t.c_iflag);
- return {};
- }
- }
- for (auto flag : all_oflags) {
- if (flag.name == parameters[parameter_idx]) {
- perform_masking(flag.value, flag.mask, t.c_oflag);
- return {};
- }
- }
- for (auto flag : all_cflags) {
- if (flag.name == parameters[parameter_idx]) {
- perform_masking(flag.value, flag.mask, t.c_cflag);
- return {};
- }
- }
- for (auto flag : all_lflags) {
- if (flag.name == parameters[parameter_idx]) {
- perform_masking(flag.value, flag.mask, t.c_lflag);
- return {};
- }
- }
- warnln("Invalid control flag or control character name {}", parameters[parameter_idx]);
- return 1;
- };
- while (parameter_idx < parameter_count) {
- if (looks_like_stty_readable(parameter_idx)) {
- auto maybe_error = apply_stty_readable_modes(parameters[parameter_idx], t);
- if (maybe_error.is_error())
- return maybe_error.error();
- } else if (isdigit(parameters[parameter_idx][0])) {
- auto new_baud = parse_baud(parameter_idx);
- if (!new_baud.has_value()) {
- warnln("Invalid baud rate {}", parameters[parameter_idx]);
- return 1;
- }
- t.c_ispeed = t.c_ospeed = new_baud.value();
- } else if (parameters[parameter_idx] == "ispeed") {
- if (parameter_idx == parameter_count - 1) {
- warnln("No baud rate specified for ispeed");
- return 1;
- }
- auto new_baud = parse_baud(++parameter_idx);
- if (!new_baud.has_value()) {
- warnln("Invalid input baud rate {}", parameters[parameter_idx]);
- return 1;
- }
- t.c_ispeed = new_baud.value();
- } else if (parameters[parameter_idx] == "ospeed") {
- if (parameter_idx == parameter_count - 1) {
- warnln("No baud rate specified for ospeed");
- return 1;
- }
- auto new_baud = parse_baud(++parameter_idx);
- if (!new_baud.has_value()) {
- warnln("Invalid output baud rate {}", parameters[parameter_idx]);
- return 1;
- }
- t.c_ospeed = new_baud.value();
- } else if (parameters[parameter_idx] == "columns" || parameters[parameter_idx] == "cols") {
- auto maybe_number = parse_number(++parameter_idx);
- if (!maybe_number.has_value()) {
- warnln("Invalid column count {}", parameters[parameter_idx]);
- return 1;
- }
- w.ws_col = maybe_number.value();
- } else if (parameters[parameter_idx] == "rows") {
- auto maybe_number = parse_number(++parameter_idx);
- if (!maybe_number.has_value()) {
- warnln("Invalid row count {}", parameters[parameter_idx]);
- return 1;
- }
- w.ws_row = maybe_number.value();
- } else if (parameters[parameter_idx] == "evenp" || parameters[parameter_idx] == "parity") {
- t.c_cflag &= ~(CSIZE | PARODD);
- t.c_cflag |= CS7 | PARENB;
- } else if (parameters[parameter_idx] == "oddp") {
- t.c_cflag &= ~(CSIZE);
- t.c_cflag |= CS7 | PARENB | PARODD;
- } else if (parameters[parameter_idx] == "-parity" || parameters[parameter_idx] == "-evenp" || parameters[parameter_idx] == "-oddp") {
- t.c_cflag &= ~(PARENB | CSIZE);
- t.c_cflag |= CS8;
- } else if (parameters[parameter_idx] == "raw") {
- cfmakeraw(&t);
- } else if (parameters[parameter_idx] == "nl") {
- t.c_iflag &= ~ICRNL;
- } else if (parameters[parameter_idx] == "-nl") {
- t.c_cflag &= ~(INLCR & IGNCR);
- t.c_iflag |= ICRNL;
- } else if (parameters[parameter_idx] == "ek") {
- t.c_cc[VERASE] = CERASE;
- t.c_cc[VKILL] = CKILL;
- } else if (parameters[parameter_idx] == "sane") {
- t.c_iflag = TTYDEF_IFLAG;
- t.c_oflag = TTYDEF_OFLAG;
- t.c_cflag = TTYDEF_CFLAG;
- t.c_lflag = TTYDEF_LFLAG;
- for (size_t i = 0; i < NCCS; ++i)
- t.c_cc[i] = ttydefchars[i];
- t.c_ispeed = t.c_ospeed = TTYDEF_SPEED;
- } else {
- auto maybe_error = parse_flag_or_char();
- if (maybe_error.is_error())
- return maybe_error.error();
- }
- ++parameter_idx;
- }
- return {};
- }
- ErrorOr<int> serenity_main(Main::Arguments arguments)
- {
- TRY(Core::System::pledge("stdio tty rpath"));
- TRY(Core::System::unveil("/dev", "r"));
- TRY(Core::System::unveil(nullptr, nullptr));
- String device_file;
- bool stty_readable = false;
- bool all_settings = false;
- // Core::ArgsParser can't handle the weird syntax of stty, so we use getopt_long instead.
- int argc = arguments.argc;
- char** argv = arguments.argv;
- opterr = 0; // We handle unknown flags gracefully by starting to parse the arguments in `apply_modes`.
- int optc;
- bool should_quit = false;
- while (!should_quit && ((optc = getopt_long(argc, argv, "-agF:", long_options, nullptr)) != -1)) {
- switch (optc) {
- case 'a':
- all_settings = true;
- break;
- case 'g':
- stty_readable = true;
- break;
- case 'F':
- if (!device_file.is_empty()) {
- warnln("Only one device may be specified");
- exit(1);
- }
- device_file = optarg;
- break;
- default:
- should_quit = true;
- break;
- }
- }
- if (stty_readable && all_settings) {
- warnln("Save mode and all-settings mode are mutually exclusive");
- exit(1);
- }
- int terminal_fd = STDIN_FILENO;
- if (!device_file.is_empty()) {
- if ((terminal_fd = open(device_file.characters(), O_RDONLY, 0)) < 0) {
- perror("open");
- exit(1);
- }
- }
- ScopeGuard file_close_guard = [&] { close(terminal_fd); };
- termios initial_termios = TRY(Core::System::tcgetattr(terminal_fd));
- winsize initial_winsize;
- TRY(Core::System::ioctl(terminal_fd, TIOCGWINSZ, &initial_winsize));
- if (optind < argc) {
- if (stty_readable || all_settings) {
- warnln("Modes cannot be set when printing settings");
- exit(1);
- }
- auto result = apply_modes(argc - optind, argv + optind, initial_termios, initial_winsize);
- if (result.is_error())
- return result.error();
- TRY(Core::System::tcsetattr(terminal_fd, TCSADRAIN, initial_termios));
- TRY(Core::System::ioctl(terminal_fd, TIOCSWINSZ, &initial_winsize));
- } else if (stty_readable) {
- print_stty_readable(initial_termios);
- } else {
- print_human_readable(initial_termios, initial_winsize, all_settings);
- }
- return 0;
- }
|