mirror of
https://github.com/systemd/systemd.git
synced 2026-08-08 17:10:55 +00:00
basic/terminal-util: use non-blocking writes when sending ANSI sequences in terminal_get_size()
terminal_get_size() writes ANSI escape sequences (CSI 18 and DSR
queries) to the output fd to determine terminal dimensions. This is
called during early boot via reset_dev_console_fd() and from service
execution contexts via exec_context_apply_tty_size().
Previously, these writes used loop_write() on a blocking fd, which
could block indefinitely if the terminal is not consuming data — for
example on a serial console with flow control asserted, or a
disconnected terminal. This is the same problem that was solved for
terminal_reset_ansi_seq() in systemd/systemd#32369 by temporarily
setting the fd to non-blocking mode with a write timeout.
Apply the same pattern here: set the output fd to non-blocking in
terminal_get_size() before issuing the queries, and restore blocking
mode afterward. Change the loop_write() calls in
terminal_query_size_by_dsr() and terminal_query_size_by_csi18() to
loop_write_full() with a 100ms timeout so writes fail gracefully
instead of hanging.
Also introduce the CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC constant for all
timeouts used across all ANSI sequence writes and reads (vt_disallocate(),
terminal_reset_ansi_seq(), and the two size query functions). 333ms
is now used for all timeouts in terminal-util.c.
Also introduce a cleanup function for resetting a fd back to blocking
mode after it was made non-blocking.
(cherry picked from commit ba10d789a8)
This commit is contained in:
committed by
Zbigniew Jędrzejewski-Szmek
parent
abca10b766
commit
ade6e7a2a3
@@ -166,6 +166,13 @@ int fd_nonblock(int fd, bool nonblock) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void nonblock_resetp(int *fd) {
|
||||
PROTECT_ERRNO;
|
||||
|
||||
if (*fd >= 0)
|
||||
(void) fd_nonblock(*fd, false);
|
||||
}
|
||||
|
||||
int stdio_disable_nonblock(void) {
|
||||
int ret = 0;
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
|
||||
int fd_nonblock(int fd, bool nonblock);
|
||||
int stdio_disable_nonblock(void);
|
||||
|
||||
void nonblock_resetp(int *fd);
|
||||
|
||||
int fd_cloexec(int fd, bool cloexec);
|
||||
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
#include "time-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
/* How much to wait for a reply to a terminal sequence */
|
||||
#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC)
|
||||
/* How much to wait when reading/writing ANSI sequences from/to the console */
|
||||
#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC (333 * USEC_PER_MSEC)
|
||||
|
||||
static volatile unsigned cached_columns = 0;
|
||||
static volatile unsigned cached_lines = 0;
|
||||
@@ -853,7 +853,7 @@ int vt_disallocate(const char *tty_path) {
|
||||
"\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */
|
||||
"\033c", /* reset to initial state */
|
||||
SIZE_MAX,
|
||||
100 * USEC_PER_MSEC);
|
||||
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
}
|
||||
|
||||
static int vt_default_utf8(void) {
|
||||
@@ -952,7 +952,8 @@ finish:
|
||||
}
|
||||
|
||||
int terminal_reset_ansi_seq(int fd) {
|
||||
int r, k;
|
||||
_cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
@@ -962,8 +963,10 @@ int terminal_reset_ansi_seq(int fd) {
|
||||
r = fd_nonblock(fd, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
|
||||
if (r > 0)
|
||||
nonblock_reset = fd;
|
||||
|
||||
k = loop_write_full(fd,
|
||||
r = loop_write_full(fd,
|
||||
"\033[!p" /* soft terminal reset */
|
||||
ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
|
||||
ANSI_NORMAL /* reset colors */
|
||||
@@ -971,17 +974,11 @@ int terminal_reset_ansi_seq(int fd) {
|
||||
"\033[1G" /* place cursor at beginning of current line */
|
||||
"\033[0J", /* erase till end of screen */
|
||||
SIZE_MAX,
|
||||
100 * USEC_PER_MSEC);
|
||||
if (k < 0)
|
||||
log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
|
||||
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %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;
|
||||
return r;
|
||||
}
|
||||
|
||||
void reset_dev_console_fd(int fd, bool switch_to_text) {
|
||||
@@ -2016,7 +2013,7 @@ int terminal_get_cursor_position(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
|
||||
size_t buf_full = 0;
|
||||
CursorPositionContext context = {};
|
||||
@@ -2315,7 +2312,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
|
||||
size_t buf_full = 0;
|
||||
BackgroundColorContext context = {};
|
||||
@@ -2386,16 +2383,17 @@ static int terminal_query_size_by_dsr(
|
||||
|
||||
/* Use DECSC/DECRC to save/restore cursor instead of querying position via DSR. This way the cursor
|
||||
* is always restored — even on timeout — and we only need one DSR response instead of two. */
|
||||
r = loop_write(output_fd,
|
||||
"\x1B" "7" /* DECSC: save cursor position */
|
||||
"\x1B[32766;32766H" /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */
|
||||
"\x1B[6n" /* DSR: request cursor position (CPR) */
|
||||
"\x1B" "8", /* DECRC: restore cursor position */
|
||||
SIZE_MAX);
|
||||
r = loop_write_full(output_fd,
|
||||
"\x1B" "7" /* DECSC: save cursor position */
|
||||
"\x1B[32766;32766H" /* CUP: position cursor far to the right and to the bottom, staying within 16bit signed range */
|
||||
"\x1B[6n" /* DSR: request cursor position (CPR) */
|
||||
"\x1B" "8", /* DECRC: restore cursor position */
|
||||
SIZE_MAX,
|
||||
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
|
||||
size_t buf_full = 0;
|
||||
CursorPositionContext context = {};
|
||||
@@ -2543,11 +2541,11 @@ static int terminal_query_size_by_csi18(
|
||||
assert(nonblock_input_fd >= 0);
|
||||
assert(output_fd >= 0);
|
||||
|
||||
r = loop_write(output_fd, CSI18_Q, SIZE_MAX);
|
||||
r = loop_write_full(output_fd, CSI18_Q, SIZE_MAX, CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
char buf[STRLEN(CSI18_R1)];
|
||||
size_t bytes = 0;
|
||||
|
||||
@@ -2598,6 +2596,7 @@ int terminal_get_size(
|
||||
_cleanup_close_ int nonblock_input_fd = -EBADF;
|
||||
struct termios old_termios = TERMIOS_NULL;
|
||||
CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
|
||||
_cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
|
||||
int r;
|
||||
|
||||
assert(try_dsr || try_csi18);
|
||||
@@ -2606,6 +2605,14 @@ int terminal_get_size(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Put the output fd in non-blocking mode with a write timeout, to avoid blocking indefinitely on
|
||||
* write if the terminal is not consuming data (e.g. serial console with flow control). */
|
||||
r = fd_nonblock(output_fd, true);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
|
||||
if (r > 0)
|
||||
nonblock_reset = output_fd;
|
||||
|
||||
/* Flush any stale input that might confuse the response parsers. */
|
||||
(void) tcflush(nonblock_input_fd, TCIFLUSH);
|
||||
|
||||
@@ -2731,7 +2738,7 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_REPLY_WAIT_USEC);
|
||||
usec_t end = usec_add(now(CLOCK_MONOTONIC), CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||
char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
|
||||
size_t bytes = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user