From: Daan De Meyer Date: Sat, 28 Mar 2026 13:12:25 +0000 (+0000) Subject: vmspawn: propagate $TERM from host into VM via kernel command line X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=dcce04e649f3e7f2a290fd35b039784a7cdd6490;p=thirdparty%2Fsystemd.git vmspawn: propagate $TERM from host into VM via kernel command line When running in a console mode (interactive, native, or read-only), propagate the host's $TERM into the VM by adding TERM= and systemd.tty.term.hvc0= to the kernel command line. TERM= is picked up by PID 1 and inherited by services on /dev/console (such as emergency.service). systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such as serial-getty@hvc0.service) which look up $TERM via the systemd.tty.term. kernel command line parameter. While systemd can auto-detect the terminal type via DCS XTGETTCAP, not all terminal emulators implement this, so explicitly propagating $TERM provides a more reliable experience. We skip propagation when $TERM is unset or set to "unknown" (as is the case in GitHub Actions and some other CI environments). Previously this was handled by mkosi synthesizing the corresponding kernel command line parameters externally. Co-developed-by: Claude Opus 4.6 --- diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 2da4f1c2fb8..ecdc2412472 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -1713,6 +1713,16 @@ static bool on_dev_null(void) { return cached_on_dev_null; } +bool term_env_valid(const char *term) { + /* Checks if the specified $TERM value is suitable for propagation, i.e. is not empty, not set to + * "unknown" (as is common in CI), and only contains characters valid in terminal type names. + * Valid $TERM values are things like "xterm-256color", "linux", "screen.xterm-256color", i.e. + * alphanumeric characters, hyphens, underscores, dots, and plus signs. */ + return !isempty(term) && + !streq(term, "unknown") && + in_charset(term, ALPHANUMERICAL "-_+."); +} + bool getenv_terminal_is_dumb(void) { const char *e; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index dde1430243c..7ac56611041 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -118,6 +118,7 @@ void columns_lines_cache_reset(int _unused_ signum); void reset_terminal_feature_caches(void); bool on_tty(void); +bool term_env_valid(const char *term); bool getenv_terminal_is_dumb(void); bool terminal_is_dumb(void); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 73df1cbadd9..1891ac8bad7 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3031,6 +3031,24 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = strv_prepend(&arg_kernel_cmdline_extra, "console=hvc0"); if (r < 0) return log_oom(); + + /* Propagate the host's $TERM into the VM via the kernel command line. TERM= is + * picked up by PID 1 and inherited by services on /dev/console, and + * systemd.tty.term.hvc0= is used by services directly attached to /dev/hvc0 (such + * as serial-getty). While systemd can auto-detect the terminal type via DCS + * XTGETTCAP, not all terminal emulators implement this, so let's always propagate + * $TERM if we have it. */ + const char *term = getenv("TERM"); + if (term_env_valid(term)) { + FOREACH_STRING(tty_key, "systemd.tty.term.hvc0", "TERM") { + _cleanup_free_ char *p = strjoin(tty_key, "=", term); + if (!p) + return log_oom(); + + if (strv_consume_prepend(&arg_kernel_cmdline_extra, TAKE_PTR(p)) < 0) + return log_oom(); + } + } } _cleanup_free_ char *fstab_extra = NULL;