diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2cee136fee..6bb4584a8e 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -1912,7 +1912,7 @@ static int build_environment( assert(cgroup_context); assert(ret); -#define N_ENV_VARS 20 +#define N_ENV_VARS 21 our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX); if (!our_env) return -ENOMEM; @@ -2048,8 +2048,28 @@ static int build_environment( term = cmdline; } - if (!term) + if (!term) { + /* If no precise $TERM is known and we pick a fallback default, then let's also set + * $COLORTERM=truecolor. That's because our fallback default is vt220, which is + * generally a safe bet (as it supports PageUp/PageDown unlike vt100, and is quite + * universally available in terminfo/termcap), except for the fact that real DEC + * vt220 gear never actually supported color. Most tools these days generate color on + * vt220 anyway, ignoring the physical capabilities of the real hardware, but some + * tools actually believe in the historical truth. Which is unfortunate since *we* + * *don't* care about the historical truth, we just want sane defaults if nothing + * better is explicitly configured. It's 2025 after all, at the time of writing, + * pretty much all terminal emulators actually *do* support color, hence if we don't + * know any better let's explicitly claim color support via $COLORTERM. Or in other + * words: we now explicitly claim to be connected to a franken-vt220 with true color + * support. */ + x = strdup("COLORTERM=truecolor"); + if (!x) + return -ENOMEM; + + our_env[n_env++] = x; + term = default_term_for_tty(tty_path); + } x = strjoin("TERM=", term); if (!x) diff --git a/src/core/main.c b/src/core/main.c index dc93c42f17..bca6c96b4e 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1558,8 +1558,6 @@ static int bump_unix_max_dgram_qlen(void) { } static int fixup_environment(void) { - _cleanup_free_ char *term = NULL; - const char *t; int r; /* Only fix up the environment when we are started as PID 1 */ @@ -1575,19 +1573,29 @@ static int fixup_environment(void) { * not have support for color mode for example. * * However if TERM was configured through the kernel command line then leave it alone. */ + _cleanup_free_ char *term = NULL; r = proc_cmdline_get_key("TERM", 0, &term); if (r < 0) return r; - - if (r == 0) { + if (r > 0) { + /* If we pick up $TERM, then also pick up $COLORTERM, $NO_COLOR */ + FOREACH_STRING(v, "COLORTERM", "NO_COLOR") { + _cleanup_free_ char *vv = NULL; + r = proc_cmdline_get_key(v, 0, &vv); + if (r < 0) + return r; + if (r > 0 && setenv(v, vv, /* overwrite= */ true) < 0) + return -errno; + } + } else { + /* If no $TERM is set then look for the per-tty variable instead */ r = proc_cmdline_get_key("systemd.tty.term.console", 0, &term); if (r < 0) return r; } - t = term ?: default_term_for_tty("/dev/console"); - - if (setenv("TERM", t, 1) < 0) + const char *t = term ?: default_term_for_tty("/dev/console"); + if (setenv("TERM", t, /* overwrite= */ true) < 0) return -errno; /* The kernels sets HOME=/ for init. Let's undo this. */ diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index b7b7130fdb..366446c306 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -1362,15 +1362,17 @@ static int shell_machine(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Shell only supported on local machines."); - /* Pass $TERM to shell session, if not explicitly specified. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) { - const char *t; + /* Pass $TERM & Co. to shell session, if not explicitly specified. */ + FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { + if (strv_find_prefix(arg_setenv, v)) + continue; - t = strv_find_prefix(environ, "TERM="); - if (t) { - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } + const char *t = strv_find_prefix(environ, v); + if (!t) + continue; + + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); } (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index f19014016f..e3c5d6b732 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3337,6 +3337,8 @@ static int inner_child( (char*) "PATH=" DEFAULT_PATH_COMPAT, NULL, /* container */ NULL, /* TERM */ + NULL, /* COLORTERM */ + NULL, /* NO_COLOR */ NULL, /* HOME */ NULL, /* USER */ NULL, /* LOGNAME */ @@ -3588,9 +3590,17 @@ static int inner_child( /* LXC sets container=lxc, so follow the scheme here */ envp[n_env++] = strjoina("container=", arg_container_service_name); - /* Propagate $TERM unless we are invoked in pipe mode and stdin/stdout/stderr don't refer to a TTY */ - const char *term = (arg_console_mode != CONSOLE_PIPE || on_tty()) ? strv_find_prefix(environ, "TERM=") : NULL; - envp[n_env++] = (char*) (term ?: "TERM=dumb"); + /* Propagate $TERM & Co. unless we are invoked in pipe mode and stdin/stdout/stderr don't refer to a TTY */ + if (arg_console_mode != CONSOLE_PIPE || on_tty()) { + FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") { + char *t = strv_find_prefix(environ, v); + if (!t) + continue; + + envp[n_env++] = t; + } + } else + envp[n_env++] = (char*) "TERM=dumb"; if (home || !uid_is_valid(arg_uid) || arg_uid == 0) if (asprintf(envp + n_env++, "HOME=%s", home ?: "/root") < 0) diff --git a/src/run/run.c b/src/run/run.c index 523993cffd..b9a4ecd2e4 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -1334,29 +1334,48 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p send_term = false; if (send_term != 0) { - const char *e; + const char *e, *colorterm = NULL, *no_color = NULL; - /* Propagate $TERM only if we are actually connected to a TTY */ + /* Propagate $TERM + $COLORTERM + $NO_COLOR if we are actually connected to a TTY */ if (isatty_safe(STDIN_FILENO) || isatty_safe(STDOUT_FILENO) || isatty_safe(STDERR_FILENO)) { - e = getenv("TERM"); + e = strv_find_prefix(environ, "TERM="); send_term = !!e; + + if (send_term) { + /* If we send $TERM along, then also propagate $COLORTERM + $NO_COLOR right with it */ + colorterm = strv_find_prefix(environ, "COLORTERM="); + no_color = strv_find_prefix(environ, "NO_COLOR="); + } } else /* If we are not connected to any TTY ourselves, then send TERM=dumb, but only if we * really need to (because we actually allocated a TTY for the service) */ - e = "dumb"; + e = "TERM=dumb"; if (send_term > 0) { - _cleanup_free_ char *n = NULL; - - n = strjoin("TERM=", e); - if (!n) - return log_oom(); - - r = sd_bus_message_append(m, - "(sv)", - "Environment", "as", 1, n); + r = sd_bus_message_append( + m, + "(sv)", + "Environment", "as", 1, e); if (r < 0) return bus_log_create_error(r); + + if (colorterm) { + r = sd_bus_message_append( + m, + "(sv)", + "Environment", "as", 1, colorterm); + if (r < 0) + return bus_log_create_error(r); + } + + if (no_color) { + r = sd_bus_message_append( + m, + "(sv)", + "Environment", "as", 1, no_color); + if (r < 0) + return bus_log_create_error(r); + } } } diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 134ed3f0de..ac663d0323 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -132,7 +132,7 @@ static int exec_process(const char *name, char **argv, int start_fd, size_t n_fd return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--inetd only supported for single file descriptors."); - FOREACH_STRING(var, "TERM", "PATH", "USER", "HOME") { + FOREACH_STRING(var, "TERM", "COLORTERM", "NO_COLOR", "PATH", "USER", "HOME") { const char *n; n = strv_find_prefix(environ, var);