1
0
mirror of https://github.com/systemd/systemd synced 2024-07-08 20:15:55 +00:00

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
This commit is contained in:
Sam Leonard 2023-10-12 13:13:19 +01:00
parent 35dab95cdd
commit 9de3cc1484
No known key found for this signature in database
GPG Key ID: 96850F0978CE78F0
8 changed files with 714 additions and 0 deletions

View 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

View 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',

27
src/vmspawn/meson.build Normal file
View File

@ -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,
}
]

View File

@ -0,0 +1,3 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "vmspawn-settings.h"

View File

@ -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;

238
src/vmspawn/vmspawn-util.c Normal file
View File

@ -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);
}

View File

@ -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);

406
src/vmspawn/vmspawn.c Normal file
View File

@ -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);