diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 7a2fc76b65..c79cf67445 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. diff --git a/src/basic/ansi-color.h b/src/basic/ansi-color.h new file mode 100644 index 0000000000..efc1dea131 --- /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 488ed20713..50e975e80d 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/env-util.c b/src/basic/env-util.c index 3e385e5ee4..cf2c47d214 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)); } diff --git a/src/basic/log.c b/src/basic/log.c index 50b405b843..a8056d2635 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 e50b54006a..c2ff3ca85a 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -21,6 +21,8 @@ #include #include "alloc-util.h" +#include "ansi-color.h" +#include "chase.h" #include "constants.h" #include "devnum-util.h" #include "env-util.h" @@ -32,6 +34,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" @@ -76,7 +79,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] = { @@ -246,92 +249,6 @@ int ask_string(char **ret, const char *text, ...) { return 0; } -int reset_terminal_fd(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 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; @@ -344,7 +261,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 (;;) { @@ -517,69 +434,210 @@ int terminal_vhangup_fd(int fd) { return RET_NERRNO(ioctl(fd, TIOCVHANGUP)); } -int terminal_vhangup(const char *name) { - _cleanup_close_ int fd = -EBADF; +int vt_disallocate(const char *tty_path) { + assert(tty_path); - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; + /* 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). */ - return terminal_vhangup_fd(fd); -} - -int vt_disallocate(const char *name) { - const char *e; - int r; - - /* 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 */ - 10); - 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) { + _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 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"); + + 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; + + /* 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, + 100 * USEC_PER_MSEC); + if (k < 0) + log_debug_errno(k, "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; +} + +void reset_dev_console_fd(int fd, bool switch_to_text) { + int r; + + 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"); + + 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 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) { @@ -598,20 +656,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 { - r = terminal_set_size_fd(fd, NULL, rows, cols); - if (r < 0) - log_warning_errno(r, "Failed to set terminal size, 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) @@ -657,25 +702,38 @@ int vtnr_from_tty(const char *tty) { return i; } - int resolve_dev_console(char **ret) { - _cleanup_free_ char *active = NULL; - char *tty; +int resolve_dev_console(char **ret) { 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 @@ -797,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; } @@ -834,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; } @@ -864,13 +922,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; @@ -889,7 +952,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; } @@ -939,7 +1002,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 */ @@ -1006,11 +1069,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); @@ -1037,8 +1100,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; } @@ -1413,28 +1476,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 = { @@ -1522,50 +1563,47 @@ 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) { - int r, k; +int terminal_reset_defensive(int fd, bool switch_to_text) { + int r = 0; assert(fd >= 0); - if (getenv_terminal_is_dumb()) - return 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*! */ - r = fd_nonblock(fd, true); - if (r < 0) - return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + if (!isatty_safe(fd)) + return -ENOTTY; - 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 */ - SIZE_MAX, - 50 * USEC_PER_MSEC); - if (k < 0) - log_debug_errno(k, "Failed to write to terminal: %m"); + RET_GATHER(r, terminal_reset_ioctl(fd, switch_to_text)); - if (r > 0) { - r = fd_nonblock(fd, false); - if (r < 0) - log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); - } + if (terminal_is_pty_fd(fd) == 0) + RET_GATHER(r, terminal_reset_ansi_seq(fd)); - return k < 0 ? k : r; + 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) { @@ -1576,6 +1614,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, @@ -1601,7 +1670,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 +1747,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 +1769,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 +1789,9 @@ static int scan_background_color_response( } } + if (ret_processed) + *ret_processed = size; + return 0; /* all good, but not enough data yet */ } @@ -1724,8 +1805,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 */ @@ -1740,47 +1822,57 @@ 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); if (r < 0) goto finish; - usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC); - char buf[256]; + 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 = {}; - for (;;) { - usec_t n = now(CLOCK_MONOTONIC); + for (bool first = true;; first = false) { + 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; + } + + /* 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; + } + + 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); @@ -1794,6 +1886,297 @@ int get_default_background_color(double *ret_red, double *ret_green, double *ret } finish: - (void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios); + 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; +} + +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; +} + +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 691b37aa51..84d4731ea8 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" @@ -95,10 +29,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_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); int open_terminal(const char *name, int mode); @@ -117,6 +51,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. */ @@ -130,11 +67,7 @@ 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_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); @@ -155,6 +88,8 @@ 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 lock_dev_console(void); int make_console_stdio(void); int fd_columns(int fd); @@ -177,99 +112,11 @@ 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 : ""; \ - } +int get_ctty_devnr(pid_t pid, dev_t *ret); +int get_ctty(pid_t, dev_t *ret_devnr, char **ret); -#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 *d); -int get_ctty(pid_t, dev_t *_devnr, char **r); - -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); @@ -277,20 +124,19 @@ 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); 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 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); + +int terminal_is_pty_fd(int fd); diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index dbda6e5457..75c26df52f 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/exec-invoke.c b/src/core/exec-invoke.c index 94c908afd7..ea85c36581 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -353,16 +353,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) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ true); - - (void) exec_context_apply_tty_size(context, STDIN_FILENO, /* tty_path= */ NULL); - } - return STDIN_FILENO; } @@ -389,10 +383,6 @@ static int setup_input( if (tty_fd < 0) return tty_fd; - r = exec_context_apply_tty_size(context, 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 +544,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: @@ -659,11 +648,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; @@ -671,15 +660,19 @@ 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; - r = reset_terminal_fd(fd, /* switch_to_text= */ true); + r = terminal_reset_defensive(fd, /* switch_to_text= */ true); 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; @@ -694,15 +687,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) { @@ -750,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; @@ -4031,6 +4025,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, @@ -4198,6 +4225,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)) { @@ -4381,6 +4413,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 18396db113..cdc1277956 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" @@ -99,56 +98,65 @@ 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; + int fd, r; 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->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); @@ -168,13 +176,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_reset) + (void) terminal_reset_defensive(fd, /* switch_to_text= */ true); + + 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); - if (context->tty_reset) - (void) reset_terminal_fd(fd, /* switch_to_text= */ true); - - (void) exec_context_apply_tty_size(context, fd, path); + /* 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); diff --git a/src/core/execute.h b/src/core/execute.h index 0414fbad22..b801bfe853 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); diff --git a/src/core/job.c b/src/core/job.c index a877f9f06f..21083497c0 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/main.c b/src/core/main.c index 1b78cd652f..e5c2dc914e 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -204,33 +204,58 @@ static int manager_find_user_config_paths(char ***ret_files, char ***ret_dirs) { return 0; } -static int console_setup(void) { - _cleanup_close_ int tty_fd = -EBADF; - unsigned rows, cols; +static int save_console_winsize_in_environment(int tty_fd) { int r; - tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + 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) + 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"); + /* 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); - 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"); + save_console_winsize_in_environment(tty_fd); return 0; } @@ -2910,7 +2935,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(); } diff --git a/src/core/show-status.c b/src/core/show-status.c index 57ad4db30b..7c7c10f963 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; diff --git a/src/core/transaction.c b/src/core/transaction.c index ab6e699d33..9d48768d87 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 c281a29391..2d750b7ef4 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 c4bf1fab95..0b1e380e06 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/firstboot/firstboot.c b/src/firstboot/firstboot.c index 92b0289859..c70bfa468f 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_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-recovery-key.c b/src/home/homectl-recovery-key.c index 015d265435..b5c57e14a8 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/home/homectl.c b/src/home/homectl.c index 65431db8ee..a71548439e 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_locked(STDOUT_FILENO, /* switch_to_text= */ false); for (;;) { username = mfree(username); diff --git a/src/import/export.c b/src/import/export.c index cdb1d6246a..1e82e84d7d 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 44fc5be8a5..e74d36288a 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 889cd63ff0..97db0b836a 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 7c838a5307..46055ce5e7 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/bsod.c b/src/journal/bsod.c index b2889f02ed..2116c498a4 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/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c index 10630f5e6c..8167aef7f5 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 e8ffc72bb8..9f07facad4 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 aa46fec91b..5fc3614afb 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 e852591a89..0fb93340a6 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 396ebe192c..dde40bf377 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 2219bf26cc..98ffe4db54 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 181e6c193d..76a2499561 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 bf79dc2633..778b31274d 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 2e6ed362f2..120f7201df 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 8b462b5627..4d6f2be3c4 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 87177316da..33fe222730 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/dev-setup.c b/src/shared/dev-setup.c index 4b4b62565c..eb72793c04 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 5339bc4e5e..92ba6cf764 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); diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index 9eb1611905..2c97c354ef 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 83eef7f7d8..3e11ba456f 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 a63e6cac73..36cd5dcd7c 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" @@ -52,7 +53,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 +65,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 +97,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 +128,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 +207,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"); diff --git a/src/systemctl/systemctl-list-dependencies.c b/src/systemctl/systemctl-list-dependencies.c index a9121f1035..3df9b7abdf 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 fcfe2ac561..2fecf61317 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 4407d2598a..1fffceb38e 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 943d7ffec5..975b3ebc54 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 184468ed61..b4ccc8ebd7 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 8068d77d1c..909f051cde 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 66b3d340bb..d9232e4a17 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 53bcc5d965..0756dff04b 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 f1814768fd..d70b727cee 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 9f8ca15a65..68b9cbe606 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -3,10 +3,13 @@ #include #include #include +#include #include +#include #include #include "alloc-util.h" +#include "ansi-color.h" #include "fd-util.h" #include "fs-util.h" #include "macro.h" @@ -176,6 +179,77 @@ 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); + } +} + +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."); +} + +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(); @@ -209,4 +283,12 @@ TEST(get_color_mode) { reset_terminal_feature_caches(); } +TEST(terminal_reset_defensive) { + int r; + + r = terminal_reset_defensive(STDOUT_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 84ce5e1940..4b1e848749 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_locked(tty_fd, /* switch_to_text= */ true); log_info("Starting password query on %s.", con); } diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 1d105047c9..2cdc3bd305 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 c8c23e811a..ff4625c3be 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" diff --git a/units/console-getty.service.in b/units/console-getty.service.in index d64112be5e..2de31dd01a 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 8847d735fb..7e277a49bc 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 80b8f3e922..ce8db9764e 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 6bf101eac9..5669b19aff 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