From 9de3cc14842beedeeaf592592027d026a5650bcb Mon Sep 17 00:00:00 2001 From: Sam Leonard Date: Thu, 12 Oct 2023 13:13:19 +0100 Subject: [PATCH] vmspawn: added initial code for vmspawn vmspawn-settings.c is currently empty but this will be used in future to house code for parsing settings from a file --- meson.build | 3 + meson_options.txt | 2 + src/vmspawn/meson.build | 27 +++ src/vmspawn/vmspawn-settings.c | 3 + src/vmspawn/vmspawn-settings.h | 11 + src/vmspawn/vmspawn-util.c | 238 +++++++++++++++++++ src/vmspawn/vmspawn-util.h | 24 ++ src/vmspawn/vmspawn.c | 406 +++++++++++++++++++++++++++++++++ 8 files changed, 714 insertions(+) create mode 100644 src/vmspawn/meson.build create mode 100644 src/vmspawn/vmspawn-settings.c create mode 100644 src/vmspawn/vmspawn-settings.h create mode 100644 src/vmspawn/vmspawn-util.c create mode 100644 src/vmspawn/vmspawn-util.h create mode 100644 src/vmspawn/vmspawn.c diff --git a/meson.build b/meson.build index 5f1331e6f3..fd7310750f 100644 --- a/meson.build +++ b/meson.build @@ -1605,6 +1605,7 @@ foreach term : ['analyze', 'userdb', 'utmp', 'vconsole', + 'vmspawn', 'xdg-autostart'] have = get_option(term) name = 'ENABLE_' + term.underscorify().to_upper() @@ -2208,6 +2209,7 @@ subdir('src/userdb') subdir('src/varlinkctl') subdir('src/vconsole') subdir('src/veritysetup') +subdir('src/vmspawn') subdir('src/volatile-root') subdir('src/xdg-autostart-generator') @@ -2798,6 +2800,7 @@ foreach tuple : [ ['tmpfiles'], ['userdb'], ['vconsole'], + ['vmspawn'], ['xdg-autostart'], # optional features diff --git a/meson_options.txt b/meson_options.txt index 831d23e299..63e14d4f6c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -154,6 +154,8 @@ option('backlight', type : 'boolean', description : 'support for restoring backlight state') option('vconsole', type : 'boolean', description : 'support for vconsole configuration') +option('vmspawn', type : 'boolean', value: false, + description : 'install the systemd-vmspawn tool') option('quotacheck', type : 'boolean', description : 'support for the quotacheck tools') option('sysusers', type : 'boolean', diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build new file mode 100644 index 0000000000..800d7c362c --- /dev/null +++ b/src/vmspawn/meson.build @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +libvmspawn_core_sources = files( + 'vmspawn-settings.c', + 'vmspawn-util.c', +) +libvmspawn_core = static_library( + 'vmspawn-core', + libvmspawn_core_sources, + include_directories : includes, + dependencies : [userspace], + build_by_default : false) + +vmspawn_libs = [ + libvmspawn_core, + libshared, +] + +executables += [ + executable_template + { + 'name' : 'systemd-vmspawn', + 'public' : true, + 'conditions': ['ENABLE_VMSPAWN'], + 'sources' : files('vmspawn.c'), + 'link_with' : vmspawn_libs, + } +] diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c new file mode 100644 index 0000000000..cb1a463781 --- /dev/null +++ b/src/vmspawn/vmspawn-settings.c @@ -0,0 +1,3 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "vmspawn-settings.h" diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h new file mode 100644 index 0000000000..268a874664 --- /dev/null +++ b/src/vmspawn/vmspawn-settings.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +typedef enum SettingsMask { + SETTING_START_MODE = UINT64_C(1) << 0, + SETTING_DIRECTORY = UINT64_C(1) << 26, + SETTING_CREDENTIALS = UINT64_C(1) << 30, + _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX +} SettingsMask; diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c new file mode 100644 index 0000000000..852a81d071 --- /dev/null +++ b/src/vmspawn/vmspawn-util.c @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "conf-files.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "json.h" +#include "log.h" +#include "macro.h" +#include "memory-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "sort-util.h" +#include "string-util.h" +#include "strv.h" +#include "vmspawn-util.h" + +OvmfConfig* ovmf_config_free(OvmfConfig *config) { + if (!config) + return NULL; + + free(config->path); + free(config->vars); + return mfree(config); +} + +int qemu_check_kvm_support(void) { + if (access("/dev/kvm", F_OK) >= 0) + return true; + if (errno == ENOENT) { + log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration."); + return false; + } + if (errno == EPERM) { + log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration."); + return false; + } + + return -errno; +} + +/* holds the data retrieved from the QEMU firmware interop JSON data */ +typedef struct FirmwareData { + char **features; + char *firmware; + char *vars; +} FirmwareData; + +static FirmwareData* firmware_data_free(FirmwareData *fwd) { + if (!fwd) + return NULL; + + fwd->features = strv_free(fwd->features); + fwd->firmware = mfree(fwd->firmware); + fwd->vars = mfree(fwd->vars); + + return mfree(fwd); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareData*, firmware_data_free); + +static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + static const JsonDispatch table[] = { + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + {} + }; + + return json_dispatch(v, table, 0, userdata); +} + +static int firmware_nvram_template(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + static const JsonDispatch table[] = { + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + {} + }; + + return json_dispatch(v, table, 0, userdata); +} + +static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + static const JsonDispatch table[] = { + { "device", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "executable", JSON_VARIANT_OBJECT, firmware_executable, 0, JSON_MANDATORY }, + { "nvram-template", JSON_VARIANT_OBJECT, firmware_nvram_template, 0, JSON_MANDATORY }, + {} + }; + + return json_dispatch(v, table, 0, userdata); +} + +int find_ovmf_config(int search_sb, OvmfConfig **ret) { + _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; + _cleanup_free_ char *user_firmware_dir = NULL; + _cleanup_strv_free_ char **conf_files = NULL; + int r; + + /* Search in: + * - $XDG_CONFIG_HOME/qemu/firmware + * - /etc/qemu/firmware + * - /usr/share/qemu/firmware + * + * Prioritising entries in "more specific" directories + */ + + r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware"); + if (r < 0) + return r; + + r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR, + STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware")); + if (r < 0) + return log_debug_errno(r, "Failed to list config files: %m"); + + STRV_FOREACH(file, conf_files) { + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL; + _cleanup_free_ char *contents = NULL; + size_t contents_sz = 0; + + r = read_full_file(*file, &contents, &contents_sz); + if (r == -ENOMEM) + return r; + if (r < 0) { + log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file); + continue; + } + + r = json_parse(contents, 0, &config_json, NULL, NULL); + if (r == -ENOMEM) + return r; + if (r < 0) { + log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file); + continue; + } + + static const JsonDispatch table[] = { + { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY }, + { "targets", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY }, + { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + {} + }; + + fwd = new0(FirmwareData, 1); + if (!fwd) + return -ENOMEM; + + r = json_dispatch(config_json, table, 0, fwd); + if (r == -ENOMEM) + return r; + if (r < 0) { + log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file); + continue; + } + + int sb_present = !!strv_find(fwd->features, "secure-boot"); + + /* exclude firmware which doesn't match our Secure Boot requirements */ + if (search_sb >= 0 && search_sb != sb_present) { + log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file); + continue; + } + + config = new0(OvmfConfig, 1); + if (!config) + return -ENOMEM; + + config->path = TAKE_PTR(fwd->firmware); + config->vars = TAKE_PTR(fwd->vars); + config->supports_sb = sb_present; + break; + } + + if (!config) + return -ENOENT; + + if (ret) + *ret = TAKE_PTR(config); + + return 0; +} + +int find_qemu_binary(char **ret_qemu_binary) { + int r; + + /* + * On success the path to the qemu binary will be stored in `req_qemu_binary` + * + * If the qemu binary cannot be found -ENOENT will be returned. + * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; + */ + + static const char *architecture_to_qemu_table[_ARCHITECTURE_MAX] = { + [ARCHITECTURE_ARM64] = "aarch64", /* differs from our name */ + [ARCHITECTURE_ARM] = "arm", + [ARCHITECTURE_ALPHA] = "alpha", + [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ + [ARCHITECTURE_X86] = "i386", /* differs from our name */ + [ARCHITECTURE_LOONGARCH64] = "loongarch64", + [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ + [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC] = "ppc", + [ARCHITECTURE_RISCV32] = "riscv32", + [ARCHITECTURE_RISCV64] = "riscv64", + [ARCHITECTURE_S390X] = "s390x", + }; + + FOREACH_STRING(s, "qemu", "qemu-kvm") { + r = find_executable(s, ret_qemu_binary); + if (r == 0) + return 0; + + if (r != -ENOENT) + return r; + } + + const char *arch_qemu = architecture_to_qemu_table[native_architecture()]; + if (!arch_qemu) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture())); + + _cleanup_free_ char *qemu_arch_specific = NULL; + qemu_arch_specific = strjoin("qemu-system-", arch_qemu); + if (!qemu_arch_specific) + return -ENOMEM; + + return find_executable(qemu_arch_specific, ret_qemu_binary); +} diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h new file mode 100644 index 0000000000..64ef4f2eda --- /dev/null +++ b/src/vmspawn/vmspawn-util.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include "macro.h" + +#if defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__) +#define ARCHITECTURE_SUPPORTS_SMBIOS 1 +#else +#define ARCHITECTURE_SUPPORTS_SMBIOS 0 +#endif + +typedef struct OvmfConfig { + char *path; + char *vars; + bool supports_sb; +} OvmfConfig; + +OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config); +DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free); + +int qemu_check_kvm_support(void); +int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config); +int find_qemu_binary(char **ret_qemu_binary); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c new file mode 100644 index 0000000000..68c483ad02 --- /dev/null +++ b/src/vmspawn/vmspawn.c @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "copy.h" +#include "creds-util.h" +#include "escape.h" +#include "fileio.h" +#include "format-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "machine-credential.h" +#include "main-func.h" +#include "pager.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "process-util.h" +#include "strv.h" +#include "tmpfile-util.h" +#include "vmspawn-settings.h" +#include "vmspawn-util.h" + +static PagerFlags arg_pager_flags = 0; +static char *arg_image = NULL; +static char *arg_qemu_smp = NULL; +static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL; +static int arg_qemu_kvm = -1; +static bool arg_qemu_gui = false; +static int arg_secure_boot = -1; +static MachineCredential *arg_credentials = NULL; +static size_t arg_n_credentials = 0; +static SettingsMask arg_settings_mask = 0; +static char **arg_parameters = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep); +STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + pager_open(arg_pager_flags); + + r = terminal_urlify_man("systemd-vmspawn", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] [ARGUMENTS...]\n\n" + "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n" + " -h --help Show this help\n" + " --version Print version string\n" + " --no-pager Do not pipe output into a pager\n\n" + "%3$sImage:%4$s\n" + " -i --image=PATH Root file system disk image (or device node) for\n" + " the virtual machine\n\n" + "%3$sHost Configuration:%4$s\n" + " --qemu-smp=SMP Configure guest's SMP settings\n" + " --qemu-mem=MEM Configure guest's RAM size\n" + " --qemu-kvm= Configure whether to use KVM or not\n" + " --qemu-gui Start QEMU in graphical mode\n" + " --secure-boot= Configure whether to search for firmware which supports Secure Boot\n\n" + "%3$sCredentials:%4$s\n" + " --set-credential=ID:VALUE\n" + " Pass a credential with literal value to container.\n" + " --load-credential=ID:PATH\n" + " Load credential to pass to container from file or\n" + " AF_UNIX stream socket.\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_QEMU_SMP, + ARG_QEMU_MEM, + ARG_QEMU_KVM, + ARG_QEMU_GUI, + ARG_SECURE_BOOT, + ARG_SET_CREDENTIAL, + ARG_LOAD_CREDENTIAL, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "image", required_argument, NULL, 'i' }, + { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP }, + { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM }, + { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM }, + { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, + { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, + { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, + { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + optind = 0; + while ((c = getopt_long(argc, argv, "+hi:", options, NULL)) >= 0) + switch (c) { + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'i': + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + + arg_settings_mask |= SETTING_DIRECTORY; + break; + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_QEMU_SMP: + arg_qemu_smp = strdup(optarg); + if (!arg_qemu_smp) + return log_oom(); + break; + + case ARG_QEMU_MEM: + r = parse_size(optarg, 1024, &arg_qemu_mem); + if (r < 0) + return log_error_errno(r, "Failed to parse --qemu-mem=%s: %m", optarg); + break; + + case ARG_QEMU_KVM: + r = parse_tristate(optarg, &arg_qemu_kvm); + if (r < 0) + return log_error_errno(r, "Failed to parse --qemu-kvm=%s: %m", optarg); + break; + + case ARG_QEMU_GUI: + arg_qemu_gui = true; + break; + + case ARG_SECURE_BOOT: + r = parse_tristate(optarg, &arg_secure_boot); + if (r < 0) + return log_error_errno(r, "Failed to parse --secure-boot=%s: %m", optarg); + break; + + case ARG_SET_CREDENTIAL: { + r = machine_credential_set(&arg_credentials, &arg_n_credentials, optarg); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Failed to set credential from %s: %m", optarg); + arg_settings_mask |= SETTING_CREDENTIALS; + break; + } + + case ARG_LOAD_CREDENTIAL: { + r = machine_credential_load(&arg_credentials, &arg_n_credentials, optarg); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + return log_error_errno(r, "Failed to load credential from %s: %m", optarg); + + arg_settings_mask |= SETTING_CREDENTIALS; + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (argc > optind) { + strv_free(arg_parameters); + arg_parameters = strv_copy(argv + optind); + if (!arg_parameters) + return log_oom(); + + arg_settings_mask |= SETTING_START_MODE; + } + + return 1; +} + +static int run_virtual_machine(void) { + _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; + _cleanup_strv_free_ char **cmdline = NULL; + _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL; + int r; + + bool use_kvm = arg_qemu_kvm > 0; + if (arg_qemu_kvm < 0) { + r = qemu_check_kvm_support(); + if (r < 0) + return log_error_errno(r, "Failed to check for KVM support: %m"); + use_kvm = r; + } + + r = find_ovmf_config(arg_secure_boot, &ovmf_config); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); + + /* only warn if the user hasn't disabled secureboot */ + if (!ovmf_config->supports_sb && arg_secure_boot) + log_warning("Couldn't find OVMF firmware blob with Secure Boot support, " + "falling back to OVMF firmware blobs without Secure Boot support."); + + const char *accel = use_kvm ? "kvm" : "tcg"; +#ifdef __aarch64__ + machine = strjoin("type=virt,accel=", accel); +#else + machine = strjoin("type=q35,accel=", accel, ",smm=", on_off(ovmf_config->supports_sb)); +#endif + if (!machine) + return log_oom(); + + r = find_qemu_binary(&qemu_binary); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Native architecture is not supported by qemu."); + if (r < 0) + return log_error_errno(r, "Failed to find QEMU binary: %m"); + + if (asprintf(&mem, "%.4fM", (double)arg_qemu_mem / (1024.0 * 1024.0)) < 0) + return log_oom(); + + cmdline = strv_new( + qemu_binary, + "-machine", machine, + "-smp", arg_qemu_smp ?: "1", + "-m", mem, + "-object", "rng-random,filename=/dev/urandom,id=rng0", + "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", + "-nic", "user,model=virtio-net-pci", + "-cpu", "max" + ); + + if (arg_qemu_gui) { + r = strv_extend_strv(&cmdline, STRV_MAKE("-vga", "virtio"), /* filter_duplicates= */ false); + if (r < 0) + return log_oom(); + } else { + r = strv_extend_strv(&cmdline, STRV_MAKE( + "-nographic", + "-nodefaults", + "-chardev", "stdio,mux=on,id=console,signal=off", + "-serial", "chardev:console", + "-mon", "console" + ), false); + if (r < 0) + return log_oom(); + } + +#if ARCHITECTURE_SUPPORTS_SMBIOS + ssize_t n; + FOREACH_ARRAY(cred, arg_credentials, arg_n_credentials) { + _cleanup_free_ char *cred_data_b64 = NULL; + + n = base64mem(cred->data, cred->size, &cred_data_b64); + if (n < 0) + return log_oom(); + + r = strv_extend(&cmdline, "-smbios"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "type=11,value=io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64); + if (r < 0) + return log_oom(); + } +#endif + + r = strv_extend(&cmdline, "-drive"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "if=pflash,format=raw,readonly=on,file=%s", ovmf_config->path); + if (r < 0) + return log_oom(); + + if (ovmf_config->supports_sb) { + const char *ovmf_vars_from = ovmf_config->vars; + _cleanup_free_ char *ovmf_vars_to = NULL; + _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; + + r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); + if (r < 0) + return r; + + source_fd = open(ovmf_vars_from, O_RDONLY|O_CLOEXEC); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open OVMF vars file %s: %m", ovmf_vars_from); + + target_fd = open(ovmf_vars_to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", ovmf_vars_to); + + r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); + + /* These aren't always available so don't raise an error if they fail */ + (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0); + (void) copy_access(source_fd, target_fd); + (void) copy_times(source_fd, target_fd, 0); + + r = strv_extend_strv(&cmdline, STRV_MAKE( + "-global", "ICH9-LPC.disable_s3=1", + "-global", "driver=cfi.pflash01,property=secure,value=on", + "-drive" + ), false); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "file=%s,if=pflash,format=raw", ovmf_vars_to); + if (r < 0) + return log_oom(); + } + + r = strv_extend(&cmdline, "-drive"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "if=none,id=mkosi,file=%s,format=raw", arg_image); + if (r < 0) + return log_oom(); + + r = strv_extend_strv(&cmdline, STRV_MAKE( + "-device", "virtio-scsi-pci,id=scsi", + "-device", "scsi-hd,drive=mkosi,bootindex=1" + ), false); + if (r < 0) + return log_oom(); + + r = strv_extend_strv(&cmdline, arg_parameters, false); + if (r < 0) + return log_oom(); + + pid_t child_pid; + r = safe_fork(qemu_binary, 0, &child_pid); + if (r == 0) { + /* set TERM and LANG if they are missing */ + if (setenv("TERM", "vt220", 0) < 0) + return log_oom(); + + if (setenv("LANG", "C.UTF-8", 0) < 0) + return log_oom(); + + execve(qemu_binary, cmdline, environ); + log_error_errno(errno, "Failed to execve %s: %m", qemu_binary); + _exit(EXIT_FAILURE); + } + + return wait_for_terminate_and_check(qemu_binary, child_pid, WAIT_LOG); +} + +static int run(int argc, char *argv[]) { + int r, ret = EXIT_SUCCESS; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (!arg_image) { + log_error("Missing required argument -i/--image, quitting"); + goto finish; + } + + r = run_virtual_machine(); +finish: + machine_credential_free_all(arg_credentials, arg_n_credentials); + + if (r < 0) + return r; + + return ret; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);