mirror of
https://github.com/systemd/systemd.git
synced 2025-05-27 21:05:55 +03:00
vmspawn: use our own ptyfwd code for the console of a VM
Let's make systemd-nspawn use our own ptyfwd logic to handle the TTY by default. This adds a new setting --console=, inspired by nspawn's setting of the same name. If --console=interactive= is used, then we'll do the TTY dance on our own via ptyfwd, and thus get tinting, our usual hotkey handling and similar. Since qemu's own console is useful too, let's keep it around via --console=native. FInally, replace the --qemu-gui switch by --console=gui.
This commit is contained in:
parent
2f7f08005b
commit
795ec90cda
@ -205,14 +205,6 @@
|
||||
</listitem>
|
||||
</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>
|
||||
@ -361,6 +353,42 @@
|
||||
</variablelist>
|
||||
</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>
|
||||
|
||||
|
@ -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"🟢",
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user