diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 6fb4e78c2f6..fbe3e68974d 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -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; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 60caa424b4e..c15ce7fddde 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -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); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index f90da747848..654eda8e332 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -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;