]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: added initial code for vmspawn
authorSam Leonard <sam.leonard@codethink.co.uk>
Thu, 12 Oct 2023 12:13:19 +0000 (13:13 +0100)
committerSam Leonard <sam.leonard@codethink.co.uk>
Thu, 2 Nov 2023 16:21:40 +0000 (16:21 +0000)
vmspawn-settings.c is currently empty but this will be used in future to
house code for parsing settings from a file

meson.build
meson_options.txt
src/vmspawn/meson.build [new file with mode: 0644]
src/vmspawn/vmspawn-settings.c [new file with mode: 0644]
src/vmspawn/vmspawn-settings.h [new file with mode: 0644]
src/vmspawn/vmspawn-util.c [new file with mode: 0644]
src/vmspawn/vmspawn-util.h [new file with mode: 0644]
src/vmspawn/vmspawn.c [new file with mode: 0644]

index 5f1331e6f333d27a33b68febc05150bc8f631ac2..fd7310750fcd18c707bffc2862e5bf863b028331 100644 (file)
@@ -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
index 831d23e299a785240d066b18312d6cd8cc5b669c..63e14d4f6c2d416bd0fc790ca27e4769dec3f9b3 100644 (file)
@@ -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 (file)
index 0000000..800d7c3
--- /dev/null
@@ -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 (file)
index 0000000..cb1a463
--- /dev/null
@@ -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 (file)
index 0000000..268a874
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+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 (file)
index 0000000..852a81d
--- /dev/null
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdio.h>
+
+#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 (file)
index 0000000..64ef4f2
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#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 (file)
index 0000000..68c483a
--- /dev/null
@@ -0,0 +1,406 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <getopt.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#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);