summaryrefslogtreecommitdiffstats
path: root/src/basic/terminal-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/basic/terminal-util.c')
-rw-r--r--src/basic/terminal-util.c352
1 files changed, 303 insertions, 49 deletions
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index 530ef9a..dda5920 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -27,6 +27,7 @@
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
+#include "hexdecoct.h"
#include "inotify-util.h"
#include "io-util.h"
#include "log.h"
@@ -53,6 +54,18 @@ static volatile int cached_on_dev_null = -1;
static volatile int cached_color_mode = _COLOR_INVALID;
static volatile int cached_underline_enabled = -1;
+bool isatty_safe(int fd) {
+ assert(fd >= 0);
+
+ if (isatty(fd))
+ return true;
+
+ /* Be resilient if we're working on stdio, since they're set up by parent process. */
+ assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO));
+
+ return false;
+}
+
int chvt(int vt) {
_cleanup_close_ int fd = -EBADF;
@@ -239,7 +252,7 @@ int reset_terminal_fd(int fd, bool switch_to_text) {
assert(fd >= 0);
- if (isatty(fd) < 1)
+ 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,
@@ -293,6 +306,8 @@ int reset_terminal_fd(int fd, bool switch_to_text) {
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 */
@@ -346,7 +361,7 @@ int open_terminal(const char *name, int mode) {
c++;
}
- if (isatty(fd) < 1)
+ if (!isatty_safe(fd))
return negative_errno();
return TAKE_FD(fd);
@@ -684,18 +699,10 @@ int vtnr_from_tty(const char *tty) {
tty = active;
}
- if (tty == active)
- *ret = TAKE_PTR(active);
- else {
- char *tmp;
-
- tmp = strdup(tty);
- if (!tmp)
- return -ENOMEM;
-
- *ret = tmp;
- }
+ if (tty != active)
+ return strdup_to(ret, tty);
+ *ret = TAKE_PTR(active);
return 0;
}
@@ -975,7 +982,7 @@ bool on_tty(void) {
}
int getttyname_malloc(int fd, char **ret) {
- char path[PATH_MAX], *c; /* PATH_MAX is counted *with* the trailing NUL byte */
+ char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */
int r;
assert(fd >= 0);
@@ -988,12 +995,7 @@ int getttyname_malloc(int fd, char **ret) {
if (r > 0)
return -r;
- c = strdup(skip_dev_prefix(path));
- if (!c)
- return -ENOMEM;
-
- *ret = c;
- return 0;
+ return strdup_to(ret, skip_dev_prefix(path));
}
int getttyname_harder(int fd, char **ret) {
@@ -1098,13 +1100,9 @@ int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) {
return -EINVAL;
if (ret) {
- _cleanup_free_ char *b = NULL;
-
- b = strdup(w);
- if (!b)
- return -ENOMEM;
-
- *ret = TAKE_PTR(b);
+ r = strdup_to(ret, w);
+ if (r < 0)
+ return r;
}
if (ret_devnr)
@@ -1198,7 +1196,7 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) {
assert(pid > 0);
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
if (r < 0)
return r;
@@ -1249,7 +1247,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) {
pid_t child;
int r;
- r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd);
+ r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd);
if (r < 0)
return r;
@@ -1446,38 +1444,33 @@ int vt_reset_keyboard(int fd) {
}
int vt_restore(int fd) {
+
static const struct vt_mode mode = {
.mode = VT_AUTO,
};
- int r, q = 0;
- if (isatty(fd) < 1)
+ int r, ret = 0;
+
+ assert(fd >= 0);
+
+ if (!isatty_safe(fd))
return log_debug_errno(errno, "Asked to restore the VT for an fd that does not refer to a terminal: %m");
if (ioctl(fd, KDSETMODE, KD_TEXT) < 0)
- q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m");
+ RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m"));
r = vt_reset_keyboard(fd);
- if (r < 0) {
- log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m");
- if (q >= 0)
- q = r;
- }
+ if (r < 0)
+ RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"));
- if (ioctl(fd, VT_SETMODE, &mode) < 0) {
- log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m");
- if (q >= 0)
- q = -errno;
- }
+ if (ioctl(fd, VT_SETMODE, &mode) < 0)
+ RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m"));
r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID);
- if (r < 0) {
- log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m");
- if (q >= 0)
- q = r;
- }
+ if (r < 0)
+ RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"));
- return q;
+ return ret;
}
int vt_release(int fd, bool restore) {
@@ -1487,7 +1480,7 @@ int vt_release(int fd, bool restore) {
* sent by the kernel and optionally reset the VT in text and auto
* VT-switching modes. */
- if (isatty(fd) < 1)
+ if (!isatty_safe(fd))
return log_debug_errno(errno, "Asked to release the VT for an fd that does not refer to a terminal: %m");
if (ioctl(fd, VT_RELDISP, 1) < 0)
@@ -1551,3 +1544,264 @@ int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column)
return 0;
}
+
+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,
+ "\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");
+
+ 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 termios_disable_echo(struct termios *termios) {
+ assert(termios);
+
+ termios->c_lflag &= ~(ICANON|ECHO);
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
+}
+
+typedef enum BackgroundColorState {
+ BACKGROUND_TEXT,
+ BACKGROUND_ESCAPE,
+ BACKGROUND_BRACKET,
+ BACKGROUND_FIRST_ONE,
+ BACKGROUND_SECOND_ONE,
+ BACKGROUND_SEMICOLON,
+ BACKGROUND_R,
+ BACKGROUND_G,
+ BACKGROUND_B,
+ BACKGROUND_RED,
+ BACKGROUND_GREEN,
+ BACKGROUND_BLUE,
+ BACKGROUND_STRING_TERMINATOR,
+} BackgroundColorState;
+
+typedef struct BackgroundColorContext {
+ BackgroundColorState state;
+ uint32_t red, green, blue;
+ unsigned red_bits, green_bits, blue_bits;
+} BackgroundColorContext;
+
+static int scan_background_color_response(
+ BackgroundColorContext *context,
+ const char *buf,
+ size_t size) {
+
+ assert(context);
+ assert(buf || size == 0);
+
+ for (size_t i = 0; i < size; i++) {
+ char c = buf[i];
+
+ switch (context->state) {
+
+ case BACKGROUND_TEXT:
+ context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_ESCAPE:
+ context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_BRACKET:
+ context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_FIRST_ONE:
+ context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_SECOND_ONE:
+ context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_SEMICOLON:
+ context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_R:
+ context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_G:
+ context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_B:
+ context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT;
+ break;
+
+ case BACKGROUND_RED:
+ if (c == '/')
+ context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT;
+ else {
+ int d = unhexchar(c);
+ if (d < 0 || context->red_bits >= sizeof(context->red)*8)
+ context->state = BACKGROUND_TEXT;
+ else {
+ context->red = (context->red << 4) | d;
+ context->red_bits += 4;
+ }
+ }
+ break;
+
+ case BACKGROUND_GREEN:
+ if (c == '/')
+ context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT;
+ else {
+ int d = unhexchar(c);
+ if (d < 0 || context->green_bits >= sizeof(context->green)*8)
+ context->state = BACKGROUND_TEXT;
+ else {
+ context->green = (context->green << 4) | d;
+ context->green_bits += 4;
+ }
+ }
+ break;
+
+ case BACKGROUND_BLUE:
+ if (c == '\x07') {
+ if (context->blue_bits > 0)
+ return 1; /* success! */
+
+ context->state = BACKGROUND_TEXT;
+ } else if (c == '\x1b')
+ context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT;
+ else {
+ int d = unhexchar(c);
+ if (d < 0 || context->blue_bits >= sizeof(context->blue)*8)
+ context->state = BACKGROUND_TEXT;
+ else {
+ context->blue = (context->blue << 4) | d;
+ context->blue_bits += 4;
+ }
+ }
+ break;
+
+ case BACKGROUND_STRING_TERMINATOR:
+ if (c == '\\')
+ return 1; /* success! */
+
+ context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT;
+ break;
+
+ }
+
+ /* Reset any colors we might have picked up */
+ if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) {
+ /* reset color */
+ context->red = context->green = context->blue = 0;
+ context->red_bits = context->green_bits = context->blue_bits = 0;
+ }
+ }
+
+ return 0; /* all good, but not enough data yet */
+}
+
+int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) {
+ int r;
+
+ assert(ret_red);
+ assert(ret_green);
+ assert(ret_blue);
+
+ if (!colors_enabled())
+ return -EOPNOTSUPP;
+
+ if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO))
+ return -EOPNOTSUPP;
+
+ if (streq_ptr(getenv("TERM"), "linux")) {
+ /* Linux console is black */
+ *ret_red = *ret_green = *ret_blue = 0.0;
+ return 0;
+ }
+
+ struct termios old_termios;
+ if (tcgetattr(STDIN_FILENO, &old_termios) < 0)
+ return -errno;
+
+ struct termios new_termios = old_termios;
+ termios_disable_echo(&new_termios);
+
+ if (tcsetattr(STDOUT_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];
+ size_t buf_full = 0;
+ BackgroundColorContext context = {};
+
+ for (;;) {
+ usec_t n = now(CLOCK_MONOTONIC);
+
+ if (n >= end) {
+ r = -EOPNOTSUPP;
+ goto finish;
+ }
+
+ r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n));
+ if (r < 0)
+ goto finish;
+ if (r == 0) {
+ r = -EOPNOTSUPP;
+ goto finish;
+ }
+
+ ssize_t l;
+ l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full);
+ if (l < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ 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);
+ assert(context.green_bits > 0);
+ *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1);
+ assert(context.blue_bits > 0);
+ *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1);
+ r = 0;
+ goto finish;
+ }
+ }
+
+finish:
+ (void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios);
+ return r;
+}