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.
This commit is contained in:
committed by
Daan De Meyer
parent
e98a26ddc0
commit
ba10d789a8
@@ -166,6 +166,13 @@ int fd_nonblock(int fd, bool nonblock) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nonblock_resetp(int *fd) {
|
||||||
|
PROTECT_ERRNO;
|
||||||
|
|
||||||
|
if (*fd >= 0)
|
||||||
|
(void) fd_nonblock(*fd, false);
|
||||||
|
}
|
||||||
|
|
||||||
int stdio_disable_nonblock(void) {
|
int stdio_disable_nonblock(void) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
|
|||||||
int fd_nonblock(int fd, bool nonblock);
|
int fd_nonblock(int fd, bool nonblock);
|
||||||
int stdio_disable_nonblock(void);
|
int stdio_disable_nonblock(void);
|
||||||
|
|
||||||
|
void nonblock_resetp(int *fd);
|
||||||
|
|
||||||
int fd_cloexec(int fd, bool cloexec);
|
int fd_cloexec(int fd, bool cloexec);
|
||||||
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
|
int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
|
||||||
|
|
||||||
|
|||||||
+34
-27
@@ -44,8 +44,8 @@
|
|||||||
#include "time-util.h"
|
#include "time-util.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
|
||||||
/* How much to wait for a reply to a terminal sequence */
|
/* How much to wait when reading/writing ANSI sequences from/to the console */
|
||||||
#define CONSOLE_REPLY_WAIT_USEC (333 * USEC_PER_MSEC)
|
#define CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC (333 * USEC_PER_MSEC)
|
||||||
|
|
||||||
static volatile unsigned cached_columns = 0;
|
static volatile unsigned cached_columns = 0;
|
||||||
static volatile unsigned cached_lines = 0;
|
static volatile unsigned cached_lines = 0;
|
||||||
@@ -848,7 +848,7 @@ int vt_disallocate(const char *tty_path) {
|
|||||||
"\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */
|
"\033[3J" /* clear screen including scrollback, requires Linux 2.6.40 */
|
||||||
"\033c", /* reset to initial state */
|
"\033c", /* reset to initial state */
|
||||||
SIZE_MAX,
|
SIZE_MAX,
|
||||||
100 * USEC_PER_MSEC);
|
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vt_default_utf8(void) {
|
static int vt_default_utf8(void) {
|
||||||
@@ -947,7 +947,8 @@ finish:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int terminal_reset_ansi_seq(int fd) {
|
int terminal_reset_ansi_seq(int fd) {
|
||||||
int r, k;
|
_cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
|
||||||
|
int r;
|
||||||
|
|
||||||
assert(fd >= 0);
|
assert(fd >= 0);
|
||||||
|
|
||||||
@@ -957,8 +958,10 @@ int terminal_reset_ansi_seq(int fd) {
|
|||||||
r = fd_nonblock(fd, true);
|
r = fd_nonblock(fd, true);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m");
|
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 */
|
"\033[!p" /* soft terminal reset */
|
||||||
ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
|
ANSI_OSC "104" ANSI_ST /* reset color palette via OSC 104 */
|
||||||
ANSI_NORMAL /* reset colors */
|
ANSI_NORMAL /* reset colors */
|
||||||
@@ -966,17 +969,11 @@ int terminal_reset_ansi_seq(int fd) {
|
|||||||
"\033[1G" /* place cursor at beginning of current line */
|
"\033[1G" /* place cursor at beginning of current line */
|
||||||
"\033[0J", /* erase till end of screen */
|
"\033[0J", /* erase till end of screen */
|
||||||
SIZE_MAX,
|
SIZE_MAX,
|
||||||
100 * USEC_PER_MSEC);
|
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||||
if (k < 0)
|
if (r < 0)
|
||||||
log_debug_errno(k, "Failed to reset terminal through ANSI sequences: %m");
|
log_debug_errno(r, "Failed to reset terminal through ANSI sequences: %m");
|
||||||
|
|
||||||
if (r > 0) {
|
return r;
|
||||||
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) {
|
void reset_dev_console_fd(int fd, bool switch_to_text) {
|
||||||
@@ -2009,7 +2006,7 @@ int terminal_get_cursor_position(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
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 */
|
char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
|
||||||
size_t buf_full = 0;
|
size_t buf_full = 0;
|
||||||
CursorPositionContext context = {};
|
CursorPositionContext context = {};
|
||||||
@@ -2308,7 +2305,7 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
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 */
|
char buf[STRLEN(ANSI_OSC "11;rgb:0/0/0" ANSI_ST)]; /* shortest possible reply */
|
||||||
size_t buf_full = 0;
|
size_t buf_full = 0;
|
||||||
BackgroundColorContext context = {};
|
BackgroundColorContext context = {};
|
||||||
@@ -2379,16 +2376,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
|
/* 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. */
|
* is always restored — even on timeout — and we only need one DSR response instead of two. */
|
||||||
r = loop_write(output_fd,
|
r = loop_write_full(output_fd,
|
||||||
"\x1B" "7" /* DECSC: save cursor position */
|
"\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[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[6n" /* DSR: request cursor position (CPR) */
|
||||||
"\x1B" "8", /* DECRC: restore cursor position */
|
"\x1B" "8", /* DECRC: restore cursor position */
|
||||||
SIZE_MAX);
|
SIZE_MAX,
|
||||||
|
CONSOLE_ANSI_SEQUENCE_TIMEOUT_USEC);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
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 */
|
char buf[STRLEN("\x1B[1;1R")]; /* The shortest valid reply possible */
|
||||||
size_t buf_full = 0;
|
size_t buf_full = 0;
|
||||||
CursorPositionContext context = {};
|
CursorPositionContext context = {};
|
||||||
@@ -2536,11 +2534,11 @@ static int terminal_query_size_by_csi18(
|
|||||||
assert(nonblock_input_fd >= 0);
|
assert(nonblock_input_fd >= 0);
|
||||||
assert(output_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)
|
if (r < 0)
|
||||||
return r;
|
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)];
|
char buf[STRLEN(CSI18_R1)];
|
||||||
size_t bytes = 0;
|
size_t bytes = 0;
|
||||||
|
|
||||||
@@ -2591,6 +2589,7 @@ int terminal_get_size(
|
|||||||
_cleanup_close_ int nonblock_input_fd = -EBADF;
|
_cleanup_close_ int nonblock_input_fd = -EBADF;
|
||||||
struct termios old_termios = TERMIOS_NULL;
|
struct termios old_termios = TERMIOS_NULL;
|
||||||
CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
|
CLEANUP_TERMIOS_RESET(nonblock_input_fd, old_termios);
|
||||||
|
_cleanup_(nonblock_resetp) int nonblock_reset = -EBADF;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(try_dsr || try_csi18);
|
assert(try_dsr || try_csi18);
|
||||||
@@ -2599,6 +2598,14 @@ int terminal_get_size(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
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. */
|
/* Flush any stale input that might confuse the response parsers. */
|
||||||
(void) tcflush(nonblock_input_fd, TCIFLUSH);
|
(void) tcflush(nonblock_input_fd, TCIFLUSH);
|
||||||
|
|
||||||
@@ -2732,7 +2739,7 @@ int terminal_get_terminfo_by_dcs(int fd, char **ret_name) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
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)];
|
char buf[STRLEN(DCS_TERMINFO_R1) + MAX_TERMINFO_LENGTH + STRLEN(ANSI_ST)];
|
||||||
size_t bytes = 0;
|
size_t bytes = 0;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user