From 811ad9e6b2b243428165c239aeb4791bc65b93dd Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Thu, 8 Feb 2024 16:42:02 +0000 Subject: [PATCH 1/4] vmspawn: support multiple initrds via merging --- man/systemd-vmspawn.xml | 1 + src/vmspawn/vmspawn.c | 69 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 0446d27630c..cfc543a88ab 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -183,6 +183,7 @@ Set the initrd to use for direct kernel boot. If the linux kernel supplied is a UKI then this argument is not required. + If the option is specified multiple times vmspawn will merge the initrds together. If no initrd was installed into the image then the image will fail to boot. diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index d5de11bfe0b..1e084f1154b 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include #include "sd-daemon.h" @@ -64,7 +66,7 @@ static int arg_qemu_vsock = -1; static unsigned arg_vsock_cid = VMADDR_CID_ANY; static int arg_tpm = -1; static char *arg_linux = NULL; -static char *arg_initrd = NULL; +static char **arg_initrds = NULL; static bool arg_qemu_gui = false; static QemuNetworkStack arg_network_stack = QEMU_NET_NONE; static int arg_secure_boot = -1; @@ -86,7 +88,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); -STATIC_DESTRUCTOR_REGISTER(arg_initrd, freep); +STATIC_DESTRUCTOR_REGISTER(arg_initrds, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done); STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); @@ -312,9 +314,15 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_INITRD: { - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_initrd); + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(optarg, /* suppress_root= */ false, &initrd_path); if (r < 0) return r; + + r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); + if (r < 0) + return log_oom(); + break; } @@ -810,6 +818,43 @@ static int kernel_cmdline_maybe_append_root(void) { return 0; } +static int merge_initrds(char **ret) { + _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; + _cleanup_close_ int ofd = -EBADF; + int r; + + assert(ret); + + r = tempfn_random_child(NULL, "vmspawn-initrd-", &merged_initrd); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file: %m"); + + ofd = open(merged_initrd, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (ofd < 0) + return log_error_errno(errno, "Failed to create regular file %s: %m", merged_initrd); + + size_t total_size = 0; + STRV_FOREACH(i, arg_initrds) { + _cleanup_close_ int ifd = -EBADF; + size_t to_seek = (4 - (total_size % 4)) % 4; + + /* seek to assure 4 byte alignment for each initrd */ + if (to_seek != 0 && lseek(ofd, to_seek, SEEK_CUR) < 0) + return log_error_errno(errno, "Failed to seek %s: %m", merged_initrd); + + ifd = open(*i, O_RDONLY|O_CLOEXEC); + if (ifd < 0) + return log_error_errno(errno, "Failed to open %s: %m", *i); + + r = copy_bytes(ifd, ofd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", *i, merged_initrd); + } + + *ret = TAKE_PTR(merged_initrd); + return 0; +} + 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; @@ -1248,8 +1293,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_oom(); } - if (arg_initrd) { - r = strv_extend_many(&cmdline, "-initrd", arg_initrd); + char *initrd = NULL; + _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; + size_t n_initrds = strv_length(arg_initrds); + + if (n_initrds == 1) + initrd = arg_initrds[0]; + else if (n_initrds > 1) { + r = merge_initrds(&merged_initrd); + if (r < 0) + return r; + + initrd = merged_initrd; + } + + if (initrd) { + r = strv_extend_many(&cmdline, "-initrd", initrd); if (r < 0) return log_oom(); } From 6af6d44230767bc85cbd243d7113c4632621bd59 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 7 Feb 2024 13:38:37 +0000 Subject: [PATCH 2/4] vmspawn: discover bootloader for directory type images --- src/vmspawn/vmspawn.c | 99 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 1e084f1154b..fc776746d01 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -8,6 +8,10 @@ #include #include +#include "bootspec.h" +#include "chase.h" +#include "dirent-util.h" +#include "fd-util.h" #include "sd-daemon.h" #include "sd-event.h" #include "sd-id128.h" @@ -45,6 +49,7 @@ #include "rm-rf.h" #include "signal-util.h" #include "socket-util.h" +#include "stat-util.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" @@ -818,6 +823,87 @@ static int kernel_cmdline_maybe_append_root(void) { return 0; } +static int discover_boot_entry(const char *root, char **ret_linux, char ***ret_initrds) { + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL; + int r; + + assert(root); + assert(ret_linux); + assert(ret_initrds); + + esp_path = path_join(root, "efi"); + if (!esp_path) + return log_oom(); + + xbootldr_path = path_join(root, "boot"); + if (!xbootldr_path) + return log_oom(); + + r = boot_config_load(&config, esp_path, xbootldr_path); + if (r < 0) + return r; + + r = boot_config_select_special_entries(&config, /* skip_efivars= */ true); + if (r < 0) + return log_error_errno(r, "Failed to find special boot config entries: %m"); + + const BootEntry *boot_entry = boot_config_default_entry(&config); + + if (!IN_SET(boot_entry->type, BOOT_ENTRY_UNIFIED, BOOT_ENTRY_CONF)) + boot_entry = NULL; + + /* If we cannot determine a default entry search for UKIs (Type #2 EFI Unified Kernel Images) + * then .conf files (Type #1 Boot Loader Specification Entries). + * https://uapi-group.org/specifications/specs/boot_loader_specification */ + if (!boot_entry) + FOREACH_ARRAY(entry, config.entries, config.n_entries) + if (entry->type == BOOT_ENTRY_UNIFIED) { + boot_entry = entry; + break; + } + + if (!boot_entry) + FOREACH_ARRAY(entry, config.entries, config.n_entries) + if (entry->type == BOOT_ENTRY_CONF) { + boot_entry = entry; + break; + } + + if (!boot_entry) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to discover any boot entries."); + + log_debug("Discovered boot entry %s (%s)", boot_entry->id, boot_entry_type_to_string(boot_entry->type)); + + _cleanup_free_ char *linux_kernel = NULL; + _cleanup_strv_free_ char **initrds = NULL; + if (boot_entry->type == BOOT_ENTRY_UNIFIED) { + linux_kernel = path_join(boot_entry->root, boot_entry->kernel); + if (!linux_kernel) + return log_oom(); + } else if (boot_entry->type == BOOT_ENTRY_CONF) { + linux_kernel = path_join(boot_entry->root, boot_entry->kernel); + if (!linux_kernel) + return log_oom(); + + STRV_FOREACH(initrd, boot_entry->initrd) { + _cleanup_free_ char *initrd_path = path_join(boot_entry->root, *initrd); + if (!initrd_path) + return log_oom(); + + r = strv_consume(&initrds, TAKE_PTR(initrd_path)); + if (r < 0) + return log_oom(); + } + } else + assert_not_reached(); + + *ret_linux = TAKE_PTR(linux_kernel); + *ret_initrds = TAKE_PTR(initrds); + + return 0; +} + static int merge_initrds(char **ret) { _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; _cleanup_close_ int ofd = -EBADF; @@ -909,8 +995,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { kernel = strdup(arg_linux); if (!kernel) return log_oom(); - } else if (arg_directory) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Please specify a kernel (--linux=) when using -D/--directory=, refusing."); + } else if (arg_directory) { + /* a kernel is required for directory type images so attempt to locate a UKI under /boot and /efi */ + r = discover_boot_entry(arg_directory, &kernel, &arg_initrds); + if (r < 0) + return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); + + log_debug("Discovered UKI image at %s", kernel); + } r = find_qemu_binary(&qemu_binary); if (r == -EOPNOTSUPP) @@ -1442,6 +1534,9 @@ static int verify_arguments(void) { if (arg_network_stack == QEMU_NET_TAP && !arg_privileged) return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--network-tap requires root privileges, refusing."); + if (!strv_isempty(arg_initrds) && !arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); + return 0; } From b064cc563a2546d5882e31b6aa8e5ad6b14d9063 Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 7 Feb 2024 14:51:21 +0000 Subject: [PATCH 3/4] vmspawn: search for machines when only passed -M/--machine= --- src/vmspawn/vmspawn.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index fc776746d01..116f0143cde 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -12,6 +12,7 @@ #include "chase.h" #include "dirent-util.h" #include "fd-util.h" +#include "discover-image.h" #include "sd-daemon.h" #include "sd-event.h" #include "sd-id128.h" @@ -1497,8 +1498,30 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { static int determine_names(void) { int r; - if (!arg_directory && !arg_image) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine path, please use -D or -i."); + if (!arg_directory && !arg_image) { + if (arg_machine) { + _cleanup_(image_unrefp) Image *i = NULL; + + r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i); + if (r == -ENOENT) + return log_error_errno(r, "No image for machine '%s'.", arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); + + if (IN_SET(i->type, IMAGE_RAW, IMAGE_BLOCK)) + r = free_and_strdup(&arg_image, i->path); + else if (IN_SET(i->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) + r = free_and_strdup(&arg_directory, i->path); + else + assert_not_reached(); + if (r < 0) + return log_oom(); + } else { + r = safe_getcwd(&arg_directory); + if (r < 0) + return log_error_errno(r, "Failed to determine current directory: %m"); + } + } if (!arg_machine) { if (arg_directory && path_equal(arg_directory, "/")) { From 38624568d8149f22fc79707f096dab6ab6f8b87a Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Wed, 7 Feb 2024 17:22:39 +0000 Subject: [PATCH 4/4] vmspawn: add template unit to start systemd-vmspawn -M --- units/meson.build | 4 ++++ units/systemd-vmspawn@.service.in | 34 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 units/systemd-vmspawn@.service.in diff --git a/units/meson.build b/units/meson.build index acfd8d1dcbe..0c971ef0bc4 100644 --- a/units/meson.build +++ b/units/meson.build @@ -436,6 +436,10 @@ units = [ 'conditions' : ['ENABLE_NETWORKD'], }, { 'file' : 'systemd-nspawn@.service.in' }, + { + 'file' : 'systemd-vmspawn@.service.in', + 'conditions' : ['ENABLE_VMSPAWN'], + }, { 'file' : 'systemd-oomd.service.in', 'conditions' : ['ENABLE_OOMD'], diff --git a/units/systemd-vmspawn@.service.in b/units/systemd-vmspawn@.service.in new file mode 100644 index 00000000000..608002040cd --- /dev/null +++ b/units/systemd-vmspawn@.service.in @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Virtual Machine %i +Documentation=man:systemd-vmspawn(1) +PartOf=machines.target +Before=machines.target +After=network.target modprobe@tun.service +RequiresMountsFor=/var/lib/machines/%i + +[Service] +ExecStart=systemd-vmspawn --quiet --network-tap --machine=%i +KillMode=mixed +Type=notify +Slice=machine.slice + +{# Enforce a strict device policy. Make sure to keep these policies in sync if you change them! #} +DevicePolicy=closed +DeviceAllow=/dev/net/tun rwm +DeviceAllow=char-pts rw + +# vmspawn itself needs access to /dev/kvm and /dev/vhost-vsock +DeviceAllow=/dev/kvm rw +DeviceAllow=/dev/vhost-vsock rw + +[Install] +WantedBy=machines.target