diff options
author | Luca Boccassi <bluca@debian.org> | 2024-02-26 16:44:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-26 16:44:50 +0100 |
commit | 86401d9c4c0be2d87378ea65c7be26492a383da0 (patch) | |
tree | f3e28b4b98f05c941ec51dd66325b19e1239a6b7 | |
parent | Merge pull request #31480 from rpigott/dnssec-maxwork (diff) | |
parent | nspawn: hide ^] hint unless we are interactive mode (diff) | |
download | systemd-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.xml | 44 | ||||
-rw-r--r-- | src/basic/glyph-util.c | 2 | ||||
-rw-r--r-- | src/basic/glyph-util.h | 1 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 10 | ||||
-rw-r--r-- | src/run/run.c | 6 | ||||
-rw-r--r-- | src/shared/pretty-print.c | 2 | ||||
-rw-r--r-- | src/test/test-locale-util.c | 3 | ||||
-rw-r--r-- | src/vmspawn/vmspawn-settings.c | 10 | ||||
-rw-r--r-- | src/vmspawn/vmspawn-settings.h | 15 | ||||
-rw-r--r-- | src/vmspawn/vmspawn.c | 137 |
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); |