From cc915eefafa89fe49da3afaf0f3fdaef24679b62 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 17:54:09 +0200 Subject: [PATCH 01/46] env-util: suppress unnecessary setenv() in setenvf() --- src/basic/env-util.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/basic/env-util.c b/src/basic/env-util.c index 3e385e5ee4b..cf2c47d2142 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -1146,5 +1146,10 @@ int setenvf(const char *name, bool overwrite, const char *valuef, ...) { if (r < 0) return -ENOMEM; + /* Try to suppress writes if the value is already set correctly (simply because memory management of + * environment variables sucks a bit. */ + if (streq_ptr(getenv(name), value)) + return 0; + return RET_NERRNO(setenv(name, value, overwrite)); } From 845be16ffd0b832fe8fdd2a39897df318b898c45 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 17:17:20 +0200 Subject: [PATCH 02/46] terminal-util: trivial white space fix --- src/basic/terminal-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index e50b54006a3..e3e0295c8e2 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -657,7 +657,7 @@ int vtnr_from_tty(const char *tty) { return i; } - int resolve_dev_console(char **ret) { +int resolve_dev_console(char **ret) { _cleanup_free_ char *active = NULL; char *tty; int r; From 9a5e421af5b2cf3f997bd94a8dc8563ba2a3b37c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 17:01:47 +0200 Subject: [PATCH 03/46] terminal-util: refuse a few more unexpected open flags in open_terminal() --- src/basic/terminal-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index e3e0295c8e2..345fc15b94a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -344,7 +344,7 @@ int open_terminal(const char *name, int mode) { * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 */ - if (mode & O_CREAT) + if ((mode & (O_CREAT|O_PATH|O_DIRECTORY|O_TMPFILE)) != 0) return -EINVAL; for (;;) { From 4c8c499e2c9f906f632b4955dd8db45a5ea96fd7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 16:32:35 +0200 Subject: [PATCH 04/46] terminal-util: return correct error in chvt() --- src/basic/terminal-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 345fc15b94a..9d3e0ae07be 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -76,7 +76,7 @@ int chvt(int vt) { fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) - return -errno; + return fd; if (vt <= 0) { int tiocl[2] = { From 1df569b2e650afe254e09b337d598591f3068b61 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 16:06:30 +0200 Subject: [PATCH 05/46] terminal-util: don't process the same data twice when reading back bg color info If we only read partial information from the tty we ended up parsing it again and again, confusing the state machine. hence, return how much data we actually processed and drop it from the buffer. --- src/basic/terminal-util.c | 67 +++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 9d3e0ae07be..ad225995532 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1601,7 +1601,8 @@ typedef struct BackgroundColorContext { static int scan_background_color_response( BackgroundColorContext *context, const char *buf, - size_t size) { + size_t size, + size_t *ret_processed) { assert(context); assert(buf || size == 0); @@ -1677,8 +1678,12 @@ static int scan_background_color_response( case BACKGROUND_BLUE: if (c == '\x07') { - if (context->blue_bits > 0) + if (context->blue_bits > 0) { + if (ret_processed) + *ret_processed = i + 1; + return 1; /* success! */ + } context->state = BACKGROUND_TEXT; } else if (c == '\x1b') @@ -1695,8 +1700,12 @@ static int scan_background_color_response( break; case BACKGROUND_STRING_TERMINATOR: - if (c == '\\') + if (c == '\\') { + if (ret_processed) + *ret_processed = i + 1; + return 1; /* success! */ + } context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT; break; @@ -1711,6 +1720,9 @@ static int scan_background_color_response( } } + if (ret_processed) + *ret_processed = size; + return 0; /* all good, but not enough data yet */ } @@ -1753,34 +1765,41 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret BackgroundColorContext context = {}; for (;;) { - usec_t n = now(CLOCK_MONOTONIC); + if (buf_full == 0) { + usec_t n = now(CLOCK_MONOTONIC); - if (n >= end) { - r = -EOPNOTSUPP; - goto finish; + if (n >= end) { + r = -EOPNOTSUPP; + goto finish; + } + + r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n)); + if (r < 0) + goto finish; + if (r == 0) { + r = -EOPNOTSUPP; + goto finish; + } + + ssize_t l = read(STDIN_FILENO, buf, sizeof(buf)); + if (l < 0) { + r = -errno; + goto finish; + } + + assert((size_t) l <= sizeof(buf)); + buf_full = l; } - r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n)); + size_t processed; + r = scan_background_color_response(&context, buf, buf_full, &processed); if (r < 0) goto finish; - if (r == 0) { - r = -EOPNOTSUPP; - goto finish; - } - ssize_t l; - l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full); - if (l < 0) { - r = -errno; - goto finish; - } + assert(processed <= buf_full); + buf_full -= processed; + memmove(buf, buf + processed, buf_full); - buf_full += l; - assert(buf_full <= sizeof(buf)); - - r = scan_background_color_response(&context, buf, buf_full); - if (r < 0) - goto finish; if (r > 0) { assert(context.red_bits > 0); *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1); From 1c685216f4c48e1de6e58f6a86b76b488981e387 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 16:32:22 +0200 Subject: [PATCH 06/46] terminal-util: turn off echo on stdin, not stdout This doesn't make much of a different IRL, but it feels more right that an operation that happens in input is turned off via the input fd. --- src/basic/terminal-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ad225995532..5ebb7e0a74b 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1752,7 +1752,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret struct termios new_termios = old_termios; termios_disable_echo(&new_termios); - if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &new_termios) < 0) + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &new_termios) < 0) return -errno; r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX); @@ -1813,6 +1813,6 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret } finish: - (void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios); + (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios); return r; } From 0e08325baafb87cfec2f89293c4755e6167e66ea Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Jul 2024 19:04:39 +0200 Subject: [PATCH 07/46] terminal-util: remember error code from tcsetattr() --- src/basic/terminal-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 5ebb7e0a74b..2ea502c2bcd 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1813,6 +1813,6 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret } finish: - (void) tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios); + RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios))); return r; } From 445e57387e36ee73ebf43e79fe8dcf291d242945 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 17:18:55 +0200 Subject: [PATCH 08/46] terminal-util: when querying bg color, ensure input fd and output fd refer to same tty Let's add an extra safety check: before issuing the ansi sequence to query the bg color, let's make sure input and output fd actually reference the same tty. because otherwise it's unlikely we'll be able to read back the response from the tty driver. This is mostly just paranoia. --- src/basic/terminal-util.c | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 2ea502c2bcd..fb6c9177d49 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1576,6 +1576,37 @@ void termios_disable_echo(struct termios *termios) { termios->c_cc[VTIME] = 0; } +static int terminal_verify_same(int input_fd, int output_fd) { + assert(input_fd >= 0); + assert(output_fd >= 0); + + /* Validates that the specified fds reference the same TTY */ + + if (input_fd != output_fd) { + struct stat sti; + if (fstat(input_fd, &sti) < 0) + return -errno; + + if (!S_ISCHR(sti.st_mode)) /* TTYs are character devices */ + return -ENOTTY; + + struct stat sto; + if (fstat(output_fd, &sto) < 0) + return -errno; + + if (!S_ISCHR(sto.st_mode)) + return -ENOTTY; + + if (sti.st_rdev != sto.st_rdev) + return -ENOLINK; + } + + if (!isatty_safe(input_fd)) /* The check above was just for char device, but now let's ensure it's actually a tty */ + return -ENOTTY; + + return 0; +} + typedef enum BackgroundColorState { BACKGROUND_TEXT, BACKGROUND_ESCAPE, @@ -1736,8 +1767,9 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (!colors_enabled()) return -EOPNOTSUPP; - if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) - return -EOPNOTSUPP; + r = terminal_verify_same(STDIN_FILENO, STDOUT_FILENO); + if (r < 0) + return r; if (streq_ptr(getenv("TERM"), "linux")) { /* Linux console is black */ From 53f0ab5151fd855c6b926a772c7329b0c00a7504 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:31:02 +0200 Subject: [PATCH 09/46] =?UTF-8?q?terminal-util:=20rename=20set=5Fterminal?= =?UTF-8?q?=5Fcursor=5Fposition()=20=E2=86=92=20terminal=5Fset=5Fcursor=5F?= =?UTF-8?q?position()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's prefix these functions with the subsystem name, and clean them up a bit. Specifically, drop the error logging, it's entirely duplicative, since every single caller does it anyway. --- src/basic/terminal-util.c | 12 +++--------- src/basic/terminal-util.h | 3 ++- src/journal/bsod.c | 6 +++--- src/shared/qrcode-util.c | 10 +++++----- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index fb6c9177d49..8c3af0bcb3e 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1522,19 +1522,13 @@ void get_log_colors(int priority, const char **on, const char **off, const char } } -int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column) { - int r; - char cursor_position[STRLEN("\x1B[") + DECIMAL_STR_MAX(int) * 2 + STRLEN(";H") + 1]; - +int terminal_set_cursor_position(int fd, unsigned row, unsigned column) { assert(fd >= 0); + char cursor_position[STRLEN("\x1B[" ";" "H") + DECIMAL_STR_MAX(unsigned) * 2 + 1]; xsprintf(cursor_position, "\x1B[%u;%uH", row, column); - r = loop_write(fd, cursor_position, SIZE_MAX); - if (r < 0) - return log_warning_errno(r, "Failed to set cursor position, ignoring: %m"); - - return 0; + return loop_write(fd, cursor_position, SIZE_MAX); } int terminal_reset_ansi_seq(int fd) { diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 691b37aa51e..51569a73f53 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -97,9 +97,10 @@ bool isatty_safe(int fd); int reset_terminal_fd(int fd, bool switch_to_text); int reset_terminal(const char *name); -int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column); int terminal_reset_ansi_seq(int fd); +int terminal_set_cursor_position(int fd, unsigned row, unsigned column); + int open_terminal(const char *name, int mode); /* Flags for tweaking the way we become the controlling process of a terminal. */ diff --git a/src/journal/bsod.c b/src/journal/bsod.c index b2889f02eda..2116c498a4b 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -185,7 +185,7 @@ static int display_emergency_message_fullscreen(const char *message) { if (r < 0) log_warning_errno(r, "Failed to clear terminal, ignoring: %m"); - r = set_terminal_cursor_position(fd, 2, 4); + r = terminal_set_cursor_position(fd, 2, 4); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); @@ -197,7 +197,7 @@ static int display_emergency_message_fullscreen(const char *message) { qr_code_start_row = w.ws_row * 3U / 5U; qr_code_start_column = w.ws_col * 3U / 4U; - r = set_terminal_cursor_position(fd, 4, 4); + r = terminal_set_cursor_position(fd, 4, 4); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); @@ -217,7 +217,7 @@ static int display_emergency_message_fullscreen(const char *message) { if (r < 0) log_warning_errno(r, "QR code could not be printed, ignoring: %m"); - r = set_terminal_cursor_position(fd, w.ws_row - 1, w.ws_col * 2U / 5U); + r = terminal_set_cursor_position(fd, w.ws_row - 1, w.ws_col * 2U / 5U); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index a63e6cac732..10ed542df22 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -52,7 +52,7 @@ static void print_border(FILE *output, unsigned width, unsigned row, unsigned co if (fd < 0) return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); - r = set_terminal_cursor_position(fd, row, column); + r = terminal_set_cursor_position(fd, row, column); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); @@ -64,7 +64,7 @@ static void print_border(FILE *output, unsigned width, unsigned row, unsigned co fputs(UNICODE_FULL_BLOCK, output); fputs(ANSI_NORMAL "\n", output); - r = set_terminal_cursor_position(fd, row + 1, column); + r = terminal_set_cursor_position(fd, row + 1, column); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); } @@ -96,7 +96,7 @@ static void write_qrcode(FILE *output, QRcode *qr, unsigned int row, unsigned in if (fd < 0) return (void)log_debug_errno(errno, "Failed to get file descriptor from the file stream: %m"); - r = set_terminal_cursor_position(fd, row + move_down, column); + r = terminal_set_cursor_position(fd, row + move_down, column); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); @@ -127,7 +127,7 @@ static void write_qrcode(FILE *output, QRcode *qr, unsigned int row, unsigned in for (unsigned x = 0; x < 4; x++) fputs(UNICODE_FULL_BLOCK, output); - r = set_terminal_cursor_position(fd, row + move_down, column); + r = terminal_set_cursor_position(fd, row + move_down, column); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); move_down += 1; @@ -206,7 +206,7 @@ int print_qrcode_full(FILE *out, const char *header, const char *string, unsigne row = tty_height - (qr_code_height / 2 ) - 1; if (header) { - r = set_terminal_cursor_position(fd, row - 2, tty_width - qr_code_width - 2); + r = terminal_set_cursor_position(fd, row - 2, tty_width - qr_code_width - 2); if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); From 3390be38d19c9d339bbc0e003743ce4278aa58b6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 16:02:52 +0200 Subject: [PATCH 10/46] terminal-util: add helper that queries terminal sizes via ANSI sequence When we are talking to a serial terminal quite commonly the dimensions are not set properly, because the serial protocol has not handshake or similar to transfer this information. However, we can derive the dimensions via ANSI sequences too, which should get us the right information, since ANSI sequences are interpreted by the final terminal, rather than an intermediary local tty driver (which is where TIOCGWINSZ is interpreted). This adds a helper call that gets the dimensions this way. --- src/basic/terminal-util.c | 230 ++++++++++++++++++++++++++++++++++ src/basic/terminal-util.h | 1 + src/test/test-terminal-util.c | 21 ++++ 3 files changed, 252 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 8c3af0bcb3e..1cfde3e3ad6 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1842,3 +1842,233 @@ finish: RET_GATHER(r, RET_NERRNO(tcsetattr(STDIN_FILENO, TCSADRAIN, &old_termios))); return r; } + +typedef enum CursorPositionState { + CURSOR_TEXT, + CURSOR_ESCAPE, + CURSOR_ROW, + CURSOR_COLUMN, +} CursorPositionState; + +typedef struct CursorPositionContext { + CursorPositionState state; + unsigned row, column; +} CursorPositionContext; + +static int scan_cursor_position_response( + CursorPositionContext *context, + const char *buf, + size_t size, + size_t *ret_processed) { + + assert(context); + assert(buf || size == 0); + + for (size_t i = 0; i < size; i++) { + char c = buf[i]; + + switch (context->state) { + + case CURSOR_TEXT: + context->state = c == '\x1B' ? CURSOR_ESCAPE : CURSOR_TEXT; + break; + + case CURSOR_ESCAPE: + context->state = c == '[' ? CURSOR_ROW : CURSOR_TEXT; + break; + + case CURSOR_ROW: + if (c == ';') + context->state = context->row > 0 ? CURSOR_COLUMN : CURSOR_TEXT; + else { + int d = undecchar(c); + + /* We read a decimal character, let's suffix it to the number we so far read, + * but let's do an overflow check first. */ + if (d < 0 || context->row > (UINT_MAX-d)/10) + context->state = CURSOR_TEXT; + else + context->row = context->row * 10 + d; + } + break; + + case CURSOR_COLUMN: + if (c == 'R') { + if (context->column > 0) { + if (ret_processed) + *ret_processed = i + 1; + + return 1; /* success! */ + } + + context->state = CURSOR_TEXT; + } else { + int d = undecchar(c); + + /* As above, add the decimal charatcer to our column number */ + if (d < 0 || context->column > (UINT_MAX-d)/10) + context->state = CURSOR_TEXT; + else + context->column = context->column * 10 + d; + } + + break; + } + + /* Reset any positions we might have picked up */ + if (IN_SET(context->state, CURSOR_TEXT, CURSOR_ESCAPE)) + context->row = context->column = 0; + } + + if (ret_processed) + *ret_processed = size; + + return 0; /* all good, but not enough data yet */ +} + +int terminal_get_size_by_dsr( + int input_fd, + int output_fd, + unsigned *ret_rows, + unsigned *ret_columns) { + + assert(input_fd >= 0); + assert(output_fd >= 0); + + int r; + + /* Tries to determine the terminal dimension by means of ANSI sequences rather than TIOCGWINSZ + * ioctl(). Why bother with this? The ioctl() information is often incorrect on serial terminals + * (since there's no handshake or protocol to determine the right dimensions in RS232), but since the + * ANSI sequences are interpreted by the final terminal instead of an intermediary tty driver they + * should be more accurate. + * + * Unfortunately there's no direct ANSI sequence to query terminal dimensions. But we can hack around + * it: we position the cursor briefly at an absolute location very far down and very far to the + * right, and then read back where we actually ended up. Because cursor locations are capped at the + * terminal width/height we should then see the right values. In order to not risk integer overflows + * in terminal applications we'll use INT16_MAX-1 as location to jump to — hopefully a value that is + * large enough for any real-life terminals, but small enough to not overflow anything or be + * recognized as a "niche" value. (Note that the dimension fields in "struct winsize" are 16bit only, + * too). */ + + if (terminal_is_dumb()) + return -EOPNOTSUPP; + + r = terminal_verify_same(input_fd, output_fd); + if (r < 0) + return log_debug_errno(r, "Called with distinct input/output fds: %m"); + + struct termios old_termios; + if (tcgetattr(input_fd, &old_termios) < 0) + return log_debug_errno(errno, "Failed to to get terminal settings: %m"); + + struct termios new_termios = old_termios; + termios_disable_echo(&new_termios); + + if (tcsetattr(input_fd, TCSADRAIN, &new_termios) < 0) + return log_debug_errno(errno, "Failed to to set new terminal settings: %m"); + + unsigned saved_row = 0, saved_column = 0; + + r = loop_write(output_fd, + "\x1B[6n" /* Request cursor position (DSR/CPR) */ + "\x1B[32766;32766H" /* Position cursor really far to the right and to the bottom, but let's stay within the 16bit signed range */ + "\x1B[6n", /* Request cursor position again */ + SIZE_MAX); + if (r < 0) + goto finish; + + usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC); + char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */ + size_t buf_full = 0; + CursorPositionContext context = {}; + + for (bool first = true;; first = false) { + if (buf_full == 0) { + usec_t n = now(CLOCK_MONOTONIC); + + if (n >= end) { + r = -EOPNOTSUPP; + goto finish; + } + + r = fd_wait_for_event(input_fd, POLLIN, usec_sub_unsigned(end, n)); + if (r < 0) + goto finish; + if (r == 0) { + r = -EOPNOTSUPP; + goto finish; + } + + /* On the first try, read multiple characters, i.e. the shortest valid + * reply. Afterwards read byte-wise, since we don't want to read too much, and + * unnecessarily drop too many characters from the input queue. */ + ssize_t l = read(input_fd, buf, first ? sizeof(buf) : 1); + if (l < 0) { + r = -errno; + goto finish; + } + + assert((size_t) l <= sizeof(buf)); + buf_full = l; + } + + size_t processed; + r = scan_cursor_position_response(&context, buf, buf_full, &processed); + if (r < 0) + goto finish; + + assert(processed <= buf_full); + buf_full -= processed; + memmove(buf, buf + processed, buf_full); + + if (r > 0) { + if (saved_row == 0) { + assert(saved_column == 0); + + /* First sequence, this is the cursor position before we set it somewhere + * into the void at the bottom right. Let's save where we are so that we can + * return later. */ + + /* Superficial validity checks */ + if (context.row <= 0 || context.column <= 0 || context.row >= 32766 || context.column >= 32766) { + r = -ENODATA; + goto finish; + } + + saved_row = context.row; + saved_column = context.column; + + /* Reset state */ + context = (CursorPositionContext) {}; + } else { + /* Second sequence, this is the cursor position after we set it somewhere + * into the void at the bottom right. */ + + /* Superficial validity checks (no particular reason to check for < 4, it's + * just a way to look for unreasonably small values) */ + if (context.row < 4 || context.column < 4 || context.row >= 32766 || context.column >= 32766) { + r = -ENODATA; + goto finish; + } + + if (ret_rows) + *ret_rows = context.row; + if (ret_columns) + *ret_columns = context.column; + + r = 0; + goto finish; + } + } + } + +finish: + /* Restore cursor position */ + if (saved_row > 0 && saved_column > 0) + RET_GATHER(r, terminal_set_cursor_position(output_fd, saved_row, saved_column)); + + RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSADRAIN, &old_termios))); + return r; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 51569a73f53..e2c21f7fb72 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -295,3 +295,4 @@ static inline const char* ansi_highlight_green_red(bool b) { void termios_disable_echo(struct termios *termios); int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue); +int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 9f8ca15a658..3d15d45558e 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include "alloc-util.h" @@ -176,6 +178,25 @@ TEST(get_default_background_color) { log_notice("R=%g G=%g B=%g", red, green, blue); } +TEST(terminal_get_size_by_dsr) { + unsigned rows, columns; + int r; + + r = terminal_get_size_by_dsr(STDIN_FILENO, STDOUT_FILENO, &rows, &columns); + if (r < 0) + log_notice_errno(r, "Can't get screen dimensions via DSR: %m"); + else { + log_notice("terminal size via DSR: rows=%u columns=%u", rows, columns); + + struct winsize ws = {}; + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0) + log_warning_errno(errno, "Can't get terminal size via ioctl, ignoring: %m"); + else + log_notice("terminal size via ioctl: rows=%u columns=%u", ws.ws_row, ws.ws_col); + } +} + static void test_get_color_mode_with_env(const char *key, const char *val, ColorMode expected) { ASSERT_OK(setenv(key, val, true)); reset_terminal_feature_caches(); From 63c631d7e26324d96c4f7f5161c0e48b8f7b4a9d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 16:33:10 +0200 Subject: [PATCH 11/46] terminal-util: add helper that adjust terminal width/height from data acquired via ANSI sequences --- src/basic/terminal-util.c | 33 +++++++++++++++++++++++++++++++++ src/basic/terminal-util.h | 2 ++ src/test/test-terminal-util.c | 12 ++++++++++++ 3 files changed, 47 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 1cfde3e3ad6..e707adf6944 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -2072,3 +2072,36 @@ finish: RET_GATHER(r, RET_NERRNO(tcsetattr(input_fd, TCSADRAIN, &old_termios))); return r; } + +int terminal_fix_size(int input_fd, int output_fd) { + unsigned rows, columns; + int r; + + /* Tries to update the current terminal dimensions to the ones reported via ANSI sequences */ + + r = terminal_verify_same(input_fd, output_fd); + if (r < 0) + return r; + + struct winsize ws = {}; + if (ioctl(output_fd, TIOCGWINSZ, &ws) < 0) + return log_debug_errno(errno, "Failed to query terminal dimensions, ignoring: %m"); + + r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &columns); + if (r < 0) + return log_debug_errno(r, "Failed to acquire terminal dimensions via ANSI sequences, not adjusting terminal dimensions: %m"); + + if (ws.ws_row == rows && ws.ws_col == columns) { + log_debug("Terminal dimensions reported via ANSI sequences match currently set terminal dimensions, not changing."); + return 0; + } + + ws.ws_col = columns; + ws.ws_row = rows; + + if (ioctl(output_fd, TIOCSWINSZ, &ws) < 0) + return log_debug_errno(errno, "Failed to update terminal dimensions, ignoring: %m"); + + log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows); + return 1; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index e2c21f7fb72..1c47366eab4 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -296,3 +296,5 @@ void termios_disable_echo(struct termios *termios); int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue); int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns); + +int terminal_fix_size(int input_fd, int output_fd); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 3d15d45558e..9a89f55c80e 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -197,6 +197,18 @@ TEST(terminal_get_size_by_dsr) { } } +TEST(terminal_fix_size) { + int r; + + r = terminal_fix_size(STDIN_FILENO, STDOUT_FILENO); + if (r < 0) + log_warning_errno(r, "Failed to fix terminal size: %m"); + else if (r == 0) + log_notice("Not fixing terminal size, nothing to do."); + else + log_notice("Fixed terminal size."); +} + static void test_get_color_mode_with_env(const char *key, const char *val, ColorMode expected) { ASSERT_OK(setenv(key, val, true)); reset_terminal_feature_caches(); From f6927e3c98002c5a5195364f64eea75d9cb8b395 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 17:17:58 +0200 Subject: [PATCH 12/46] terminal-util: try to initialize rows/cols via ansi sequence in make_console_stdio() Let's hook this up. --- src/basic/terminal-util.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index e707adf6944..d6fd41e3ce0 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -607,11 +607,12 @@ int make_console_stdio(void) { r = proc_cmdline_tty_size("/dev/console", &rows, &cols); if (r < 0) log_warning_errno(r, "Failed to get terminal size, ignoring: %m"); - else { + else if (r > 0) { r = terminal_set_size_fd(fd, NULL, rows, cols); if (r < 0) - log_warning_errno(r, "Failed to set terminal size, ignoring: %m"); - } + log_warning_errno(r, "Failed to set configured terminal size, ignoring: %m"); + } else + (void) terminal_fix_size(fd, fd); r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */ if (r < 0) @@ -939,7 +940,7 @@ int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_col if (ret_cols) *ret_cols = cols; - return 0; + return rows != UINT_MAX || cols != UINT_MAX; } /* intended to be used as a SIGWINCH sighandler */ From 963e25c23b1e5dd1b9fbf7b12a1d3ba7ac3deed2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 17:53:58 +0200 Subject: [PATCH 13/46] terminal-util: reset /dev/console via ansi seq also in make_console_stdio() This appears to have been the intention of 00bc83a275fa3ca8d90579fe9597d8b651d47332, judging by the comments on that. --- src/basic/terminal-util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d6fd41e3ce0..92abd52a2ef 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -614,6 +614,10 @@ int make_console_stdio(void) { } else (void) terminal_fix_size(fd, fd); + r = terminal_reset_ansi_seq(fd); + if (r < 0) + log_warning_errno(r, "Failed to reset terminal using ANSI sequences, ignoring: %m"); + r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */ if (r < 0) return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m"); From 2736295ddb78a457796f24805e7b98c3f5304848 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 18:02:31 +0200 Subject: [PATCH 14/46] terminal-util: unify code that resets /dev/console in common helper We have pretty much the same code at two places, let's make it one. --- src/basic/terminal-util.c | 45 ++++++++++++++++++++++----------------- src/basic/terminal-util.h | 1 + src/core/main.c | 33 ++++++++-------------------- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 92abd52a2ef..54901ccada6 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -582,6 +582,31 @@ int vt_disallocate(const char *name) { return 0; } +void reset_dev_console_fd(int fd, bool switch_to_text) { + int r; + + assert(fd >= 0); + + r = reset_terminal_fd(fd, switch_to_text); + if (r < 0) + log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m"); + + unsigned rows, cols; + r = proc_cmdline_tty_size("/dev/console", &rows, &cols); + if (r < 0) + log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m"); + else if (r > 0) { + r = terminal_set_size_fd(fd, NULL, rows, cols); + if (r < 0) + log_warning_errno(r, "Failed to set configured terminal size on /dev/console, ignoring: %m"); + } else + (void) terminal_fix_size(fd, fd); + + r = terminal_reset_ansi_seq(fd); + if (r < 0) + log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m"); +} + int make_console_stdio(void) { int fd, r; @@ -598,25 +623,7 @@ int make_console_stdio(void) { return log_error_errno(r, "Failed to make /dev/null stdin/stdout/stderr: %m"); } else { - unsigned rows, cols; - - r = reset_terminal_fd(fd, /* switch_to_text= */ true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); - - r = proc_cmdline_tty_size("/dev/console", &rows, &cols); - if (r < 0) - log_warning_errno(r, "Failed to get terminal size, ignoring: %m"); - else if (r > 0) { - r = terminal_set_size_fd(fd, NULL, rows, cols); - if (r < 0) - log_warning_errno(r, "Failed to set configured terminal size, ignoring: %m"); - } else - (void) terminal_fix_size(fd, fd); - - r = terminal_reset_ansi_seq(fd); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal using ANSI sequences, ignoring: %m"); + reset_dev_console_fd(fd, /* switch_to_text= */ true); r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */ if (r < 0) diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 1c47366eab4..a9d51564510 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -156,6 +156,7 @@ bool tty_is_console(const char *tty) _pure_; int vtnr_from_tty(const char *tty); const char* default_term_for_tty(const char *tty); +void reset_dev_console_fd(int fd, bool switch_to_text); int make_console_stdio(void); int fd_columns(int fd); diff --git a/src/core/main.c b/src/core/main.c index 1b78cd652fc..bc2476ed3a5 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -205,33 +205,18 @@ static int manager_find_user_config_paths(char ***ret_files, char ***ret_dirs) { } static int console_setup(void) { - _cleanup_close_ int tty_fd = -EBADF; - unsigned rows, cols; - int r; - tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (getpid_cached() != 1) + return 0; + + _cleanup_close_ int tty_fd = -EBADF; + + tty_fd = open_terminal("/dev/console", O_RDWR|O_NOCTTY|O_CLOEXEC); if (tty_fd < 0) return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); - /* We don't want to force text mode. plymouth may be showing - * pictures already from initrd. */ - r = reset_terminal_fd(tty_fd, false); - if (r < 0) - return log_error_errno(r, "Failed to reset /dev/console: %m"); - - r = proc_cmdline_tty_size("/dev/console", &rows, &cols); - if (r < 0) - log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m"); - else { - r = terminal_set_size_fd(tty_fd, NULL, rows, cols); - if (r < 0) - log_warning_errno(r, "Failed to set /dev/console size, ignoring: %m"); - } - - r = terminal_reset_ansi_seq(tty_fd); - if (r < 0) - log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m"); - + /* We don't want to force text mode. Plymouth may be showing pictures already from initrd. */ + reset_dev_console_fd(tty_fd, /* switch_to_text= */ false); return 0; } @@ -2910,7 +2895,7 @@ static void setup_console_terminal(bool skip_setup) { (void) release_terminal(); /* Reset the console, but only if this is really init and we are freshly booted */ - if (getpid_cached() == 1 && !skip_setup) + if (!skip_setup) (void) console_setup(); } From ce3a1593bc1b8273ea0f94895dbae9ba12420aac Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 10 Jul 2024 18:52:42 +0200 Subject: [PATCH 15/46] terminal-util: add terminal_is_pty_fd() helper The helper checks if an fd references a pty --- src/basic/terminal-util.c | 29 +++++++++++++++++++++++++ src/basic/terminal-util.h | 2 ++ src/test/test-terminal-util.c | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 54901ccada6..4c4a715ff66 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -32,6 +32,7 @@ #include "io-util.h" #include "log.h" #include "macro.h" +#include "missing_magic.h" #include "namespace-util.h" #include "parse-util.h" #include "path-util.h" @@ -2117,3 +2118,31 @@ int terminal_fix_size(int input_fd, int output_fd) { log_debug("Fixed terminal dimensions to %ux%u based on ANSI sequence information.", columns, rows); return 1; } + +int terminal_is_pty_fd(int fd) { + int r; + + assert(fd >= 0); + + /* Returns true if we are looking at a pty, i.e. if it's backed by the /dev/pts/ file system */ + + if (!isatty_safe(fd)) + return false; + + r = is_fs_type_at(fd, NULL, DEVPTS_SUPER_MAGIC); + if (r != 0) + return r; + + /* The ptmx device is weird, it exists twice, once inside and once outside devpts. To detect the + * latter case, let's fire off an ioctl() that only works on ptmx devices. */ + + int v; + if (ioctl(fd, TIOCGPKT, &v) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + return false; + + return -errno; + } + + return true; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index a9d51564510..df28af02d50 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -299,3 +299,5 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret int terminal_get_size_by_dsr(int input_fd, int output_fd, unsigned *ret_rows, unsigned *ret_columns); int terminal_fix_size(int input_fd, int output_fd); + +int terminal_is_pty_fd(int fd); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 9a89f55c80e..2f09ae9f71c 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -209,6 +209,46 @@ TEST(terminal_fix_size) { log_notice("Fixed terminal size."); } +TEST(terminal_is_pty_fd) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF; + _cleanup_free_ char *peer = NULL; + int r; + + fd1 = openpt_allocate(O_RDWR, &peer); + assert_se(fd1 >= 0); + assert_se(terminal_is_pty_fd(fd1) > 0); + + fd2 = open_terminal(peer, O_RDWR|O_CLOEXEC|O_NOCTTY); + assert_se(fd2 >= 0); + assert_se(terminal_is_pty_fd(fd2) > 0); + + fd1 = safe_close(fd1); + fd2 = safe_close(fd2); + + fd1 = open("/dev/null", O_RDONLY|O_CLOEXEC); + assert_se(fd1 >= 0); + assert_se(terminal_is_pty_fd(fd1) == 0); + + /* In container managers real tty devices might be weird, avoid them. */ + r = path_is_read_only_fs("/sys"); + if (r != 0) + return; + + FOREACH_STRING(p, "/dev/ttyS0", "/dev/tty1") { + _cleanup_close_ int tfd = -EBADF; + + tfd = open_terminal(p, O_CLOEXEC|O_NOCTTY|O_RDONLY|O_NONBLOCK); + if (tfd == -ENOENT) + continue; + if (tfd < 0) { + log_notice_errno(tfd, "Failed to open '%s', skipping: %m", p); + continue; + } + + assert_se(terminal_is_pty_fd(tfd) <= 0); + } +} + static void test_get_color_mode_with_env(const char *key, const char *val, ColorMode expected) { ASSERT_OK(setenv(key, val, true)); reset_terminal_feature_caches(); From cfac09083b287c713a2bc3724eb71468a350321d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 09:26:07 +0200 Subject: [PATCH 16/46] terminal-util: add new helper terminal_reset_defensive() that combines reset-by-ioctl and reset-by-sequence reasonably --- src/basic/terminal-util.c | 21 +++++++++++++++++++ src/basic/terminal-util.h | 1 + src/core/exec-invoke.c | 4 ++-- src/core/execute.c | 2 +- src/firstboot/firstboot.c | 2 +- src/home/homectl.c | 2 +- src/test/test-terminal-util.c | 8 +++++++ .../tty-ask-password-agent.c | 4 +--- 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 4c4a715ff66..9754b9900b6 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1575,6 +1575,27 @@ int terminal_reset_ansi_seq(int fd) { return k < 0 ? k : r; } +int terminal_reset_defensive(int fd, bool switch_to_text) { + int r = 0; + + assert(fd >= 0); + + /* Resets the terminal comprehensively, but defensively. i.e. both resets the tty via ioctl()s and + * via ANSI sequences, but avoids the latter in case we are talking to a pty. That's a safety measure + * because ptys might be connected to shell pipelines where we cannot expect such ansi sequences to + * work. Given that ptys are generally short-lived (and not recycled) this restriction shouldn't hurt + * much. + * + * The specified fd should be open for *writing*! */ + + RET_GATHER(r, reset_terminal_fd(fd, switch_to_text)); + + if (terminal_is_pty_fd(fd) == 0) + RET_GATHER(r, terminal_reset_ansi_seq(fd)); + + return r; +} + void termios_disable_echo(struct termios *termios) { assert(termios); diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index df28af02d50..c176933dd5c 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -98,6 +98,7 @@ bool isatty_safe(int fd); int reset_terminal_fd(int fd, bool switch_to_text); int reset_terminal(const char *name); int terminal_reset_ansi_seq(int fd); +int terminal_reset_defensive(int fd, bool switch_to_text); int terminal_set_cursor_position(int fd, unsigned row, unsigned column); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 94c908afd78..f8c0a811538 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -358,7 +358,7 @@ static int setup_input( (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); if (context->tty_reset) - (void) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ true); + (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ true); (void) exec_context_apply_tty_size(context, STDIN_FILENO, /* tty_path= */ NULL); } @@ -675,7 +675,7 @@ static int setup_confirm_stdio( if (r < 0) return r; - r = reset_terminal_fd(fd, /* switch_to_text= */ true); + r = terminal_reset_defensive(fd, /* switch_to_text= */ true); if (r < 0) return r; diff --git a/src/core/execute.c b/src/core/execute.c index 18396db113c..7590943179b 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -172,7 +172,7 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) (void) terminal_vhangup_fd(fd); if (context->tty_reset) - (void) reset_terminal_fd(fd, /* switch_to_text= */ true); + (void) terminal_reset_defensive(fd, /* switch_to_text= */ true); (void) exec_context_apply_tty_size(context, fd, path); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 92b0289859a..615142cc1ed 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -133,7 +133,7 @@ static void print_welcome(int rfd) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - (void) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); if (colors_enabled()) printf("\nWelcome to your new installation of \x1B[%sm%s\x1B[0m!\n", ac, pn); diff --git a/src/home/homectl.c b/src/home/homectl.c index 65431db8ee4..c64354141c5 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2438,7 +2438,7 @@ static int create_interactively(void) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); for (;;) { username = mfree(username); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index 2f09ae9f71c..fdd590107ee 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -282,4 +282,12 @@ TEST(get_color_mode) { reset_terminal_feature_caches(); } +TEST(terminal_reset_defensive) { + int r; + + r = terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); + if (r < 0) + log_notice_errno(r, "Failed to reset terminal: %m"); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 84ce5e1940e..b411c9c309d 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -150,9 +150,7 @@ static int agent_ask_password_tty( if (tty_fd < 0) return log_error_errno(tty_fd, "Failed to acquire %s: %m", con); - r = reset_terminal_fd(tty_fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); + (void) terminal_reset_defensive(tty_fd, /* switch_to_text= */ true); log_info("Starting password query on %s.", con); } From 841eb9c186816acfef8f3f724647a7f0649f214d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 09:34:11 +0200 Subject: [PATCH 17/46] terminal-util: don't issue "ESC c" sequence on reset, but only when erasing the screen ESC c is a (vaguely defined) "reset to initial state" ANSI sequence. Many terminals clear the screen in this case, but that's a bit drastic I think for most resets. ESC c was added to the reset logic in 00bc83a275fa3ca8d90579fe9597d8b651d47332 (i.e. very recently), and I don't think the effect was clear at that time. Let's keep the ESC c in place however when we actually want to clear the screen. Hence move it from reset_terminal_fd() into vt_disallocate(). Fixes: #33689 --- src/basic/terminal-util.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 9754b9900b6..84343a3956e 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -578,8 +578,9 @@ int vt_disallocate(const char *name) { (void) loop_write(fd2, "\033[r" /* clear scrolling region */ "\033[H" /* move home */ - "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ - 10); + "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ + "\033c", /* reset to initial state */ + SIZE_MAX); return 0; } @@ -1557,7 +1558,6 @@ int terminal_reset_ansi_seq(int fd) { return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); k = loop_write_full(fd, - "\033c" /* reset to initial state */ "\033[!p" /* soft terminal reset */ "\033]104\007" /* reset colors */ "\033[?7h", /* enable line-wrapping */ From 12f1b0134a11095eae6a1c47bbd70cc8dc8be0ed Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:23:14 +0200 Subject: [PATCH 18/46] terminal-util: remove reset_terminal() as it is unused --- src/basic/terminal-util.c | 14 -------------- src/basic/terminal-util.h | 1 - 2 files changed, 15 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 84343a3956e..5e54d33ae4d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -319,20 +319,6 @@ finish: return r; } -int reset_terminal(const char *name) { - _cleanup_close_ int fd = -EBADF; - - /* We open the terminal with O_NONBLOCK here, to ensure we - * don't block on carrier if this is a terminal with carrier - * configured. */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return reset_terminal_fd(fd, true); -} - int open_terminal(const char *name, int mode) { _cleanup_close_ int fd = -EBADF; unsigned c = 0; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index c176933dd5c..118672b340c 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -96,7 +96,6 @@ bool isatty_safe(int fd); int reset_terminal_fd(int fd, bool switch_to_text); -int reset_terminal(const char *name); int terminal_reset_ansi_seq(int fd); int terminal_reset_defensive(int fd, bool switch_to_text); From 5a4e541779630c876c5fcc905a0a35bec052f3c8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:26:14 +0200 Subject: [PATCH 19/46] =?UTF-8?q?terminal-util:=20rename=20reset=5Ftermina?= =?UTF-8?q?l=5Ffd()=20=E2=86=92=20terminal=5Freset=5Fioctl()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's put "terminal_" as prefix, like with the other reset calls, and let's make clear that this only encapsulates the ioctl-based reset logic, not the ANSI sequence based reset logic. --- src/basic/terminal-util.c | 6 +++--- src/basic/terminal-util.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 5e54d33ae4d..9720ac3c371 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -247,7 +247,7 @@ int ask_string(char **ret, const char *text, ...) { return 0; } -int reset_terminal_fd(int fd, bool switch_to_text) { +int terminal_reset_ioctl(int fd, bool switch_to_text) { struct termios termios; int r; @@ -575,7 +575,7 @@ void reset_dev_console_fd(int fd, bool switch_to_text) { assert(fd >= 0); - r = reset_terminal_fd(fd, switch_to_text); + r = terminal_reset_ioctl(fd, switch_to_text); if (r < 0) log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m"); @@ -1574,7 +1574,7 @@ int terminal_reset_defensive(int fd, bool switch_to_text) { * * The specified fd should be open for *writing*! */ - RET_GATHER(r, reset_terminal_fd(fd, switch_to_text)); + RET_GATHER(r, terminal_reset_ioctl(fd, switch_to_text)); if (terminal_is_pty_fd(fd) == 0) RET_GATHER(r, terminal_reset_ansi_seq(fd)); diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 118672b340c..46a91904c53 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -95,7 +95,7 @@ bool isatty_safe(int fd); -int reset_terminal_fd(int fd, bool switch_to_text); +int terminal_reset_ioctl(int fd, bool switch_to_text); int terminal_reset_ansi_seq(int fd); int terminal_reset_defensive(int fd, bool switch_to_text); From e2216800c5d69096973b792c6dcbf92a08392aab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:37:32 +0200 Subject: [PATCH 20/46] terminal-util: don't export terminal_reset_ioctl()/terminal_reset_ansi_seq() anymore We only use them in terminal-util.c, hence make them static (and move them before their first using function). --- src/basic/terminal-util.c | 203 +++++++++++++++++++------------------- src/basic/terminal-util.h | 2 - 2 files changed, 101 insertions(+), 104 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 9720ac3c371..f171265164a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -247,78 +247,6 @@ int ask_string(char **ret, const char *text, ...) { return 0; } -int terminal_reset_ioctl(int fd, bool switch_to_text) { - struct termios termios; - int r; - - /* Set terminal to some sane defaults */ - - assert(fd >= 0); - - if (!isatty_safe(fd)) - return log_debug_errno(errno, "Asked to reset a terminal that actually isn't a terminal: %m"); - - /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set, - * and we don't interfere with that. */ - - /* Disable exclusive mode, just in case */ - if (ioctl(fd, TIOCNXCL) < 0) - log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m"); - - /* Switch to text mode */ - if (switch_to_text) - if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) - log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m"); - - - /* Set default keyboard mode */ - r = vt_reset_keyboard(fd); - if (r < 0) - log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m"); - - if (tcgetattr(fd, &termios) < 0) { - r = log_debug_errno(errno, "Failed to get terminal parameters: %m"); - goto finish; - } - - /* We only reset the stuff that matters to the software. How - * hardware is set up we don't touch assuming that somebody - * else will do that for us */ - - termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); - termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; - termios.c_oflag |= ONLCR | OPOST; - termios.c_cflag |= CREAD; - termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE; - - termios.c_cc[VINTR] = 03; /* ^C */ - termios.c_cc[VQUIT] = 034; /* ^\ */ - termios.c_cc[VERASE] = 0177; - termios.c_cc[VKILL] = 025; /* ^X */ - termios.c_cc[VEOF] = 04; /* ^D */ - termios.c_cc[VSTART] = 021; /* ^Q */ - termios.c_cc[VSTOP] = 023; /* ^S */ - termios.c_cc[VSUSP] = 032; /* ^Z */ - termios.c_cc[VLNEXT] = 026; /* ^V */ - termios.c_cc[VWERASE] = 027; /* ^W */ - termios.c_cc[VREPRINT] = 022; /* ^R */ - termios.c_cc[VEOL] = 0; - termios.c_cc[VEOL2] = 0; - - termios.c_cc[VTIME] = 0; - termios.c_cc[VMIN] = 1; - - r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios)); - if (r < 0) - log_debug_errno(r, "Failed to set terminal parameters: %m"); - -finish: - /* Just in case, flush all crap out */ - (void) tcflush(fd, TCIOFLUSH); - - return r; -} - int open_terminal(const char *name, int mode) { _cleanup_close_ int fd = -EBADF; unsigned c = 0; @@ -570,6 +498,104 @@ int vt_disallocate(const char *name) { return 0; } +static int terminal_reset_ioctl(int fd, bool switch_to_text) { + struct termios termios; + int r; + + /* Set terminal to some sane defaults */ + + assert(fd >= 0); + + /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set, + * and we don't interfere with that. */ + + /* Disable exclusive mode, just in case */ + if (ioctl(fd, TIOCNXCL) < 0) + log_debug_errno(errno, "TIOCNXCL ioctl failed on TTY, ignoring: %m"); + + /* Switch to text mode */ + if (switch_to_text) + if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) + log_debug_errno(errno, "KDSETMODE ioctl for switching to text mode failed on TTY, ignoring: %m"); + + /* Set default keyboard mode */ + r = vt_reset_keyboard(fd); + if (r < 0) + log_debug_errno(r, "Failed to reset VT keyboard, ignoring: %m"); + + if (tcgetattr(fd, &termios) < 0) { + r = log_debug_errno(errno, "Failed to get terminal parameters: %m"); + goto finish; + } + + /* We only reset the stuff that matters to the software. How + * hardware is set up we don't touch assuming that somebody + * else will do that for us */ + + termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); + termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; + termios.c_oflag |= ONLCR | OPOST; + termios.c_cflag |= CREAD; + termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE; + + termios.c_cc[VINTR] = 03; /* ^C */ + termios.c_cc[VQUIT] = 034; /* ^\ */ + termios.c_cc[VERASE] = 0177; + termios.c_cc[VKILL] = 025; /* ^X */ + termios.c_cc[VEOF] = 04; /* ^D */ + termios.c_cc[VSTART] = 021; /* ^Q */ + termios.c_cc[VSTOP] = 023; /* ^S */ + termios.c_cc[VSUSP] = 032; /* ^Z */ + termios.c_cc[VLNEXT] = 026; /* ^V */ + termios.c_cc[VWERASE] = 027; /* ^W */ + termios.c_cc[VREPRINT] = 022; /* ^R */ + termios.c_cc[VEOL] = 0; + termios.c_cc[VEOL2] = 0; + + termios.c_cc[VTIME] = 0; + termios.c_cc[VMIN] = 1; + + r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios)); + if (r < 0) + log_debug_errno(r, "Failed to set terminal parameters: %m"); + +finish: + /* Just in case, flush all crap out */ + (void) tcflush(fd, TCIOFLUSH); + + return r; +} + +static int terminal_reset_ansi_seq(int fd) { + int r, k; + + assert(fd >= 0); + + if (getenv_terminal_is_dumb()) + return 0; + + r = fd_nonblock(fd, true); + if (r < 0) + return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + + k = loop_write_full(fd, + "\033[!p" /* soft terminal reset */ + "\033]104\007" /* reset colors */ + "\033[?7h", /* enable line-wrapping */ + SIZE_MAX, + 50 * USEC_PER_MSEC); + if (k < 0) + log_debug_errno(k, "Failed to write to terminal: %m"); + + if (r > 0) { + r = fd_nonblock(fd, false); + if (r < 0) + log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); + } + + return k < 0 ? k : r; +} + void reset_dev_console_fd(int fd, bool switch_to_text) { int r; @@ -1531,36 +1557,6 @@ int terminal_set_cursor_position(int fd, unsigned row, unsigned column) { return loop_write(fd, cursor_position, SIZE_MAX); } -int terminal_reset_ansi_seq(int fd) { - int r, k; - - assert(fd >= 0); - - if (getenv_terminal_is_dumb()) - return 0; - - r = fd_nonblock(fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); - - k = loop_write_full(fd, - "\033[!p" /* soft terminal reset */ - "\033]104\007" /* reset colors */ - "\033[?7h", /* enable line-wrapping */ - SIZE_MAX, - 50 * USEC_PER_MSEC); - if (k < 0) - log_debug_errno(k, "Failed to write to terminal: %m"); - - if (r > 0) { - r = fd_nonblock(fd, false); - if (r < 0) - log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); - } - - return k < 0 ? k : r; -} - int terminal_reset_defensive(int fd, bool switch_to_text) { int r = 0; @@ -1574,6 +1570,9 @@ int terminal_reset_defensive(int fd, bool switch_to_text) { * * The specified fd should be open for *writing*! */ + if (!isatty_safe(fd)) + return -ENOTTY; + RET_GATHER(r, terminal_reset_ioctl(fd, switch_to_text)); if (terminal_is_pty_fd(fd) == 0) diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 46a91904c53..aec08e1942e 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -95,8 +95,6 @@ bool isatty_safe(int fd); -int terminal_reset_ioctl(int fd, bool switch_to_text); -int terminal_reset_ansi_seq(int fd); int terminal_reset_defensive(int fd, bool switch_to_text); int terminal_set_cursor_position(int fd, unsigned row, unsigned column); From 524e1240ff1fea45be672bcbea5633f109764bb3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Jul 2024 09:17:48 +0200 Subject: [PATCH 21/46] terminal-util: modernize terminal_reset_ansi_seq() a bit Let's update the commentary a bit. Also, use a time-out of 100ms rather than 50ms for this, simply to unify on the same value used in vt_disallocate() in a similar case. --- src/basic/terminal-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index f171265164a..0c886afc05a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -583,9 +583,9 @@ static int terminal_reset_ansi_seq(int fd) { "\033]104\007" /* reset colors */ "\033[?7h", /* enable line-wrapping */ SIZE_MAX, - 50 * USEC_PER_MSEC); + 100 * USEC_PER_MSEC); if (k < 0) - log_debug_errno(k, "Failed to write to terminal: %m"); + log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m"); if (r > 0) { r = fd_nonblock(fd, false); From b61c015aeb5b0abddca3a8ce2a8b0d7c58433b3f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:47:01 +0200 Subject: [PATCH 22/46] terminal-util: don't export vt_reset_keyboard() + vt_default_utf8() --- src/basic/terminal-util.c | 44 +++++++++++++++++++-------------------- src/basic/terminal-util.h | 2 -- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 0c886afc05a..60dcdb97128 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -498,6 +498,28 @@ int vt_disallocate(const char *name) { return 0; } +static int vt_default_utf8(void) { + _cleanup_free_ char *b = NULL; + int r; + + /* Read the default VT UTF8 setting from the kernel */ + + r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); + if (r < 0) + return r; + + return parse_boolean(b); +} + +static int vt_reset_keyboard(int fd) { + int kb; + + /* If we can't read the default, then default to unicode. It's 2017 after all. */ + kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE; + + return RET_NERRNO(ioctl(fd, KDSKBMODE, kb)); +} + static int terminal_reset_ioctl(int fd, bool switch_to_text) { struct termios termios; int r; @@ -1439,28 +1461,6 @@ bool underline_enabled(void) { return cached_underline_enabled; } -int vt_default_utf8(void) { - _cleanup_free_ char *b = NULL; - int r; - - /* Read the default VT UTF8 setting from the kernel */ - - r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); - if (r < 0) - return r; - - return parse_boolean(b); -} - -int vt_reset_keyboard(int fd) { - int kb; - - /* If we can't read the default, then default to unicode. It's 2017 after all. */ - kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE; - - return RET_NERRNO(ioctl(fd, KDSKBMODE, kb)); -} - int vt_restore(int fd) { static const struct vt_mode mode = { diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index aec08e1942e..8b55ade765a 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -277,8 +277,6 @@ int openpt_allocate(int flags, char **ret_slave); int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave); int open_terminal_in_namespace(pid_t pid, const char *name, int mode); -int vt_default_utf8(void); -int vt_reset_keyboard(int fd); int vt_restore(int fd); int vt_release(int fd, bool restore_vt); From af1d3a6d928cb6f6be342d35ee5672c2a31e9d6c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Jul 2024 09:17:29 +0200 Subject: [PATCH 23/46] terminal-util: modernize vt_reset_keyboard() a bit --- src/basic/terminal-util.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 60dcdb97128..c1f463a39b7 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -512,11 +512,16 @@ static int vt_default_utf8(void) { } static int vt_reset_keyboard(int fd) { - int kb; + int r, kb; + + assert(fd >= 0); + + /* If we can't read the default, then default to Unicode. It's 2024 after all. */ + r = vt_default_utf8(); + if (r < 0) + log_debug_errno(r, "Failed to determine kernel VT UTF-8 mode, assuming enabled: %m"); - /* If we can't read the default, then default to unicode. It's 2017 after all. */ kb = vt_default_utf8() != 0 ? K_UNICODE : K_XLATE; - return RET_NERRNO(ioctl(fd, KDSKBMODE, kb)); } From ac508b1173ae6cec95ccb3984a6f0c2dbc02ea98 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:50:51 +0200 Subject: [PATCH 24/46] terminal-util: rename return parameters ret_xyz --- src/basic/terminal-util.c | 10 +++++----- src/basic/terminal-util.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index c1f463a39b7..14556fa0408 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1059,11 +1059,11 @@ int getttyname_harder(int fd, char **ret) { return 0; } -int get_ctty_devnr(pid_t pid, dev_t *d) { - int r; +int get_ctty_devnr(pid_t pid, dev_t *ret) { _cleanup_free_ char *line = NULL; - const char *p; unsigned long ttynr; + const char *p; + int r; assert(pid >= 0); @@ -1090,8 +1090,8 @@ int get_ctty_devnr(pid_t pid, dev_t *d) { if (devnum_is_zero(ttynr)) return -ENXIO; - if (d) - *d = (dev_t) ttynr; + if (ret) + *ret = (dev_t) ttynr; return 0; } diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 8b55ade765a..86bbcd7af52 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -265,11 +265,11 @@ DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue_underline, HIGHLIGHT_BLUE); DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta_underline, HIGHLIGHT_MAGENTA); DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey_underline, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); -int get_ctty_devnr(pid_t pid, dev_t *d); -int get_ctty(pid_t, dev_t *_devnr, char **r); +int get_ctty_devnr(pid_t pid, dev_t *ret); +int get_ctty(pid_t, dev_t *ret_devnr, char **ret); -int getttyname_malloc(int fd, char **r); -int getttyname_harder(int fd, char **r); +int getttyname_malloc(int fd, char **ret); +int getttyname_harder(int fd, char **ret); int ptsname_malloc(int fd, char **ret); From 1ca392482765d96c40a4eb207b50df1fac947100 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 10:54:24 +0200 Subject: [PATCH 25/46] terminal-util: remove terminal_vhangup() because apparently unused --- src/basic/terminal-util.c | 10 ---------- src/basic/terminal-util.h | 1 - 2 files changed, 11 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 14556fa0408..698d39d180b 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -432,16 +432,6 @@ int terminal_vhangup_fd(int fd) { return RET_NERRNO(ioctl(fd, TIOCVHANGUP)); } -int terminal_vhangup(const char *name) { - _cleanup_close_ int fd = -EBADF; - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return terminal_vhangup_fd(fd); -} - int vt_disallocate(const char *name) { const char *e; int r; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 86bbcd7af52..f03e3985c65 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -133,7 +133,6 @@ int acquire_terminal(const char *name, AcquireTerminalFlags flags, usec_t timeou int release_terminal(void); int terminal_vhangup_fd(int fd); -int terminal_vhangup(const char *name); int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols); int proc_cmdline_tty_size(const char *tty, unsigned *ret_rows, unsigned *ret_cols); From b7120388f8dd638aec33dc640f273d1a9016d676 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:02:28 +0200 Subject: [PATCH 26/46] terminal-util: split out color macros/helpers into its own header This is a lot of stuff, and sometimes quite wild, let's turn this into its own header. All stuff color-related that just generates sequences is now in ansi-color.h (no .c file!), and everything more complex that probes/ineracts with terminals remains in termina-util.[ch] --- src/basic/ansi-color.h | 162 ++++++++++++++++++ src/basic/build.c | 1 + src/basic/log.c | 1 + src/basic/terminal-util.c | 1 + src/basic/terminal-util.h | 158 ----------------- src/core/emergency-action.c | 1 + src/core/job.c | 1 + src/core/transaction.c | 1 + src/core/unit.c | 1 + src/cryptenroll/cryptenroll-recovery.c | 1 + src/home/homectl-recovery-key.c | 1 + src/import/export.c | 1 + src/import/import-fs.c | 1 + src/import/import.c | 1 + src/import/pull.c | 1 + src/journal/journalctl-authenticate.c | 1 + src/journal/journalctl-show.c | 1 + src/libsystemd/sd-bus/bus-dump.c | 1 + src/libsystemd/sd-journal/journal-verify.c | 1 + .../sd-journal/test-journal-verify.c | 1 + src/libsystemd/sd-json/sd-json.c | 1 + src/libsystemd/sd-varlink/sd-varlink-idl.c | 1 + src/shared/ask-password-api.c | 1 + src/shared/blockdev-list.c | 1 + src/shared/bus-unit-procs.c | 1 + src/shared/cgroup-show.c | 1 + src/shared/pretty-print.h | 1 + src/shared/ptyfwd.c | 1 + src/shared/qrcode-util.c | 1 + src/systemctl/systemctl-list-dependencies.c | 1 + src/systemctl/systemctl-list-jobs.c | 1 + src/systemctl/systemctl-list-machines.c | 1 + src/systemctl/systemctl-list-unit-files.c | 1 + src/systemctl/systemctl-list-units.c | 1 + src/systemctl/systemctl-start-unit.c | 1 + src/sysupdate/sysupdate-update-set-flags.c | 1 + src/sysupdate/sysupdate-update-set.c | 1 + src/test/test-ellipsize.c | 1 + src/test/test-terminal-util.c | 1 + src/udev/udevadm-info.c | 1 + src/udev/udevadm-test.c | 1 + 41 files changed, 201 insertions(+), 158 deletions(-) create mode 100644 src/basic/ansi-color.h diff --git a/src/basic/ansi-color.h b/src/basic/ansi-color.h new file mode 100644 index 00000000000..efc1dea1314 --- /dev/null +++ b/src/basic/ansi-color.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "terminal-util.h" + +/* Regular colors */ +#define ANSI_BLACK "\x1B[0;30m" /* Some type of grey usually. */ +#define ANSI_RED "\x1B[0;31m" +#define ANSI_GREEN "\x1B[0;32m" +#define ANSI_YELLOW "\x1B[0;33m" +#define ANSI_BLUE "\x1B[0;34m" +#define ANSI_MAGENTA "\x1B[0;35m" +#define ANSI_CYAN "\x1B[0;36m" +#define ANSI_WHITE "\x1B[0;37m" /* This is actually rendered as light grey, legible even on a white + * background. See ANSI_HIGHLIGHT_WHITE for real white. */ + +#define ANSI_BRIGHT_BLACK "\x1B[0;90m" +#define ANSI_BRIGHT_RED "\x1B[0;91m" +#define ANSI_BRIGHT_GREEN "\x1B[0;92m" +#define ANSI_BRIGHT_YELLOW "\x1B[0;93m" +#define ANSI_BRIGHT_BLUE "\x1B[0;94m" +#define ANSI_BRIGHT_MAGENTA "\x1B[0;95m" +#define ANSI_BRIGHT_CYAN "\x1B[0;96m" +#define ANSI_BRIGHT_WHITE "\x1B[0;97m" + +#define ANSI_GREY "\x1B[0;38;5;245m" + +/* Bold/highlighted */ +#define ANSI_HIGHLIGHT_BLACK "\x1B[0;1;30m" +#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" +#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" +#define _ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" /* This yellow is currently not displayed well by some terminals */ +#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" +#define ANSI_HIGHLIGHT_MAGENTA "\x1B[0;1;35m" +#define ANSI_HIGHLIGHT_CYAN "\x1B[0;1;36m" +#define ANSI_HIGHLIGHT_WHITE "\x1B[0;1;37m" +#define ANSI_HIGHLIGHT_YELLOW4 "\x1B[0;1;38:5:100m" +#define ANSI_HIGHLIGHT_KHAKI3 "\x1B[0;1;38:5:185m" +#define ANSI_HIGHLIGHT_GREY "\x1B[0;1;38:5:245m" + +#define ANSI_HIGHLIGHT_YELLOW ANSI_HIGHLIGHT_KHAKI3 /* Replacement yellow that is more legible */ + +/* Underlined */ +#define ANSI_GREY_UNDERLINE "\x1B[0;4;38:5:245m" +#define ANSI_BRIGHT_BLACK_UNDERLINE "\x1B[0;4;90m" +#define ANSI_HIGHLIGHT_RED_UNDERLINE "\x1B[0;1;4;31m" +#define ANSI_HIGHLIGHT_GREEN_UNDERLINE "\x1B[0;1;4;32m" +#define ANSI_HIGHLIGHT_YELLOW_UNDERLINE "\x1B[0;1;4;38:5:185m" +#define ANSI_HIGHLIGHT_BLUE_UNDERLINE "\x1B[0;1;4;34m" +#define ANSI_HIGHLIGHT_MAGENTA_UNDERLINE "\x1B[0;1;4;35m" +#define ANSI_HIGHLIGHT_GREY_UNDERLINE "\x1B[0;1;4;38:5:245m" + +/* Other ANSI codes */ +#define ANSI_UNDERLINE "\x1B[0;4m" +#define ANSI_ADD_UNDERLINE "\x1B[4m" +#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58:5:245m" +#define ANSI_HIGHLIGHT "\x1B[0;1;39m" +#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" + +/* Fallback colors: 256 → 16 */ +#define ANSI_HIGHLIGHT_GREY_FALLBACK "\x1B[0;1;90m" +#define ANSI_HIGHLIGHT_GREY_FALLBACK_UNDERLINE "\x1B[0;1;4;90m" +#define ANSI_HIGHLIGHT_YELLOW_FALLBACK "\x1B[0;1;33m" +#define ANSI_HIGHLIGHT_YELLOW_FALLBACK_UNDERLINE "\x1B[0;1;4;33m" + +/* Background colors */ +#define ANSI_BACKGROUND_BLUE "\x1B[44m" + +/* Reset/clear ANSI styles */ +#define ANSI_NORMAL "\x1B[0m" + +#define DEFINE_ANSI_FUNC(name, NAME) \ + static inline const char* ansi_##name(void) { \ + return colors_enabled() ? ANSI_##NAME : ""; \ + } + +#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name(void) { \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return ANSI_##FALLBACK; \ + default : return ANSI_##NAME; \ + } \ + } + +static inline const char* ansi_underline(void) { + return underline_enabled() ? ANSI_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline(void) { + return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; +} + +static inline const char* ansi_add_underline_grey(void) { + return underline_enabled() ? + (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; +} + +#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ + static inline const char* ansi_##name(void) { \ + return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ + colors_enabled() ? ANSI_##NAME : ""; \ + } + + +#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ + static inline const char* ansi_##name(void) { \ + switch (get_color_mode()) { \ + case COLOR_OFF: return ""; \ + case COLOR_16: return underline_enabled() ? ANSI_##FALLBACK##_UNDERLINE : ANSI_##FALLBACK; \ + default : return underline_enabled() ? ANSI_##NAME##_UNDERLINE: ANSI_##NAME; \ + } \ + } + +DEFINE_ANSI_FUNC(normal, NORMAL); +DEFINE_ANSI_FUNC(highlight, HIGHLIGHT); +DEFINE_ANSI_FUNC(black, BLACK); +DEFINE_ANSI_FUNC(red, RED); +DEFINE_ANSI_FUNC(green, GREEN); +DEFINE_ANSI_FUNC(yellow, YELLOW); +DEFINE_ANSI_FUNC(blue, BLUE); +DEFINE_ANSI_FUNC(magenta, MAGENTA); +DEFINE_ANSI_FUNC(cyan, CYAN); +DEFINE_ANSI_FUNC(white, WHITE); +DEFINE_ANSI_FUNC_256(grey, GREY, BRIGHT_BLACK); + +DEFINE_ANSI_FUNC(bright_black, BRIGHT_BLACK); +DEFINE_ANSI_FUNC(bright_red, BRIGHT_RED); +DEFINE_ANSI_FUNC(bright_green, BRIGHT_GREEN); +DEFINE_ANSI_FUNC(bright_yellow, BRIGHT_YELLOW); +DEFINE_ANSI_FUNC(bright_blue, BRIGHT_BLUE); +DEFINE_ANSI_FUNC(bright_magenta, BRIGHT_MAGENTA); +DEFINE_ANSI_FUNC(bright_cyan, BRIGHT_CYAN); +DEFINE_ANSI_FUNC(bright_white, BRIGHT_WHITE); + +DEFINE_ANSI_FUNC(highlight_black, HIGHLIGHT_BLACK); +DEFINE_ANSI_FUNC(highlight_red, HIGHLIGHT_RED); +DEFINE_ANSI_FUNC(highlight_green, HIGHLIGHT_GREEN); +DEFINE_ANSI_FUNC_256(highlight_yellow, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); +DEFINE_ANSI_FUNC_256(highlight_yellow4, HIGHLIGHT_YELLOW4, HIGHLIGHT_YELLOW_FALLBACK); +DEFINE_ANSI_FUNC(highlight_blue, HIGHLIGHT_BLUE); +DEFINE_ANSI_FUNC(highlight_magenta, HIGHLIGHT_MAGENTA); +DEFINE_ANSI_FUNC(highlight_cyan, HIGHLIGHT_CYAN); +DEFINE_ANSI_FUNC_256(highlight_grey, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); +DEFINE_ANSI_FUNC(highlight_white, HIGHLIGHT_WHITE); + +static inline const char* _ansi_highlight_yellow(void) { + return colors_enabled() ? _ANSI_HIGHLIGHT_YELLOW : ""; +} + +DEFINE_ANSI_FUNC_UNDERLINE(highlight_underline, HIGHLIGHT); +DEFINE_ANSI_FUNC_UNDERLINE_256(grey_underline, GREY, BRIGHT_BLACK); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_red_underline, HIGHLIGHT_RED); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_green_underline, HIGHLIGHT_GREEN); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow_underline, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue_underline, HIGHLIGHT_BLUE); +DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta_underline, HIGHLIGHT_MAGENTA); +DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey_underline, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); + +static inline const char* ansi_highlight_green_red(bool b) { + return b ? ansi_highlight_green() : ansi_highlight_red(); +} diff --git a/src/basic/build.c b/src/basic/build.c index 488ed207138..50e975e80df 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "extract-word.h" #include "macro.h" diff --git a/src/basic/log.c b/src/basic/log.c index 50b405b8438..a8056d2635b 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -16,6 +16,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "argv-util.h" #include "env-util.h" #include "errno-util.h" diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 698d39d180b..ffd687c8984 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -21,6 +21,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "constants.h" #include "devnum-util.h" #include "env-util.h" diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index f03e3985c65..16f749f75cc 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -11,72 +11,6 @@ #include "macro.h" #include "time-util.h" -/* Regular colors */ -#define ANSI_BLACK "\x1B[0;30m" /* Some type of grey usually. */ -#define ANSI_RED "\x1B[0;31m" -#define ANSI_GREEN "\x1B[0;32m" -#define ANSI_YELLOW "\x1B[0;33m" -#define ANSI_BLUE "\x1B[0;34m" -#define ANSI_MAGENTA "\x1B[0;35m" -#define ANSI_CYAN "\x1B[0;36m" -#define ANSI_WHITE "\x1B[0;37m" /* This is actually rendered as light grey, legible even on a white - * background. See ANSI_HIGHLIGHT_WHITE for real white. */ - -#define ANSI_BRIGHT_BLACK "\x1B[0;90m" -#define ANSI_BRIGHT_RED "\x1B[0;91m" -#define ANSI_BRIGHT_GREEN "\x1B[0;92m" -#define ANSI_BRIGHT_YELLOW "\x1B[0;93m" -#define ANSI_BRIGHT_BLUE "\x1B[0;94m" -#define ANSI_BRIGHT_MAGENTA "\x1B[0;95m" -#define ANSI_BRIGHT_CYAN "\x1B[0;96m" -#define ANSI_BRIGHT_WHITE "\x1B[0;97m" - -#define ANSI_GREY "\x1B[0;38;5;245m" - -/* Bold/highlighted */ -#define ANSI_HIGHLIGHT_BLACK "\x1B[0;1;30m" -#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" -#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" -#define _ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" /* This yellow is currently not displayed well by some terminals */ -#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" -#define ANSI_HIGHLIGHT_MAGENTA "\x1B[0;1;35m" -#define ANSI_HIGHLIGHT_CYAN "\x1B[0;1;36m" -#define ANSI_HIGHLIGHT_WHITE "\x1B[0;1;37m" -#define ANSI_HIGHLIGHT_YELLOW4 "\x1B[0;1;38:5:100m" -#define ANSI_HIGHLIGHT_KHAKI3 "\x1B[0;1;38:5:185m" -#define ANSI_HIGHLIGHT_GREY "\x1B[0;1;38:5:245m" - -#define ANSI_HIGHLIGHT_YELLOW ANSI_HIGHLIGHT_KHAKI3 /* Replacement yellow that is more legible */ - -/* Underlined */ -#define ANSI_GREY_UNDERLINE "\x1B[0;4;38:5:245m" -#define ANSI_BRIGHT_BLACK_UNDERLINE "\x1B[0;4;90m" -#define ANSI_HIGHLIGHT_RED_UNDERLINE "\x1B[0;1;4;31m" -#define ANSI_HIGHLIGHT_GREEN_UNDERLINE "\x1B[0;1;4;32m" -#define ANSI_HIGHLIGHT_YELLOW_UNDERLINE "\x1B[0;1;4;38:5:185m" -#define ANSI_HIGHLIGHT_BLUE_UNDERLINE "\x1B[0;1;4;34m" -#define ANSI_HIGHLIGHT_MAGENTA_UNDERLINE "\x1B[0;1;4;35m" -#define ANSI_HIGHLIGHT_GREY_UNDERLINE "\x1B[0;1;4;38:5:245m" - -/* Other ANSI codes */ -#define ANSI_UNDERLINE "\x1B[0;4m" -#define ANSI_ADD_UNDERLINE "\x1B[4m" -#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58:5:245m" -#define ANSI_HIGHLIGHT "\x1B[0;1;39m" -#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" - -/* Fallback colors: 256 -> 16 */ -#define ANSI_HIGHLIGHT_GREY_FALLBACK "\x1B[0;1;90m" -#define ANSI_HIGHLIGHT_GREY_FALLBACK_UNDERLINE "\x1B[0;1;4;90m" -#define ANSI_HIGHLIGHT_YELLOW_FALLBACK "\x1B[0;1;33m" -#define ANSI_HIGHLIGHT_YELLOW_FALLBACK_UNDERLINE "\x1B[0;1;4;33m" - -/* Background colors */ -#define ANSI_BACKGROUND_BLUE "\x1B[44m" - -/* Reset/clear ANSI styles */ -#define ANSI_NORMAL "\x1B[0m" - /* Erase characters until the end of the line */ #define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" @@ -176,94 +110,6 @@ static inline bool colors_enabled(void) { return get_color_mode() != COLOR_OFF; } -#define DEFINE_ANSI_FUNC(name, NAME) \ - static inline const char *ansi_##name(void) { \ - return colors_enabled() ? ANSI_##NAME : ""; \ - } - -#define DEFINE_ANSI_FUNC_256(name, NAME, FALLBACK) \ - static inline const char *ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return ANSI_##FALLBACK; \ - default : return ANSI_##NAME; \ - } \ - } - -static inline const char* ansi_underline(void) { - return underline_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline(void) { - return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; -} - -static inline const char* ansi_add_underline_grey(void) { - return underline_enabled() ? - (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; -} - -#define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ - static inline const char *ansi_##name(void) { \ - return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ - colors_enabled() ? ANSI_##NAME : ""; \ - } - - -#define DEFINE_ANSI_FUNC_UNDERLINE_256(name, NAME, FALLBACK) \ - static inline const char *ansi_##name(void) { \ - switch (get_color_mode()) { \ - case COLOR_OFF: return ""; \ - case COLOR_16: return underline_enabled() ? ANSI_##FALLBACK##_UNDERLINE : ANSI_##FALLBACK; \ - default : return underline_enabled() ? ANSI_##NAME##_UNDERLINE: ANSI_##NAME; \ - } \ - } - -DEFINE_ANSI_FUNC(normal, NORMAL); -DEFINE_ANSI_FUNC(highlight, HIGHLIGHT); -DEFINE_ANSI_FUNC(black, BLACK); -DEFINE_ANSI_FUNC(red, RED); -DEFINE_ANSI_FUNC(green, GREEN); -DEFINE_ANSI_FUNC(yellow, YELLOW); -DEFINE_ANSI_FUNC(blue, BLUE); -DEFINE_ANSI_FUNC(magenta, MAGENTA); -DEFINE_ANSI_FUNC(cyan, CYAN); -DEFINE_ANSI_FUNC(white, WHITE); -DEFINE_ANSI_FUNC_256(grey, GREY, BRIGHT_BLACK); - -DEFINE_ANSI_FUNC(bright_black, BRIGHT_BLACK); -DEFINE_ANSI_FUNC(bright_red, BRIGHT_RED); -DEFINE_ANSI_FUNC(bright_green, BRIGHT_GREEN); -DEFINE_ANSI_FUNC(bright_yellow, BRIGHT_YELLOW); -DEFINE_ANSI_FUNC(bright_blue, BRIGHT_BLUE); -DEFINE_ANSI_FUNC(bright_magenta, BRIGHT_MAGENTA); -DEFINE_ANSI_FUNC(bright_cyan, BRIGHT_CYAN); -DEFINE_ANSI_FUNC(bright_white, BRIGHT_WHITE); - -DEFINE_ANSI_FUNC(highlight_black, HIGHLIGHT_BLACK); -DEFINE_ANSI_FUNC(highlight_red, HIGHLIGHT_RED); -DEFINE_ANSI_FUNC(highlight_green, HIGHLIGHT_GREEN); -DEFINE_ANSI_FUNC_256(highlight_yellow, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); -DEFINE_ANSI_FUNC_256(highlight_yellow4, HIGHLIGHT_YELLOW4, HIGHLIGHT_YELLOW_FALLBACK); -DEFINE_ANSI_FUNC(highlight_blue, HIGHLIGHT_BLUE); -DEFINE_ANSI_FUNC(highlight_magenta, HIGHLIGHT_MAGENTA); -DEFINE_ANSI_FUNC(highlight_cyan, HIGHLIGHT_CYAN); -DEFINE_ANSI_FUNC_256(highlight_grey, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); -DEFINE_ANSI_FUNC(highlight_white, HIGHLIGHT_WHITE); - -static inline const char* _ansi_highlight_yellow(void) { - return colors_enabled() ? _ANSI_HIGHLIGHT_YELLOW : ""; -} - -DEFINE_ANSI_FUNC_UNDERLINE(highlight_underline, HIGHLIGHT); -DEFINE_ANSI_FUNC_UNDERLINE_256(grey_underline, GREY, BRIGHT_BLACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_red_underline, HIGHLIGHT_RED); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_green_underline, HIGHLIGHT_GREEN); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_yellow_underline, HIGHLIGHT_YELLOW, HIGHLIGHT_YELLOW_FALLBACK); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_blue_underline, HIGHLIGHT_BLUE); -DEFINE_ANSI_FUNC_UNDERLINE(highlight_magenta_underline, HIGHLIGHT_MAGENTA); -DEFINE_ANSI_FUNC_UNDERLINE_256(highlight_grey_underline, HIGHLIGHT_GREY, HIGHLIGHT_GREY_FALLBACK); - int get_ctty_devnr(pid_t pid, dev_t *ret); int get_ctty(pid_t, dev_t *ret_devnr, char **ret); @@ -281,10 +127,6 @@ int vt_release(int fd, bool restore_vt); void get_log_colors(int priority, const char **on, const char **off, const char **highlight); -static inline const char* ansi_highlight_green_red(bool b) { - return b ? ansi_highlight_green() : ansi_highlight_red(); -} - /* This assumes there is a 'tty' group */ #define TTY_MODE 0620 diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index dbda6e54573..75c26df52f0 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -2,6 +2,7 @@ #include +#include "ansi-color.h" #include "bus-error.h" #include "bus-util.h" #include "emergency-action.h" diff --git a/src/core/job.c b/src/core/job.c index a877f9f06f3..21083497c09 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -6,6 +6,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "async.h" #include "cgroup.h" #include "dbus-job.h" diff --git a/src/core/transaction.c b/src/core/transaction.c index ab6e699d33e..9d48768d876 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "bus-common-errors.h" #include "bus-error.h" #include "dbus-unit.h" diff --git a/src/core/unit.c b/src/core/unit.c index c281a29391d..2d750b7ef48 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -10,6 +10,7 @@ #include "all-units.h" #include "alloc-util.h" +#include "ansi-color.h" #include "bpf-firewall.h" #include "bpf-foreign.h" #include "bpf-socket-bind.h" diff --git a/src/cryptenroll/cryptenroll-recovery.c b/src/cryptenroll/cryptenroll-recovery.c index c4bf1fab959..0b1e380e065 100644 --- a/src/cryptenroll/cryptenroll-recovery.c +++ b/src/cryptenroll/cryptenroll-recovery.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "cryptenroll-recovery.h" #include "glyph-util.h" #include "json-util.h" diff --git a/src/home/homectl-recovery-key.c b/src/home/homectl-recovery-key.c index 015d2654350..b5c57e14a8a 100644 --- a/src/home/homectl-recovery-key.c +++ b/src/home/homectl-recovery-key.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "errno-util.h" #include "glyph-util.h" #include "homectl-recovery-key.h" diff --git a/src/import/export.c b/src/import/export.c index cdb1d6246a3..1e82e84d7d9 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -7,6 +7,7 @@ #include "sd-id128.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "discover-image.h" #include "export-raw.h" diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 44fc5be8a5c..e74d36288af 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -4,6 +4,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "btrfs-util.h" #include "discover-image.h" diff --git a/src/import/import.c b/src/import/import.c index 889cd63ff0d..97db0b836a4 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -7,6 +7,7 @@ #include "sd-id128.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "discover-image.h" #include "env-util.h" diff --git a/src/import/pull.c b/src/import/pull.c index 7c838a5307b..46055ce5e73 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -7,6 +7,7 @@ #include "sd-id128.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "discover-image.h" #include "env-util.h" diff --git a/src/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c index 10630f5e6cc..8167aef7f56 100644 --- a/src/journal/journalctl-authenticate.c +++ b/src/journal/journalctl-authenticate.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "chattr-util.h" #include "errno-util.h" #include "fd-util.h" diff --git a/src/journal/journalctl-show.c b/src/journal/journalctl-show.c index e8ffc72bb8a..9f07facad42 100644 --- a/src/journal/journalctl-show.c +++ b/src/journal/journalctl-show.c @@ -4,6 +4,7 @@ #include "sd-event.h" +#include "ansi-color.h" #include "fileio.h" #include "journalctl.h" #include "journalctl-filter.h" diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c index aa46fec91b9..5fc3614afb7 100644 --- a/src/libsystemd/sd-bus/bus-dump.c +++ b/src/libsystemd/sd-bus/bus-dump.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "bus-dump.h" #include "bus-internal.h" #include "bus-message.h" diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index e852591a891..0fb93340a61 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -6,6 +6,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "compress.h" #include "fd-util.h" #include "fileio.h" diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c index 396ebe192cb..dde40bf3774 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -4,6 +4,7 @@ #include #include +#include "ansi-color.h" #include "chattr-util.h" #include "fd-util.h" #include "iovec-util.h" diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index 2219bf26cc7..98ffe4db548 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -10,6 +10,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "errno-util.h" #include "escape.h" #include "ether-addr-util.h" diff --git a/src/libsystemd/sd-varlink/sd-varlink-idl.c b/src/libsystemd/sd-varlink/sd-varlink-idl.c index 181e6c193df..76a2499561d 100644 --- a/src/libsystemd/sd-varlink/sd-varlink-idl.c +++ b/src/libsystemd/sd-varlink/sd-varlink-idl.c @@ -2,6 +2,7 @@ #include "sd-varlink-idl.h" +#include "ansi-color.h" #include "json-util.h" #include "memstream-util.h" #include "set.h" diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index bf79dc2633c..778b31274d7 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -19,6 +19,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "ask-password-api.h" #include "creds-util.h" #include "fd-util.h" diff --git a/src/shared/blockdev-list.c b/src/shared/blockdev-list.c index 2e6ed362f28..120f7201df8 100644 --- a/src/shared/blockdev-list.c +++ b/src/shared/blockdev-list.c @@ -2,6 +2,7 @@ #include "sd-device.h" +#include "ansi-color.h" #include "blockdev-list.h" #include "blockdev-util.h" #include "device-util.h" diff --git a/src/shared/bus-unit-procs.c b/src/shared/bus-unit-procs.c index 8b462b56273..4d6f2be3c48 100644 --- a/src/shared/bus-unit-procs.c +++ b/src/shared/bus-unit-procs.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "bus-locator.h" #include "bus-unit-procs.h" #include "glyph-util.h" diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index 87177316da8..33fe222730f 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -7,6 +7,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "bus-error.h" #include "bus-util.h" #include "cgroup-show.h" diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 9eb16119059..2c97c354ef0 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "ansi-color.h" #include "glyph-util.h" #include "terminal-util.h" diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 83eef7f7d82..3e11ba456f1 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -17,6 +17,7 @@ #include "sd-event.h" #include "alloc-util.h" +#include "ansi-color.h" #include "env-util.h" #include "errno-util.h" #include "extract-word.h" diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index 10ed542df22..36cd5dcd7cf 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -5,6 +5,7 @@ #if HAVE_QRENCODE #include +#include "ansi-color.h" #include "dlfcn-util.h" #include "locale-util.h" #include "log.h" diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c index a9121f10356..3df9b7abdff 100644 --- a/src/systemctl/systemctl-list-dependencies.c +++ b/src/systemctl/systemctl-list-dependencies.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "locale-util.h" #include "sort-util.h" #include "special.h" diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index fcfe2ac561b..2fecf613179 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "bus-error.h" #include "bus-locator.h" #include "locale-util.h" diff --git a/src/systemctl/systemctl-list-machines.c b/src/systemctl/systemctl-list-machines.c index 4407d2598a9..1fffceb38e0 100644 --- a/src/systemctl/systemctl-list-machines.c +++ b/src/systemctl/systemctl-list-machines.c @@ -4,6 +4,7 @@ #include "sd-login.h" +#include "ansi-color.h" #include "bus-map-properties.h" #include "hostname-util.h" #include "locale-util.h" diff --git a/src/systemctl/systemctl-list-unit-files.c b/src/systemctl/systemctl-list-unit-files.c index 943d7ffec52..975b3ebc548 100644 --- a/src/systemctl/systemctl-list-unit-files.c +++ b/src/systemctl/systemctl-list-unit-files.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "bus-error.h" #include "bus-locator.h" #include "sort-util.h" diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index 184468ed618..b4ccc8ebd76 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -2,6 +2,7 @@ #include "sd-login.h" +#include "ansi-color.h" #include "bus-error.h" #include "bus-locator.h" #include "format-table.h" diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 8068d77d1cd..909f051cdef 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -2,6 +2,7 @@ #include "sd-bus.h" +#include "ansi-color.h" #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" diff --git a/src/sysupdate/sysupdate-update-set-flags.c b/src/sysupdate/sysupdate-update-set-flags.c index 66b3d340bb1..d9232e4a17b 100644 --- a/src/sysupdate/sysupdate-update-set-flags.c +++ b/src/sysupdate/sysupdate-update-set-flags.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "glyph-util.h" #include "sysupdate-update-set-flags.h" #include "terminal-util.h" diff --git a/src/sysupdate/sysupdate-update-set.c b/src/sysupdate/sysupdate-update-set.c index 53bcc5d9658..0756dff04b4 100644 --- a/src/sysupdate/sysupdate-update-set.c +++ b/src/sysupdate/sysupdate-update-set.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "ansi-color.h" #include "alloc-util.h" #include "string-util.h" #include "sysupdate-update-set.h" diff --git a/src/test/test-ellipsize.c b/src/test/test-ellipsize.c index f1814768fd6..d70b727cee7 100644 --- a/src/test/test-ellipsize.c +++ b/src/test/test-ellipsize.c @@ -3,6 +3,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "constants.h" #include "escape.h" #include "string-util.h" diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index fdd590107ee..e7cfeab6d00 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -9,6 +9,7 @@ #include #include "alloc-util.h" +#include "ansi-color.h" #include "fd-util.h" #include "fs-util.h" #include "macro.h" diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 1d105047c90..2cdc3bd3057 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -13,6 +13,7 @@ #include "sd-json.h" #include "alloc-util.h" +#include "ansi-color.h" #include "device-enumerator-private.h" #include "device-private.h" #include "device-util.h" diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index c8c23e811ad..ff4625c3be0 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -14,6 +14,7 @@ #include "sd-device.h" +#include "ansi-color.h" #include "device-private.h" #include "device-util.h" #include "format-util.h" From 061b445828fbba3ed9348453c515d73624766815 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:28:29 +0200 Subject: [PATCH 27/46] terminal-util: simplify terminal_set_size_fd() a tiny bit --- src/basic/terminal-util.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index ffd687c8984..4c9816d9698 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -908,13 +908,18 @@ unsigned lines(void) { int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols) { struct winsize ws; + assert(fd >= 0); + + if (!ident) + ident = "TTY"; + if (rows == UINT_MAX && cols == UINT_MAX) return 0; if (ioctl(fd, TIOCGWINSZ, &ws) < 0) return log_debug_errno(errno, "TIOCGWINSZ ioctl for getting %s size failed, not setting terminal size: %m", - ident ?: "TTY"); + ident); if (rows == UINT_MAX) rows = ws.ws_row; @@ -933,7 +938,7 @@ int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols ws.ws_col = cols; if (ioctl(fd, TIOCSWINSZ, &ws) < 0) - return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident ?: "TTY"); + return log_debug_errno(errno, "TIOCSWINSZ ioctl for setting %s size failed: %m", ident); return 0; } From 7147e10c9e316b9345246b00a6b3ddf93e45cbc0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:28:46 +0200 Subject: [PATCH 28/46] terminal-util: move acquire_terminal() and AcquireTerminalFlags back together in header file --- src/basic/terminal-util.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 16f749f75cc..11aab24acb5 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -50,6 +50,9 @@ typedef enum AcquireTerminalFlags { ACQUIRE_TERMINAL_PERMISSIVE = 1 << 2, } AcquireTerminalFlags; +int acquire_terminal(const char *name, AcquireTerminalFlags flags, usec_t timeout); +int release_terminal(void); + /* Limits the use of ANSI colors to a subset. */ typedef enum ColorMode { COLOR_OFF, /* No colors, monochrome output. */ @@ -63,9 +66,6 @@ typedef enum ColorMode { const char* color_mode_to_string(ColorMode m) _const_; ColorMode color_mode_from_string(const char *s) _pure_; -int acquire_terminal(const char *name, AcquireTerminalFlags flags, usec_t timeout); -int release_terminal(void); - int terminal_vhangup_fd(int fd); int terminal_set_size_fd(int fd, const char *ident, unsigned rows, unsigned cols); From 45d785dfc03f5b7ec62faf69e6d7ff18c807b93d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 17:45:34 +0200 Subject: [PATCH 29/46] terminal-util: refactor vt_disallocate() Numerous fixes: 1. use vtnr_from_tty() to parse out VT number from tty path 2. open tty for write only when we want to output just ansi sequences 3. open tty in asynchronous mode, and apply a timeout, just to be safe 4. propagate error from writing (most callers ignore it anyway, might as well pass it along correctly) --- src/basic/terminal-util.c | 59 +++++++++++++-------------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 4c9816d9698..d8253037ec4 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -433,60 +433,39 @@ int terminal_vhangup_fd(int fd) { return RET_NERRNO(ioctl(fd, TIOCVHANGUP)); } -int vt_disallocate(const char *name) { - const char *e; - int r; +int vt_disallocate(const char *tty_path) { + assert(tty_path); - /* Deallocate the VT if possible. If not possible - * (i.e. because it is the active one), at least clear it - * entirely (including the scrollback buffer). */ + /* Deallocate the VT if possible. If not possible (i.e. because it is the active one), at least clear + * it entirely (including the scrollback buffer). */ - e = path_startswith(name, "/dev/"); - if (!e) - return -EINVAL; - - if (tty_is_vc(name)) { - _cleanup_close_ int fd = -EBADF; - unsigned u; - const char *n; - - n = startswith(e, "tty"); - if (!n) - return -EINVAL; - - r = safe_atou(n, &u); - if (r < 0) - return r; - - if (u <= 0) - return -EINVAL; - - /* Try to deallocate */ - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + int ttynr = vtnr_from_tty(tty_path); + if (ttynr > 0) { + _cleanup_close_ int fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) return fd; - r = ioctl(fd, VT_DISALLOCATE, u); - if (r >= 0) + /* Try to deallocate */ + if (ioctl(fd, VT_DISALLOCATE, ttynr) >= 0) return 0; if (errno != EBUSY) return -errno; } - /* So this is not a VT (in which case we cannot deallocate it), - * or we failed to deallocate. Let's at least clear the screen. */ + /* So this is not a VT (in which case we cannot deallocate it), or we failed to deallocate. Let's at + * least clear the screen. */ - _cleanup_close_ int fd2 = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + _cleanup_close_ int fd2 = open_terminal(tty_path, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (fd2 < 0) return fd2; - (void) loop_write(fd2, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ - "\033c", /* reset to initial state */ - SIZE_MAX); - return 0; + return loop_write_full(fd2, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */ + "\033c", /* reset to initial state */ + SIZE_MAX, + 100 * USEC_PER_MSEC); } static int vt_default_utf8(void) { From 967bcc6e267aee6c95d9a6603b1c0a2ba27e1d1b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 15 Jul 2024 11:48:48 +0200 Subject: [PATCH 30/46] tree-wide: reset stdout not stdin We nowadays reset TTYs by writing ANSI sequences to them. This can only work if we operate on an *output* fd, not an input fd. Hence switch various cases where we erroneously used an input fd to use an output fd instead. --- src/core/execute.c | 4 ++-- src/firstboot/firstboot.c | 2 +- src/home/homectl.c | 2 +- src/test/test-terminal-util.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index 7590943179b..6ed2f5a0f9d 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -147,8 +147,8 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) const char *path = exec_context_tty_path(context); - if (p && p->stdin_fd >= 0 && isatty_safe(p->stdin_fd)) - fd = p->stdin_fd; + if (p && p->stdout_fd >= 0 && isatty_safe(p->stdout_fd)) + fd = p->stdout_fd; else if (path && (context->tty_path || is_terminal_input(context->std_input) || is_terminal_output(context->std_output) || is_terminal_output(context->std_error))) { fd = _fd = open_terminal(path, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 615142cc1ed..86aa8e8c181 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -133,7 +133,7 @@ static void print_welcome(int rfd) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); if (colors_enabled()) printf("\nWelcome to your new installation of \x1B[%sm%s\x1B[0m!\n", ac, pn); diff --git a/src/home/homectl.c b/src/home/homectl.c index c64354141c5..8022694f704 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2438,7 +2438,7 @@ static int create_interactively(void) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); for (;;) { username = mfree(username); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index e7cfeab6d00..68b9cbe6062 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -286,7 +286,7 @@ TEST(get_color_mode) { TEST(terminal_reset_defensive) { int r; - r = terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ false); + r = terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); if (r < 0) log_notice_errno(r, "Failed to reset terminal: %m"); } From 3f2e8e951ace01b2f7da6ac45bd24e4c7a73eb91 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:29:15 +0200 Subject: [PATCH 31/46] execute: also hook up ansi-seq-based terminal size determination with exec_context_determine_size() And while we are at it, merge exec_context_determine_tty_size() + exec_context_apply_tty_size(). Let's simplify things, and merge the two funcs, since the latter just does one more call. At the same time, let's make sure we actually allow passing separate input/output fds. --- src/core/exec-invoke.c | 6 +++--- src/core/execute.c | 46 +++++++++++++++++++++++------------------- src/core/execute.h | 2 +- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index f8c0a811538..9489fc23096 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -360,7 +360,7 @@ static int setup_input( if (context->tty_reset) (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ true); - (void) exec_context_apply_tty_size(context, STDIN_FILENO, /* tty_path= */ NULL); + (void) exec_context_apply_tty_size(context, STDIN_FILENO, STDIN_FILENO, /* tty_path= */ NULL); } return STDIN_FILENO; @@ -389,7 +389,7 @@ static int setup_input( if (tty_fd < 0) return tty_fd; - r = exec_context_apply_tty_size(context, tty_fd, tty_path); + r = exec_context_apply_tty_size(context, tty_fd, tty_fd, tty_path); if (r < 0) return r; @@ -679,7 +679,7 @@ static int setup_confirm_stdio( if (r < 0) return r; - r = exec_context_apply_tty_size(context, fd, vc); + r = exec_context_apply_tty_size(context, fd, fd, vc); if (r < 0) return r; diff --git a/src/core/execute.c b/src/core/execute.c index 6ed2f5a0f9d..e821133eea1 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -99,46 +99,50 @@ const char* exec_context_tty_path(const ExecContext *context) { return "/dev/console"; } -static void exec_context_determine_tty_size( +int exec_context_apply_tty_size( const ExecContext *context, - const char *tty_path, - unsigned *ret_rows, - unsigned *ret_cols) { + int input_fd, + int output_fd, + const char *tty_path) { unsigned rows, cols; + int r; assert(context); - assert(ret_rows); - assert(ret_cols); + assert(input_fd >= 0); + assert(output_fd >= 0); + + if (!isatty_safe(output_fd)) + return 0; if (!tty_path) tty_path = exec_context_tty_path(context); + /* Preferably use explicitly configured data */ rows = context->tty_rows; cols = context->tty_cols; + /* Fill in data from kernel command line if anything is unspecified */ if (tty_path && (rows == UINT_MAX || cols == UINT_MAX)) (void) proc_cmdline_tty_size( tty_path, rows == UINT_MAX ? &rows : NULL, cols == UINT_MAX ? &cols : NULL); - *ret_rows = rows; - *ret_cols = cols; + /* If we got nothing so far and we are talking to a physical device, and the TTY reset logic is on, + * then let's query dimensions from the ANSI driver. */ + if (rows == UINT_MAX && cols == UINT_MAX && + context->tty_reset && + terminal_is_pty_fd(output_fd) == 0 && + isatty_safe(input_fd)) { + r = terminal_get_size_by_dsr(input_fd, output_fd, &rows, &cols); + if (r < 0) + log_debug_errno(r, "Failed to get terminal size by DSR, ignoring: %m"); + } + + return terminal_set_size_fd(output_fd, tty_path, rows, cols); } -int exec_context_apply_tty_size( - const ExecContext *context, - int tty_fd, - const char *tty_path) { - - unsigned rows, cols; - - exec_context_determine_tty_size(context, tty_path, &rows, &cols); - - return terminal_set_size_fd(tty_fd, tty_path, rows, cols); - } - void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { _cleanup_close_ int _fd = -EBADF, lock_fd = -EBADF; int fd; @@ -174,7 +178,7 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) if (context->tty_reset) (void) terminal_reset_defensive(fd, /* switch_to_text= */ true); - (void) exec_context_apply_tty_size(context, fd, path); + (void) exec_context_apply_tty_size(context, fd, fd, path); if (context->tty_vt_disallocate && path) (void) vt_disallocate(path); diff --git a/src/core/execute.h b/src/core/execute.h index 0414fbad229..b801bfe8532 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -526,7 +526,7 @@ int exec_context_get_clean_directories(ExecContext *c, char **prefix, ExecCleanM int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret); const char* exec_context_tty_path(const ExecContext *context); -int exec_context_apply_tty_size(const ExecContext *context, int tty_fd, const char *tty_path); +int exec_context_apply_tty_size(const ExecContext *context, int input_fd, int output_fd, const char *tty_path); void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p); uint64_t exec_context_get_rlimit(const ExecContext *c, const char *name); From 83e5672f908e02055e21cc8e40703007efb7bf7f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:43:40 +0200 Subject: [PATCH 32/46] exec-invoke: save original stdin/stdout with O_CLOEXEC set We turn off the flag anyway when we install them back as stdin/stdout later (via dup2()). let's hence follow our usual rules, and turn on O_CLOEXEC. --- src/core/exec-invoke.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 9489fc23096..5a47c336c0b 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -659,11 +659,11 @@ static int setup_confirm_stdio( assert(ret_saved_stdin); assert(ret_saved_stdout); - saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); + saved_stdin = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, 3); if (saved_stdin < 0) return -errno; - saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); + saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); if (saved_stdout < 0) return -errno; From 85f3957072071f0135f335d1631d2e2fc26e2659 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:44:31 +0200 Subject: [PATCH 33/46] exec-invoke: handle errno log message writing in write_confirm_error_fd() like we usually do --- src/core/exec-invoke.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 5a47c336c0b..7d8270bc1c8 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -694,15 +694,16 @@ static int setup_confirm_stdio( } static void write_confirm_error_fd(int err, int fd, const char *unit_id) { - assert(err < 0); + assert(err != 0); + assert(fd >= 0); assert(unit_id); - if (err == -ETIMEDOUT) + errno = abs(err); + + if (errno == ETIMEDOUT) dprintf(fd, "Confirmation question timed out for %s, assuming positive response.\n", unit_id); - else { - errno = -err; - dprintf(fd, "Couldn't ask confirmation for %s: %m, assuming positive response.\n", unit_id); - } + else + dprintf(fd, "Couldn't ask confirmation for %s, assuming positive response: %m\n", unit_id); } static void write_confirm_error(int err, const char *vc, const char *unit_id) { From 27c067ceeb6a6cfdaf40d4d65a82087d21154b26 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 16:28:08 +0200 Subject: [PATCH 34/46] execute: reorder "destructive" tty reset operations Let's make sure to first issue the non-destructive operations, then issue the hangup (for which we need the fd), then try to disallocate the device (for which we don't need it anymore). --- src/core/execute.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/execute.c b/src/core/execute.c index e821133eea1..8982af10aa2 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -145,7 +145,7 @@ int exec_context_apply_tty_size( void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { _cleanup_close_ int _fd = -EBADF, lock_fd = -EBADF; - int fd; + int fd, r; assert(context); @@ -172,13 +172,19 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) else if (lock_fd < 0) log_warning_errno(lock_fd, "Failed to lock /dev/console, proceeding without lock: %m"); - if (context->tty_vhangup) - (void) terminal_vhangup_fd(fd); - if (context->tty_reset) (void) terminal_reset_defensive(fd, /* switch_to_text= */ true); - (void) exec_context_apply_tty_size(context, fd, fd, path); + r = exec_context_apply_tty_size(context, fd, fd, path); + if (r < 0) + log_debug_errno(r, "Failed to configure TTY dimensions, ignoring: %m"); + + if (context->tty_vhangup) + (void) terminal_vhangup_fd(fd); + + /* We don't need the fd anymore now, and it potentially points to a hungup TTY anyway, let's close it + * hence. */ + _fd = safe_close(_fd); if (context->tty_vt_disallocate && path) (void) vt_disallocate(path); From 628c214656fada4228b62b1546220ac781002897 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 11:29:37 +0200 Subject: [PATCH 35/46] exec-invoke: move terminal initialization a bit It's a bit confusing, but we actually initialize the terminal twice for each service, potentially. One earlier time, where we might end up firing vhangup() and vt_disallocate(), which is a pretty brutal way to reset things, by disconnecting and possibly invalidating the tty completely. When we do this we do not keep any fd open afterwards, since it quite likely points to a dead connection of a tty. The 2nd time we initialize things when we actually want to use it. The first initialization is hence "destructive" (killing any left-overs from previous uses) the 2nd one "constructive" (preparing things for our new use), if you so will. Let's document this distinction in comments, and let's also move both initializations to exec_invoke(), so that they are easier to see in their symmetric behaviour. Moreover, let's run the tty initialization after we opened both input and output, since we need both for doing the fancy dimension auto init stuff now. Oh, and of course, one thing to mention: we nowadays initialize terminals both with ioctl() and with ansi sequences. But the latter means we need an fd that is open for *write* (since we are *writing* those ansi sequences to the tty). Hence, resetting via the input fd is conceptually wrong, it worked only so far if we had O_RDWR open mode selected) --- src/core/exec-invoke.c | 60 +++++++++++++++++++++++++++++++++--------- src/core/execute.c | 5 ++++ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 7d8270bc1c8..08f9188ec7d 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -32,6 +32,7 @@ #include "chown-recursive.h" #include "copy.h" #include "data-fd-util.h" +#include "dev-setup.h" #include "env-util.h" #include "escape.h" #include "exec-credential.h" @@ -353,16 +354,10 @@ static int setup_input( if (dup2(params->stdin_fd, STDIN_FILENO) < 0) return -errno; - /* Try to make this the controlling tty, if it is a tty, and reset it */ - if (isatty(STDIN_FILENO)) { + /* Try to make this the controlling tty, if it is a tty */ + if (isatty(STDIN_FILENO)) (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); - if (context->tty_reset) - (void) terminal_reset_defensive(STDIN_FILENO, /* switch_to_text= */ true); - - (void) exec_context_apply_tty_size(context, STDIN_FILENO, STDIN_FILENO, /* tty_path= */ NULL); - } - return STDIN_FILENO; } @@ -389,10 +384,6 @@ static int setup_input( if (tty_fd < 0) return tty_fd; - r = exec_context_apply_tty_size(context, tty_fd, tty_fd, tty_path); - if (r < 0) - return r; - r = move_fd(tty_fd, STDIN_FILENO, /* cloexec= */ false); if (r < 0) return r; @@ -554,7 +545,6 @@ static int setup_output( if (is_terminal_input(i)) return RET_NERRNO(dup2(STDIN_FILENO, fileno)); - /* We don't reset the terminal if this is just about output */ return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); case EXEC_OUTPUT_KMSG: @@ -4032,6 +4022,39 @@ static int send_handoff_timestamp( return 1; } +static void prepare_terminal( + const ExecContext *context, + ExecParameters *p) { + + _cleanup_close_ int lock_fd = -EBADF; + + /* This is the "constructive" reset, i.e. is about preparing things for our invocation rather than + * cleaning up things from older invocations. */ + + assert(context); + assert(p); + + /* We only try to reset things if we there's the chance our stdout points to a TTY */ + if (!(is_terminal_output(context->std_output) || + (context->std_output == EXEC_OUTPUT_INHERIT && is_terminal_input(context->std_input)) || + context->std_output == EXEC_OUTPUT_NAMED_FD || + p->stdout_fd >= 0)) + return; + + if (context->tty_reset) { + /* When we are resetting the TTY, then let's create a lock first, to synchronize access. This + * in particular matters as concurrent resets and the TTY size ANSI DSR logic done by the + * exec_context_apply_tty_size() below might interfere */ + lock_fd = lock_dev_console(); + if (lock_fd < 0) + log_exec_debug_errno(context, p, lock_fd, "Failed to lock /dev/console, ignoring: %m"); + + (void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); + } + + (void) exec_context_apply_tty_size(context, STDIN_FILENO, STDOUT_FILENO, /* tty_path= */ NULL); +} + int exec_invoke( const ExecCommand *command, const ExecContext *context, @@ -4199,6 +4222,11 @@ int exec_invoke( return log_exec_error_errno(context, params, errno, "Failed to create new process session: %m"); } + /* Now, reset the TTY associated to this service "destructively" (i.e. possibly even hang up or + * disallocate the VT), to get rid of any prior uses of the device. Note that we do not keep any fd + * open here, hence some of the settings made here might vanish again, depending on the TTY driver + * used. A 2nd ("constructive") initialization after we opened the input/output fds we actually want + * will fix this. */ exec_context_tty_reset(context, params); if (params->shall_confirm_spawn && exec_context_shall_confirm_spawn(context)) { @@ -4382,6 +4410,12 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to set up standard error output: %m"); } + /* Now that stdin/stdout are definiely opened, properly initialize it with our desired + * settings. Note: this is a "constructive" reset, it prepares things for us to use. This is + * different from the "destructive" TTY reset further up. Also note: we apply this on stdin/stdout in + * case this is a tty, regardless if we opened it ourselves or got it passed in pre-opened. */ + prepare_terminal(context, params); + if (context->oom_score_adjust_set) { /* When we can't make this change due to EPERM, then let's silently skip over it. User * namespaces prohibit write access to this file, and we shouldn't trip up over that. */ diff --git a/src/core/execute.c b/src/core/execute.c index 8982af10aa2..dc418fd14c2 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -149,6 +149,11 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) assert(context); + /* Note that this is potentially a "destructive" reset of a TTY device. It's about getting rid of the + * remains of previous uses of the TTY. It's *not* about getting things set up for coming uses. We'll + * potentially invalidate the TTY here through hangups or VT disallocations, and hence do not keep a + * continous fd open. */ + const char *path = exec_context_tty_path(context); if (p && p->stdout_fd >= 0 && isatty_safe(p->stdout_fd)) From 56ea3c262cd08cf9c2264ee1009326ff9a25a975 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 11 Jul 2024 16:18:49 +0200 Subject: [PATCH 36/46] units: bring agetty command lines back into sync Let's always rely on our own TTY reset logic and tty disallocation/clear screen logic, thus always pass --noclear and --noreset. Also, bring the list of baud rates to try into sync for console-getty and serial-getty (the former might or might not be connected to rs232, we can't know, hence assume the worst, and copy what serial-getty@.service does) --- units/console-getty.service.in | 7 ++++--- units/container-getty@.service.in | 7 ++++--- units/getty@.service.in | 3 +-- units/serial-getty@.service.in | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/units/console-getty.service.in b/units/console-getty.service.in index d64112be5ef..2de31dd01aa 100644 --- a/units/console-getty.service.in +++ b/units/console-getty.service.in @@ -20,9 +20,10 @@ Before=getty.target ConditionPathExists=/dev/console [Service] -# The '-o' option value tells agetty to replace 'login' arguments with an option to preserve environment (-p), -# followed by '--' for safety, and then the entered username. -ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear --keep-baud - 115200,38400,9600 $TERM +# The '-o' option value tells agetty to replace 'login' arguments with an +# option to preserve environment (-p), followed by '--' for safety, and then +# the entered username. +ExecStart=-/sbin/agetty -o '-p -- \\u' --noreset --noclear --keep-baud 115200,57600,38400,9600 - ${TERM} Type=idle Restart=always UtmpIdentifier=cons diff --git a/units/container-getty@.service.in b/units/container-getty@.service.in index 8847d735fbd..7e277a49bc5 100644 --- a/units/container-getty@.service.in +++ b/units/container-getty@.service.in @@ -25,9 +25,10 @@ Conflicts=rescue.service Before=rescue.service [Service] -# The '-o' option value tells agetty to replace 'login' arguments with an option to preserve environment (-p), -# followed by '--' for safety, and then the entered username. -ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear - $TERM +# The '-o' option value tells agetty to replace 'login' arguments with an +# option to preserve environment (-p), followed by '--' for safety, and then +# the entered username. +ExecStart=-/sbin/agetty -o '-p -- \\u' --noreset --noclear - ${TERM} Type=idle Restart=always RestartSec=0 diff --git a/units/getty@.service.in b/units/getty@.service.in index 80b8f3e9228..ce8db9764e0 100644 --- a/units/getty@.service.in +++ b/units/getty@.service.in @@ -34,11 +34,10 @@ Before=rescue.service ConditionPathExists=/dev/tty0 [Service] -# the VT is cleared by TTYVTDisallocate # The '-o' option value tells agetty to replace 'login' arguments with an # option to preserve environment (-p), followed by '--' for safety, and then # the entered username. -ExecStart=-/sbin/agetty -o '-p -- \\u' --noclear - $TERM +ExecStart=-/sbin/agetty -o '-p -- \\u' --noreset --noclear - ${TERM} Type=idle Restart=always RestartSec=0 diff --git a/units/serial-getty@.service.in b/units/serial-getty@.service.in index 6bf101eac9a..5669b19aff1 100644 --- a/units/serial-getty@.service.in +++ b/units/serial-getty@.service.in @@ -33,7 +33,7 @@ Before=rescue.service # The '-o' option value tells agetty to replace 'login' arguments with an # option to preserve environment (-p), followed by '--' for safety, and then # the entered username. -ExecStart=-/sbin/agetty -o '-p -- \\u' --keep-baud 115200,57600,38400,9600 - $TERM +ExecStart=-/sbin/agetty -o '-p -- \\u' --noreset --noclear --keep-baud 115200,57600,38400,9600 - ${TERM} Type=idle Restart=always UtmpIdentifier=%I From c06b84d816fdeb6a26691b6f3504f541644edb6f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Jul 2024 06:06:11 +0200 Subject: [PATCH 37/46] man: clarify what TTYReset= and TTYVTDisallocate= do and do not do regarding screen clearing --- man/systemd.exec.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 7a2fc76b656..c79cf674458 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -3322,7 +3322,8 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX TTYReset= Reset the terminal device specified with TTYPath= before and after - execution. Defaults to no. + execution. This does not erase the screen (see TTYVTDisallocate= below for + that). Defaults to no. @@ -3333,11 +3334,12 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX - TTYRows= TTYColumns= + TTYRows= Configure the size of the TTY specified with TTYPath=. If unset or - set to the empty string, the kernel default is used. + set to the empty string, it is attempted to retrieve the dimensions of the terminal screen via ANSI + sequences, and if that fails the kernel defaults (typically 80x24) are used. @@ -3345,9 +3347,10 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX TTYVTDisallocate= - If the terminal device specified with TTYPath= is a virtual console - terminal, try to deallocate the TTY before and after execution. This ensures that the screen and scrollback - buffer is cleared. Defaults to no. + If the terminal device specified with TTYPath= is a virtual + console terminal, try to deallocate the TTY before and after execution. This ensures that the screen + and scrollback buffer is cleared. If the terminal device is of any other type of TTY an attempt is + made to clear the screen via ANSI sequences. Defaults to no. From 4a24cc859f2fc9834ce2af633d5b4c582e5627f7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 12:20:32 +0200 Subject: [PATCH 38/46] terminal-util: move lock_dev_console() here It doesn't really make sense to have that in dev-setup.c, which is mostly about setting up /dev/, creating device nodes and stuff. let's move it to the other stuff that deals with /dev/console's peculiarities. --- src/basic/terminal-util.c | 17 +++++++++++++++++ src/basic/terminal-util.h | 1 + src/core/exec-invoke.c | 1 - src/core/execute.c | 1 - src/shared/dev-setup.c | 17 ----------------- src/shared/dev-setup.h | 2 -- 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d8253037ec4..6d3a0f8a5c7 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -618,6 +618,23 @@ void reset_dev_console_fd(int fd, bool switch_to_text) { log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m"); } +int lock_dev_console(void) { + _cleanup_close_ int fd = -EBADF; + int r; + + /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some + * pty in /dev/console, in which case it should be fine to lock the target TTY. */ + fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return fd; + + r = lock_generic(fd, LOCK_BSD, LOCK_EX); + if (r < 0) + return r; + + return TAKE_FD(fd); +} + int make_console_stdio(void) { int fd, r; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 11aab24acb5..3cf0ff98485 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -88,6 +88,7 @@ int vtnr_from_tty(const char *tty); const char* default_term_for_tty(const char *tty); void reset_dev_console_fd(int fd, bool switch_to_text); +int lock_dev_console(void); int make_console_stdio(void); int fd_columns(int fd); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 08f9188ec7d..2521e940c9b 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -32,7 +32,6 @@ #include "chown-recursive.h" #include "copy.h" #include "data-fd-util.h" -#include "dev-setup.h" #include "env-util.h" #include "escape.h" #include "exec-credential.h" diff --git a/src/core/execute.c b/src/core/execute.c index dc418fd14c2..cdc12779564 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -25,7 +25,6 @@ #include "cgroup-setup.h" #include "constants.h" #include "cpu-set-util.h" -#include "dev-setup.h" #include "env-file.h" #include "env-util.h" #include "errno-list.h" diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index 4b4b62565c5..eb72793c04b 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -18,23 +18,6 @@ #include "umask-util.h" #include "user-util.h" -int lock_dev_console(void) { - _cleanup_close_ int fd = -EBADF; - int r; - - /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some - * pty in /dev/console, in which case it should be fine to lock the target TTY. */ - fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return fd; - - r = lock_generic(fd, LOCK_BSD, LOCK_EX); - if (r < 0) - return r; - - return TAKE_FD(fd); -} - int dev_setup(const char *prefix, uid_t uid, gid_t gid) { static const char symlinks[] = "-/proc/kcore\0" "/dev/core\0" diff --git a/src/shared/dev-setup.h b/src/shared/dev-setup.h index 5339bc4e5e9..92ba6cf7640 100644 --- a/src/shared/dev-setup.h +++ b/src/shared/dev-setup.h @@ -3,8 +3,6 @@ #include -int lock_dev_console(void); - int dev_setup(const char *prefix, uid_t uid, gid_t gid); int make_inaccessible_nodes(const char *parent_dir, uid_t uid, gid_t gid); From dffbe1d152d3ddf5abd747d3792dc1fd18e84775 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 12:28:53 +0200 Subject: [PATCH 39/46] terminal-util: teach resolve_dev_console() to deal correctly with /dev/console being a symlink /dev/console is sometimes a symlink in container managers. Let's handle that correctly, and resolve the symlink, and not consider the data from /sys/ in that case. --- src/basic/terminal-util.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 6d3a0f8a5c7..93f356bde06 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -22,6 +22,7 @@ #include "alloc-util.h" #include "ansi-color.h" +#include "chase.h" #include "constants.h" #include "devnum-util.h" #include "env-util.h" @@ -698,24 +699,37 @@ int vtnr_from_tty(const char *tty) { } int resolve_dev_console(char **ret) { - _cleanup_free_ char *active = NULL; - char *tty; int r; assert(ret); - /* Resolve where /dev/console is pointing to, if /sys is actually ours (i.e. not read-only-mounted which is a - * sign for container setups) */ + /* Resolve where /dev/console is pointing to. If /dev/console is a symlink (like in container + * managers), we'll just resolve the symlink. If it's a real device node, we'll use if + * /sys/class/tty/tty0/active, but only if /sys/ is actually ours (i.e. not read-only-mounted which + * is a sign for container setups). */ - if (path_is_read_only_fs("/sys") > 0) + _cleanup_free_ char *chased = NULL; + r = chase("/dev/console", /* root= */ NULL, /* chase_flags= */ 0, &chased, /* ret_fd= */ NULL); + if (r < 0) + return r; + if (!path_equal(chased, "/dev/console")) { + *ret = TAKE_PTR(chased); + return 0; + } + + r = path_is_read_only_fs("/sys"); + if (r < 0) + return r; + if (r > 0) return -ENOMEDIUM; + _cleanup_free_ char *active = NULL; r = read_one_line_file("/sys/class/tty/console/active", &active); if (r < 0) return r; /* If multiple log outputs are configured the last one is what /dev/console points to */ - tty = strrchr(active, ' '); + const char *tty = strrchr(active, ' '); if (tty) tty++; else From 2cd19499a0ab85e2c48e397a53a1bad0f025c31c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 15:06:27 +0200 Subject: [PATCH 40/46] tree-wide: acquire /dev/console lock around any attempts to reset TTY --- src/basic/terminal-util.c | 14 ++++++++++++++ src/basic/terminal-util.h | 1 + src/core/exec-invoke.c | 4 ++++ src/firstboot/firstboot.c | 2 +- src/home/homectl.c | 2 +- .../tty-ask-password-agent.c | 2 +- 6 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 93f356bde06..058f8df1097 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -599,6 +599,10 @@ void reset_dev_console_fd(int fd, bool switch_to_text) { assert(fd >= 0); + _cleanup_close_ int lock_fd = lock_dev_console(); + if (lock_fd < 0) + log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m"); + r = terminal_reset_ioctl(fd, switch_to_text); if (r < 0) log_warning_errno(r, "Failed to reset /dev/console, ignoring: %m"); @@ -1592,6 +1596,16 @@ int terminal_reset_defensive(int fd, bool switch_to_text) { return r; } +int terminal_reset_defensive_locked(int fd, bool switch_to_text) { + assert(fd >= 0); + + _cleanup_close_ int lock_fd = lock_dev_console(); + if (lock_fd < 0) + log_debug_errno(lock_fd, "Failed to acquire lock for /dev/console, ignoring: %m"); + + return terminal_reset_defensive(fd, switch_to_text); +} + void termios_disable_echo(struct termios *termios) { assert(termios); diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 3cf0ff98485..84d4731ea8b 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -30,6 +30,7 @@ bool isatty_safe(int fd); int terminal_reset_defensive(int fd, bool switch_to_text); +int terminal_reset_defensive_locked(int fd, bool switch_to_text); int terminal_set_cursor_position(int fd, unsigned row, unsigned column); diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2521e940c9b..4c6f35827cb 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -660,6 +660,10 @@ static int setup_confirm_stdio( if (fd < 0) return fd; + _cleanup_close_ int lock_fd = lock_dev_console(); + if (lock_fd < 0) + log_debug_errno(lock_fd, "Failed to lock /dev/console, ignoring: %m"); + r = chown_terminal(fd, getuid()); if (r < 0) return r; diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 86aa8e8c181..c70bfa468fd 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -133,7 +133,7 @@ static void print_welcome(int rfd) { pn = os_release_pretty_name(pretty_name, os_name); ac = isempty(ansi_color) ? "0" : ansi_color; - (void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false); if (colors_enabled()) printf("\nWelcome to your new installation of \x1B[%sm%s\x1B[0m!\n", ac, pn); diff --git a/src/home/homectl.c b/src/home/homectl.c index 8022694f704..a71548439e2 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2438,7 +2438,7 @@ static int create_interactively(void) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) terminal_reset_defensive(STDOUT_FILENO, /* switch_to_text= */ false); + (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false); for (;;) { username = mfree(username); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index b411c9c309d..4b1e8487490 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -150,7 +150,7 @@ static int agent_ask_password_tty( if (tty_fd < 0) return log_error_errno(tty_fd, "Failed to acquire %s: %m", con); - (void) terminal_reset_defensive(tty_fd, /* switch_to_text= */ true); + (void) terminal_reset_defensive_locked(tty_fd, /* switch_to_text= */ true); log_info("Starting password query on %s.", con); } From e2d66781ee233198c3610e4fc1b8f6266508b529 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 15:06:48 +0200 Subject: [PATCH 41/46] exec-invoke: user EBADF where appropriate --- src/core/exec-invoke.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 4c6f35827cb..ea85c36581a 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -744,7 +744,7 @@ static bool confirm_spawn_disabled(void) { } static int ask_for_confirmation(const ExecContext *context, const ExecParameters *params, const char *cmdline) { - int saved_stdout = -1, saved_stdin = -1, r; + int saved_stdout = -EBADF, saved_stdin = -EBADF, r; _cleanup_free_ char *e = NULL; char c; From abe8e99ee654241c0c0d5003dc64c944c749125e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 15:56:15 +0200 Subject: [PATCH 42/46] terminal-util: try to avoid reading more from terminal than we need in get_default_background_color() --- src/basic/terminal-util.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 058f8df1097..635b5f62ee4 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1830,11 +1830,11 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret goto finish; usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC); - char buf[256]; + char buf[STRLEN("\x1B]11;rgb:0/0/0\x07")]; /* shortest possible reply */ size_t buf_full = 0; BackgroundColorContext context = {}; - for (;;) { + for (bool first = true;; first = false) { if (buf_full == 0) { usec_t n = now(CLOCK_MONOTONIC); @@ -1851,7 +1851,10 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret goto finish; } - ssize_t l = read(STDIN_FILENO, buf, sizeof(buf)); + /* On the first try, read multiple characters, i.e. the shortest valid + * reply. Afterwards read byte-wise, since we don't want to read too much, and + * unnecessarily drop too many characters from the input queue. */ + ssize_t l = read(STDIN_FILENO, buf, first ? sizeof(buf) : 1); if (l < 0) { r = -errno; goto finish; From ad2fa21f833dff9eb81f8febb6bfceeaefa9326e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 17:57:12 +0200 Subject: [PATCH 43/46] terminal-util: extend timeout on background color request I managed to hit the timeout a couple of times inside of slow qemu. Let's increase it a bit to 1/3s --- src/basic/terminal-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 635b5f62ee4..df091c36a96 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1829,7 +1829,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret if (r < 0) goto finish; - usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC); + usec_t end = usec_add(now(CLOCK_MONOTONIC), 333 * USEC_PER_MSEC); char buf[STRLEN("\x1B]11;rgb:0/0/0\x07")]; /* shortest possible reply */ size_t buf_full = 0; BackgroundColorContext context = {}; From 0ea4198f0a9ef4aa2226b437bfa6e9a741ea99b0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 17:45:26 +0200 Subject: [PATCH 44/46] terminal-util: add recognizable error if cols/rows of tty are initially not initialized Various tty types come up with cols/rows not initialized (i.e. set to zero). Let's detect these cases, and return a better error than EIO, simply to make things easier to debug. --- src/basic/terminal-util.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index df091c36a96..c2ff3ca85a8 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -855,7 +855,7 @@ int fd_columns(int fd) { return -errno; if (ws.ws_col <= 0) - return -EIO; + return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */ return ws.ws_col; } @@ -892,7 +892,7 @@ int fd_lines(int fd) { return -errno; if (ws.ws_row <= 0) - return -EIO; + return -ENODATA; /* some tty types come up with invalid row/column initially, return a recognizable error for that */ return ws.ws_row; } From b4112281990839a28d56684c75a9b7d3631291cf Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 17:47:41 +0200 Subject: [PATCH 45/46] main: set $COLUMNS/$ROWS for PID 1 based on /dev/console data In PID 1 we write status information to /dev/console regularly, but we cannot keep it open continously, due to the kernel's SAK logic (which would kill PID 1 if user hits SAK). But closing/reopening it all the time really sucks for tty types that have no window size management (such as serial terminals/hvc0 and suchlike), because it also means the TTY is fully closed most of the time, and that resets the window sizes to 0/0. Now, we reinitialize the window size on every reopen, but that is a bit expensive for simple status output. Hence, cache the window size in the usualy $COLUMNS/$ROWS environment variables. We don't inherit these to our payloads anyway, hence these are free to us to use. --- src/core/main.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/core/main.c b/src/core/main.c index bc2476ed3a5..e5c2dc914e5 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -204,6 +204,43 @@ static int manager_find_user_config_paths(char ***ret_files, char ***ret_dirs) { return 0; } +static int save_console_winsize_in_environment(int tty_fd) { + int r; + + assert(tty_fd >= 0); + + struct winsize ws = {}; + if (ioctl(tty_fd, TIOCGWINSZ, &ws) < 0) { + log_debug_errno(errno, "Failed to acquire console window size, ignoring."); + goto unset; + } + + if (ws.ws_col <= 0 && ws.ws_row <= 0) { + log_debug("No console window size set, ignoring."); + goto unset; + } + + r = setenvf("COLUMNS", /* overwrite= */ true, "%u", ws.ws_col); + if (r < 0) { + log_debug_errno(r, "Failed to set $COLUMNS, ignoring: %m"); + goto unset; + } + + r = setenvf("LINES", /* overwrite= */ true, "%u", ws.ws_row); + if (r < 0) { + log_debug_errno(r, "Failed to set $LINES, ignoring: %m"); + goto unset; + } + + log_debug("Recorded console dimensions in environment: $COLUMNS=%u $LINES=%u.", ws.ws_col, ws.ws_row); + return 1; + +unset: + (void) unsetenv("COLUMNS"); + (void) unsetenv("LINES"); + return 0; +} + static int console_setup(void) { if (getpid_cached() != 1) @@ -217,6 +254,9 @@ static int console_setup(void) { /* We don't want to force text mode. Plymouth may be showing pictures already from initrd. */ reset_dev_console_fd(tty_fd, /* switch_to_text= */ false); + + save_console_winsize_in_environment(tty_fd); + return 0; } From 16044277e9b2810c04970276fda3fb052b742727 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Jul 2024 17:56:01 +0200 Subject: [PATCH 46/46] pid1: use $COLUMNS info in status_vprintf() This way, we can work around the fact that "struct winsize" for /dev/console might not be initialized the moment we open the device. --- src/core/show-status.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/show-status.c b/src/core/show-status.c index 57ad4db30b5..7c7c10f9636 100644 --- a/src/core/show-status.c +++ b/src/core/show-status.c @@ -72,8 +72,14 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format int c; c = fd_columns(fd); - if (c <= 0) - c = 80; + if (c <= 0) { + const char *env = getenv("COLUMNS"); + if (env) + (void) safe_atoi(env, &c); + + if (c <= 0) + c = 80; + } sl = status ? strlen(status_indent) : 0;