summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuca Boccassi <bluca@debian.org>2024-02-26 16:44:50 +0100
committerGitHub <noreply@github.com>2024-02-26 16:44:50 +0100
commit86401d9c4c0be2d87378ea65c7be26492a383da0 (patch)
treef3e28b4b98f05c941ec51dd66325b19e1239a6b7
parentMerge pull request #31480 from rpigott/dnssec-maxwork (diff)
parentnspawn: hide ^] hint unless we are interactive mode (diff)
downloadsystemd-86401d9c4c0be2d87378ea65c7be26492a383da0.tar.xz
systemd-86401d9c4c0be2d87378ea65c7be26492a383da0.zip
Merge pull request #31458 from poettering/vmspawn-ptyfwd
vmspawn: implement TTY logic via ptyfwd
-rw-r--r--man/systemd-vmspawn.xml44
-rw-r--r--src/basic/glyph-util.c2
-rw-r--r--src/basic/glyph-util.h1
-rw-r--r--src/nspawn/nspawn.c10
-rw-r--r--src/run/run.c6
-rw-r--r--src/shared/pretty-print.c2
-rw-r--r--src/test/test-locale-util.c3
-rw-r--r--src/vmspawn/vmspawn-settings.c10
-rw-r--r--src/vmspawn/vmspawn-settings.h15
-rw-r--r--src/vmspawn/vmspawn.c137
10 files changed, 201 insertions, 29 deletions
diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml
index ed4dfc8bfa..d7fee0538a 100644
--- a/man/systemd-vmspawn.xml
+++ b/man/systemd-vmspawn.xml
@@ -206,14 +206,6 @@
</varlistentry>
<varlistentry>
- <term><option>--qemu-gui</option></term>
-
- <listitem><para>Start QEMU in graphical mode.</para>
-
- <xi:include href="version-info.xml" xpointer="v255"/></listitem>
- </varlistentry>
-
- <varlistentry>
<term><option>-n</option></term>
<term><option>--network-tap</option></term>
@@ -362,6 +354,42 @@
</refsect2>
<refsect2>
+ <title>Input/Output Options</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--console=</option><replaceable>MODE</replaceable></term>
+
+ <listitem><para>Configures how to set up the console of the VM. Takes one of
+ <literal>interactive</literal>, <literal>read-only</literal>, <literal>native</literal>,
+ <literal>gui</literal>. Defaults to <literal>interactive</literal>. <literal>interactive</literal>
+ provides an interactive terminal interface to the VM. <literal>read-only</literal> is similar, but
+ is strictly read-only, i.e. does not accept any input from the user. <literal>native</literal> also
+ provides a TTY-based interface, but uses qemu native implementation (which means the qemu monitor
+ is available). <literal>gui</literal> shows the qemu graphical UI.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--background=<replaceable>COLOR</replaceable></option></term>
+
+ <listitem><para>Change the terminal background color to the specified ANSI color as long as the VM
+ runs. The color specified should be an ANSI X3.64 SGR background color, i.e. strings such as
+ <literal>40</literal>, <literal>41</literal>, …, <literal>47</literal>, <literal>48;2;…</literal>,
+ <literal>48;5;…</literal>. See <ulink
+ url="https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters">ANSI
+ Escape Code (Wikipedia)</ulink> for details. Assign an empty string to disable any coloring. This
+ only has an effect in <option>--console=interactive</option> and
+ <option>--console=read-only</option> modes.</para>
+
+ <xi:include href="version-info.xml" xpointer="v256"/>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2>
<title>Credentials</title>
<variablelist>
diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c
index 2cec3d82cf..b6b0f40ca6 100644
--- a/src/basic/glyph-util.c
+++ b/src/basic/glyph-util.c
@@ -77,6 +77,7 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) {
[SPECIAL_GLYPH_RED_CIRCLE] = "o",
[SPECIAL_GLYPH_YELLOW_CIRCLE] = "o",
[SPECIAL_GLYPH_BLUE_CIRCLE] = "o",
+ [SPECIAL_GLYPH_GREEN_CIRCLE] = "o",
},
/* UTF-8 */
@@ -143,6 +144,7 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) {
[SPECIAL_GLYPH_RED_CIRCLE] = u8"🔴",
[SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡",
[SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵",
+ [SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢",
},
};
diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h
index e476fefe94..2f70b187fc 100644
--- a/src/basic/glyph-util.h
+++ b/src/basic/glyph-util.h
@@ -52,6 +52,7 @@ typedef enum SpecialGlyph {
SPECIAL_GLYPH_RED_CIRCLE,
SPECIAL_GLYPH_YELLOW_CIRCLE,
SPECIAL_GLYPH_BLUE_CIRCLE,
+ SPECIAL_GLYPH_GREEN_CIRCLE,
_SPECIAL_GLYPH_MAX,
_SPECIAL_GLYPH_INVALID = -EINVAL,
} SpecialGlyph;
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index cd398f2461..1cb039ade4 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -5991,10 +5991,12 @@ static int run(int argc, char *argv[]) {
_cleanup_free_ char *u = NULL;
(void) terminal_urlify_path(t, t, &u);
- log_info("%s %sSpawning container %s on %s.%s\n"
- "%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ log_info("%s %sSpawning container %s on %s.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal());
+
+ if (arg_console_mode == CONSOLE_INTERACTIVE)
+ log_info("%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18) >= 0);
diff --git a/src/run/run.c b/src/run/run.c
index ec0f4f700e..5181c18c20 100644
--- a/src/run/run.c
+++ b/src/run/run.c
@@ -1751,9 +1751,9 @@ static int start_transient_service(sd_bus *bus) {
return log_error_errno(r, "Failed to get event loop: %m");
if (master >= 0) {
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT) >= 0);
- (void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL);
- (void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL);
+ assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGWINCH) >= 0);
+
+ (void) sd_event_set_signal_exit(c.event, true);
if (!arg_quiet)
log_info("Press ^] three times within 1s to disconnect TTY.");
diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c
index 946da5f42d..a4e5809446 100644
--- a/src/shared/pretty-print.c
+++ b/src/shared/pretty-print.c
@@ -452,7 +452,7 @@ int terminal_tint_color(double hue, char **ret) {
else /* otherwise pump it up */
s = 75;
- v = MAX(30, v); /* Make sure we don't hide the color in black */
+ v = MAX(20, v); /* Make sure we don't hide the color in black */
uint8_t r8, g8, b8;
hsv_to_rgb(hue, s, v, &r8, &g8, &b8);
diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c
index dd9a8134bf..67d9c7e65c 100644
--- a/src/test/test-locale-util.c
+++ b/src/test/test-locale-util.c
@@ -82,7 +82,7 @@ TEST(keymaps) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
TEST(dump_special_glyphs) {
- assert_cc(SPECIAL_GLYPH_BLUE_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
+ assert_cc(SPECIAL_GLYPH_GREEN_CIRCLE + 1 == _SPECIAL_GLYPH_MAX);
log_info("is_locale_utf8: %s", yes_no(is_locale_utf8()));
@@ -130,6 +130,7 @@ TEST(dump_special_glyphs) {
dump_glyph(SPECIAL_GLYPH_RED_CIRCLE);
dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE);
dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE);
+ dump_glyph(SPECIAL_GLYPH_GREEN_CIRCLE);
}
DEFINE_TEST_MAIN(LOG_INFO);
diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c
index cb1a463781..780df553aa 100644
--- a/src/vmspawn/vmspawn-settings.c
+++ b/src/vmspawn/vmspawn-settings.c
@@ -1,3 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "string-table.h"
#include "vmspawn-settings.h"
+
+static const char *const console_mode_table[_CONSOLE_MODE_MAX] = {
+ [CONSOLE_INTERACTIVE] = "interactive",
+ [CONSOLE_READ_ONLY] = "read-only",
+ [CONSOLE_NATIVE] = "native",
+ [CONSOLE_GUI] = "gui",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h
index 60ea10e6de..fe23aa23cf 100644
--- a/src/vmspawn/vmspawn-settings.h
+++ b/src/vmspawn/vmspawn-settings.h
@@ -1,8 +1,20 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
+#include <errno.h>
#include <stdint.h>
+#include "macro.h"
+
+typedef enum ConsoleMode {
+ CONSOLE_INTERACTIVE, /* ptyfwd */
+ CONSOLE_READ_ONLY, /* ptyfwd, but in read-only mode */
+ CONSOLE_NATIVE, /* qemu's native TTY handling */
+ CONSOLE_GUI, /* qemu's graphical UI */
+ _CONSOLE_MODE_MAX,
+ _CONSOLE_MODE_INVALID = -EINVAL,
+} ConsoleMode;
+
typedef enum SettingsMask {
SETTING_START_MODE = UINT64_C(1) << 0,
SETTING_BIND_MOUNTS = UINT64_C(1) << 11,
@@ -10,3 +22,6 @@ typedef enum SettingsMask {
SETTING_CREDENTIALS = UINT64_C(1) << 30,
_SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
} SettingsMask;
+
+const char *console_mode_to_string(ConsoleMode m) _const_;
+ConsoleMode console_mode_from_string(const char *s) _pure_;
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index 6c2d943daa..ce7f1ef2e3 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -46,6 +46,7 @@
#include "path-util.h"
#include "pretty-print.h"
#include "process-util.h"
+#include "ptyfwd.h"
#include "random-util.h"
#include "rm-rf.h"
#include "signal-util.h"
@@ -73,7 +74,7 @@ static unsigned arg_vsock_cid = VMADDR_CID_ANY;
static int arg_tpm = -1;
static char *arg_linux = NULL;
static char **arg_initrds = NULL;
-static bool arg_qemu_gui = false;
+static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE;
static NetworkStack arg_network_stack = NETWORK_STACK_NONE;
static int arg_secure_boot = -1;
static MachineCredentialContext arg_credentials = {};
@@ -87,6 +88,7 @@ static bool arg_runtime_directory_created = false;
static bool arg_privileged = false;
static char **arg_kernel_cmdline_extra = NULL;
static char **arg_extra_drives = NULL;
+static char *arg_background = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -101,6 +103,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -130,7 +133,6 @@ static int help(void) {
" --tpm=BOOL Enable use of a virtual TPM\n"
" --linux=PATH Specify the linux kernel for direct kernel boot\n"
" --initrd=PATH Specify the initrd for direct kernel boot\n"
- " --qemu-gui Start QEMU in graphical mode\n"
" -n --network-tap Create a TAP device for networking\n"
" --network-user-mode Use user mode networking\n"
" --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n"
@@ -150,6 +152,9 @@ static int help(void) {
"\n%3$sIntegration:%4$s\n"
" --forward-journal=FILE|DIR\n"
" Forward the VM's journal to the host\n"
+ "\n%3$sInput/Output:%4$s\n"
+ " --console=MODE Console mode (interactive, native, gui)\n"
+ " --background=COLOR Set ANSI color for background\n"
"\n%3$sCredentials:%4$s\n"
" --set-credential=ID:VALUE\n"
" Pass a credential with literal value to the VM\n"
@@ -190,6 +195,8 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
ARG_FIRMWARE,
+ ARG_CONSOLE,
+ ARG_BACKGROUND,
};
static const struct option options[] = {
@@ -212,7 +219,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "tpm", required_argument, NULL, ARG_TPM },
{ "linux", required_argument, NULL, ARG_LINUX },
{ "initrd", required_argument, NULL, ARG_INITRD },
- { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI },
+ { "console", required_argument, NULL, ARG_CONSOLE },
+ { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */
{ "network-tap", no_argument, NULL, 'n' },
{ "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE },
{ "bind", required_argument, NULL, ARG_BIND },
@@ -224,6 +232,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
+ { "background", required_argument, NULL, ARG_BACKGROUND },
{}
};
@@ -344,8 +353,15 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+ case ARG_CONSOLE:
+ arg_console_mode = console_mode_from_string(optarg);
+ if (arg_console_mode < 0)
+ return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg);
+
+ break;
+
case ARG_QEMU_GUI:
- arg_qemu_gui = true;
+ arg_console_mode = CONSOLE_GUI;
break;
case 'n':
@@ -438,6 +454,12 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_BACKGROUND:
+ r = free_and_strdup_warn(&arg_background, optarg);
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
@@ -1030,6 +1052,25 @@ static int merge_initrds(char **ret) {
return 0;
}
+static void set_window_title(PTYForward *f) {
+ _cleanup_free_ char *hn = NULL, *dot = NULL;
+
+ assert(f);
+
+ (void) gethostname_strict(&hn);
+
+ if (emoji_enabled())
+ dot = strjoin(special_glyph(SPECIAL_GLYPH_GREEN_CIRCLE), " ");
+
+ if (hn)
+ (void) pty_forward_set_titlef(f, "%sVirtual Machine %s on %s", strempty(dot), arg_machine, hn);
+ else
+ (void) pty_forward_set_titlef(f, "%sVirtual Machine %s", strempty(dot), arg_machine);
+
+ if (dot)
+ (void) pty_forward_set_title_prefix(f, dot);
+}
+
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
@@ -1222,12 +1263,54 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return log_oom();
- if (arg_qemu_gui)
+ _cleanup_close_ int master = -EBADF;
+ PTYForwardFlags ptyfwd_flags = 0;
+ switch (arg_console_mode) {
+
+ case CONSOLE_READ_ONLY:
+ ptyfwd_flags |= PTY_FORWARD_READ_ONLY;
+
+ _fallthrough_;
+
+ case CONSOLE_INTERACTIVE: {
+ _cleanup_free_ char *pty_path = NULL;
+
+ master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK);
+ if (master < 0)
+ return log_error_errno(errno, "Failed to acquire pseudo tty: %m");
+
+ r = ptsname_malloc(master, &pty_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine tty name: %m");
+
+ if (unlockpt(master) < 0)
+ return log_error_errno(errno, "Failed to unlock tty: %m");
+
+ if (strv_extend_many(
+ &cmdline,
+ "-nographic",
+ "-nodefaults",
+ "-chardev") < 0)
+ return log_oom();
+
+ if (strv_extendf(&cmdline,
+ "serial,id=console,path=%s", pty_path) < 0)
+ return log_oom();
+
+ r = strv_extend_many(
+ &cmdline,
+ "-serial", "chardev:console");
+ break;
+ }
+
+ case CONSOLE_GUI:
r = strv_extend_many(
&cmdline,
"-vga",
"virtio");
- else
+ break;
+
+ case CONSOLE_NATIVE:
r = strv_extend_many(
&cmdline,
"-nographic",
@@ -1235,6 +1318,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
"-chardev", "stdio,mux=on,id=console,signal=off",
"-serial", "chardev:console",
"-mon", "console");
+ break;
+
+ default:
+ assert_not_reached();
+ }
if (r < 0)
return log_oom();
@@ -1583,7 +1671,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
log_debug("Executing: %s", joined);
}
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0);
+ assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGCHLD, SIGWINCH) >= 0);
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
@@ -1635,6 +1723,26 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
/* Exit when the child exits */
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
+ _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+ if (master >= 0) {
+ r = pty_forward_new(event, master, ptyfwd_flags, &forward);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create PTY forwarder: %m");
+
+ if (!arg_background) {
+ _cleanup_free_ char *bg = NULL;
+
+ r = terminal_tint_color(130 /* green */, &bg);
+ if (r < 0)
+ log_debug_errno(r, "Failed to determine terminal background color, not tinting.");
+ else
+ (void) pty_forward_set_background_color(forward, bg);
+ } else if (!isempty(arg_background))
+ (void) pty_forward_set_background_color(forward, arg_background);
+
+ set_window_title(forward);
+ }
+
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
@@ -1740,15 +1848,20 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
- if (!arg_quiet) {
+ if (!arg_quiet && arg_console_mode != CONSOLE_GUI) {
_cleanup_free_ char *u = NULL;
const char *vm_path = arg_image ?: arg_directory;
(void) terminal_urlify_path(vm_path, vm_path, &u);
- log_info("%s %sSpawning VM %s on %s.%s\n"
- "%s %sPress %sCtrl-a x%s to kill VM.%s",
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: vm_path, ansi_normal(),
- special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ log_info("%s %sSpawning VM %s on %s.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: vm_path, ansi_normal());
+
+ if (arg_console_mode == CONSOLE_INTERACTIVE)
+ log_info("%s %sPress %sCtrl-]%s three times within 1s to kill VM.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
+ else if (arg_console_mode == CONSOLE_NATIVE)
+ log_info("%s %sPress %sCtrl-a x%s to kill VM.%s",
+ special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
r = sd_listen_fds_with_names(true, &names);