Merge pull request #31023 from poettering/vmspawn-work

vmspawn: make it work on current fedora
This commit is contained in:
Lennart Poettering 2024-01-22 17:54:43 +01:00 committed by GitHub
commit afd08d7740
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 301 additions and 94 deletions

View file

@ -135,7 +135,18 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
</variablelist>
<varlistentry>
<term><option>--firmware=</option><replaceable>PATH</replaceable></term>
<listitem><para>Takes an absolute path, or a relative path beginning with
<filename>./</filename>. Specifies a JSON firmware definition file, which allows selecting the
firmware to boot in the VM. If not specified a suitable firmware is automatically discovered. If the
special string <literal>list</literal> is specified lists all discovered firmwares.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
</variablelist>
</refsect2><refsect2>
<title>System Identity Options</title>

View file

@ -151,3 +151,20 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle
return 0;
}
int event_add_child_pidref(
sd_event *e,
sd_event_source **s,
const PidRef *pid,
int options,
sd_event_child_handler_t callback,
void *userdata) {
if (!pidref_is_set(pid))
return -ESRCH;
if (pid->fd >= 0)
return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata);
return sd_event_add_child(e, s, pid->pid, options, callback, userdata);
}

View file

@ -5,6 +5,8 @@
#include "sd-event.h"
#include "pidref.h"
int event_reset_time(
sd_event *e,
sd_event_source **s,
@ -32,3 +34,5 @@ static inline int event_source_disable(sd_event_source *s) {
}
int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata);
int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata);

View file

@ -5943,9 +5943,16 @@ static int run(int argc, char *argv[]) {
if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */
arg_quiet = true;
if (!arg_quiet)
log_info("Spawning container %s on %s.\nPress Ctrl-] three times within 1s to kill container.",
arg_machine, arg_image ?: arg_directory);
if (!arg_quiet) {
const char *t = arg_image ?: arg_directory;
_cleanup_free_ char *u = NULL;
(void) terminal_urlify_path(t, t, &u);
log_info("%s %sSpawning container %s on %s.%s\n"
"%s %sPress %sCtrl-]%s three times within 1s to kill container.%s",
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal(),
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0);

View file

@ -29,7 +29,9 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) {
return NULL;
free(config->path);
free(config->format);
free(config->vars);
free(config->vars_format);
return mfree(config);
}
@ -40,7 +42,7 @@ int qemu_check_kvm_support(void) {
log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration.");
return false;
}
if (errno == EPERM) {
if (ERRNO_IS_PRIVILEGE(errno)) {
log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration.");
return false;
}
@ -62,11 +64,11 @@ int qemu_check_vsock_support(void) {
fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
if (fd >= 0)
return true;
if (errno == ENODEV) {
if (ERRNO_IS_DEVICE_ABSENT(errno)) {
log_debug_errno(errno, "/dev/vhost-vsock device doesn't exist. Not adding a vsock device to the virtual machine.");
return false;
}
if (errno == EPERM) {
if (ERRNO_IS_PRIVILEGE(errno)) {
log_debug_errno(errno, "Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine.");
return false;
}
@ -78,16 +80,26 @@ int qemu_check_vsock_support(void) {
typedef struct FirmwareData {
char **features;
char *firmware;
char *firmware_format;
char *vars;
char *vars_format;
} FirmwareData;
static bool firmware_data_supports_sb(const FirmwareData *fwd) {
assert(fwd);
return strv_contains(fwd->features, "secure-boot");
}
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);
strv_free(fwd->features);
free(fwd->firmware);
free(fwd->firmware_format);
free(fwd->vars);
free(fwd->vars_format);
return mfree(fwd);
}
@ -95,8 +107,8 @@ 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 },
{ "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY },
{ "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware_format), JSON_MANDATORY },
{}
};
@ -105,8 +117,8 @@ static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFla
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 },
{ "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY },
{ "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars_format), JSON_MANDATORY },
{}
};
@ -124,12 +136,136 @@ static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags
return json_dispatch(v, table, flags, userdata);
}
static int get_firmware_search_dirs(char ***ret) {
int r;
assert(ret);
/* Search in:
* - $XDG_CONFIG_HOME/qemu/firmware
* - /etc/qemu/firmware
* - /usr/share/qemu/firmware
*
* Prioritising entries in "more specific" directories */
_cleanup_free_ char *user_firmware_dir = NULL;
r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
if (r < 0)
return r;
_cleanup_strv_free_ char **l = NULL;
l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware");
if (!l)
return log_oom_debug();
*ret = TAKE_PTR(l);
return 0;
}
int list_ovmf_config(char ***ret) {
_cleanup_strv_free_ char **search_dirs = NULL;
int r;
assert(ret);
r = get_firmware_search_dirs(&search_dirs);
if (r < 0)
return r;
r = conf_files_list_strv(
ret,
".json",
/* root= */ NULL,
CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR,
(const char *const*) search_dirs);
if (r < 0)
return log_debug_errno(r, "Failed to list firmware files: %m");
return 0;
}
static int load_firmware_data(const char *path, FirmwareData **ret) {
int r;
assert(path);
assert(ret);
_cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
r = json_parse_file(
/* f= */ NULL,
path,
/* flags= */ 0,
&json,
/* ret_line= */ NULL,
/* ret_column= */ NULL);
if (r < 0)
return r;
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 },
{}
};
_cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
fwd = new0(FirmwareData, 1);
if (!fwd)
return -ENOMEM;
r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd);
if (r < 0)
return r;
*ret = TAKE_PTR(fwd);
return 0;
}
static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) {
assert(fwd);
assert(ret);
_cleanup_free_ OvmfConfig *config = NULL;
config = new(OvmfConfig, 1);
if (!config)
return -ENOMEM;
*config = (OvmfConfig) {
.path = TAKE_PTR(fwd->firmware),
.format = TAKE_PTR(fwd->firmware_format),
.vars = TAKE_PTR(fwd->vars),
.vars_format = TAKE_PTR(fwd->vars_format),
.supports_sb = firmware_data_supports_sb(fwd),
};
*ret = TAKE_PTR(config);
return 0;
}
int load_ovmf_config(const char *path, OvmfConfig **ret) {
_cleanup_(firmware_data_freep) FirmwareData *fwd = NULL;
int r;
assert(path);
assert(ret);
r = load_firmware_data(path, &fwd);
if (r < 0)
return r;
return ovmf_config_make(fwd, ret);
}
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;
assert(ret);
/* Search in:
* - $XDG_CONFIG_HOME/qemu/firmware
* - /etc/qemu/firmware
@ -138,79 +274,35 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) {
* Prioritising entries in "more specific" directories
*/
r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware");
r = list_ovmf_config(&conf_files);
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;
r = load_firmware_data(*file, &fwd);
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, JSON_ALLOW_EXTENSIONS, 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);
log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file);
continue;
}
if (strv_contains(fwd->features, "enrolled-keys")) {
log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues", *file);
log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file);
continue;
}
bool sb_present = strv_contains(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);
if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) {
log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file);
continue;
}
config = new0(OvmfConfig, 1);
if (!config)
return -ENOMEM;
r = ovmf_config_make(fwd, &config);
if (r < 0)
return r;
config->path = TAKE_PTR(fwd->firmware);
config->vars = TAKE_PTR(fwd->vars);
config->supports_sb = sb_present;
log_debug("Selected firmware definition %s.", *file);
break;
}

View file

@ -22,15 +22,27 @@
typedef struct OvmfConfig {
char *path;
char *format;
char *vars;
char *vars_format;
bool supports_sb;
} OvmfConfig;
static inline const char *ovmf_config_format(const OvmfConfig *c) {
return ASSERT_PTR(c)->format ?: "raw";
}
static inline const char *ovmf_config_vars_format(const OvmfConfig *c) {
return ASSERT_PTR(c)->vars_format ?: "raw";
}
OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config);
DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
int qemu_check_kvm_support(void);
int qemu_check_vsock_support(void);
int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config);
int list_ovmf_config(char ***ret);
int load_ovmf_config(const char *path, OvmfConfig **ret);
int find_ovmf_config(int search_sb, OvmfConfig **ret);
int find_qemu_binary(char **ret_qemu_binary);
int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock);

View file

@ -13,6 +13,7 @@
#include "copy.h"
#include "creds-util.h"
#include "escape.h"
#include "event-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
@ -35,11 +36,12 @@
#include "vmspawn-settings.h"
#include "vmspawn-util.h"
static bool arg_quiet = false;
static PagerFlags arg_pager_flags = 0;
static char *arg_image = NULL;
static char *arg_machine = NULL;
static char *arg_qemu_smp = NULL;
static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL;
static uint64_t arg_qemu_mem = UINT64_C(2) * U64_GB;
static int arg_qemu_kvm = -1;
static int arg_qemu_vsock = -1;
static unsigned arg_vsock_cid = VMADDR_CID_ANY;
@ -48,12 +50,14 @@ static int arg_secure_boot = -1;
static MachineCredentialContext arg_credentials = {};
static SettingsMask arg_settings_mask = 0;
static char **arg_parameters = NULL;
static char *arg_firmware = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep);
STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@ -69,6 +73,7 @@ static int help(void) {
"%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"
" -q --quiet Do not show status information\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"
@ -82,6 +87,7 @@ static int help(void) {
" --qemu-gui Start QEMU in graphical mode\n"
" --secure-boot=BOOL Configure whether to search for firmware which\n"
" supports Secure Boot\n"
" --firmware=PATH|list Select firmware definition file (or list available)\n"
"\n%3$sSystem Identity:%4$s\n"
" -M --machine=NAME Set the machine name for the container\n"
"\n%3$sCredentials:%4$s\n"
@ -114,11 +120,13 @@ static int parse_argv(int argc, char *argv[]) {
ARG_SECURE_BOOT,
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
ARG_FIRMWARE,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "quiet", no_argument, NULL, 'q' },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "image", required_argument, NULL, 'i' },
{ "machine", required_argument, NULL, 'M' },
@ -131,6 +139,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
{ "firmware", required_argument, NULL, ARG_FIRMWARE },
{}
};
@ -140,7 +149,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argv);
optind = 0;
while ((c = getopt_long(argc, argv, "+hi:M", options, NULL)) >= 0)
while ((c = getopt_long(argc, argv, "+hi:Mq", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
@ -148,6 +157,10 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_VERSION:
return version();
case 'q':
arg_quiet = true;
break;
case 'i':
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
if (r < 0)
@ -241,6 +254,31 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_FIRMWARE:
if (streq(optarg, "list")) {
_cleanup_strv_free_ char **l = NULL;
r = list_ovmf_config(&l);
if (r < 0)
return log_error_errno(r, "Failed to list firmwares: %m");
bool nl = false;
fputstrv(stdout, l, "\n", &nl);
if (nl)
putchar('\n');
return 0;
}
if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
return log_error_errno(SYNTHETIC_ERRNO(errno), "Absolute path or path starting with './' required.");
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
@ -378,25 +416,34 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u
return 0;
}
static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **notify_event_source) {
static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **ret_notify_event_source) {
int r;
r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
assert(event);
assert(fd >= 0);
assert(exit_status);
assert(ret_notify_event_source);
r = sd_event_add_io(event, ret_notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status);
if (r < 0)
return log_error_errno(r, "Failed to allocate notify socket event source: %m");
(void) sd_event_source_set_description(*notify_event_source, "vmspawn-notify-sock");
(void) sd_event_source_set_description(*ret_notify_event_source, "vmspawn-notify-sock");
return 0;
}
static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
pid_t pid;
PidRef *pidref = userdata;
int r;
pid = PTR_TO_PID(userdata);
if (pid > 0) {
/* TODO: actually talk to qemu and ask the guest to shutdown here */
if (kill(pid, SIGKILL) >= 0) {
/* TODO: actually talk to qemu and ask the guest to shutdown here */
if (pidref) {
r = pidref_kill(pidref, SIGKILL);
if (r < 0)
log_warning_errno(r, "Failed to kill qemu, terminating: %m");
else {
log_info("Trying to halt qemu. Send SIGTERM again to trigger vmspawn to immediately terminate.");
sd_event_source_set_userdata(s, NULL);
return 0;
@ -450,7 +497,10 @@ static int run_virtual_machine(void) {
use_kvm = r;
}
r = find_ovmf_config(arg_secure_boot, &ovmf_config);
if (arg_firmware)
r = load_ovmf_config(arg_firmware, &ovmf_config);
else
r = find_ovmf_config(arg_secure_boot, &ovmf_config);
if (r < 0)
return log_error_errno(r, "Failed to find OVMF config: %m");
@ -473,7 +523,7 @@ static int run_virtual_machine(void) {
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)
if (asprintf(&mem, "%" PRIu64, DIV_ROUND_UP(arg_qemu_mem, U64_MB)) < 0)
return log_oom();
cmdline = strv_new(
@ -558,7 +608,7 @@ static int run_virtual_machine(void) {
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "if=pflash,format=raw,readonly=on,file=%s", ovmf_config->path);
r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), ovmf_config->path);
if (r < 0)
return log_oom();
@ -596,7 +646,7 @@ static int run_virtual_machine(void) {
if (r < 0)
return log_oom();
r = strv_extendf(&cmdline, "file=%s,if=pflash,format=raw", ovmf_vars_to);
r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", ovmf_vars_to, ovmf_config_format(ovmf_config));
if (r < 0)
return log_oom();
}
@ -663,15 +713,16 @@ static int run_virtual_machine(void) {
(void) sd_event_set_watchdog(event, true);
pid_t child_pid;
r = safe_fork_full(
_cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL;
r = pidref_safe_fork_full(
qemu_binary,
NULL,
/* stdio_fds= */ NULL,
&child_vsock_fd, 1, /* pass the vsock fd to qemu */
FORK_CLOEXEC_OFF,
&child_pid);
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE,
&child_pidref);
if (r < 0)
return log_error_errno(r, "Failed to fork off %s: %m", qemu_binary);
return r;
if (r == 0) {
/* set TERM and LANG if they are missing */
if (setenv("TERM", "vt220", 0) < 0)
@ -680,11 +731,14 @@ static int run_virtual_machine(void) {
if (setenv("LANG", "C.UTF-8", 0) < 0)
return log_oom();
execve(qemu_binary, cmdline, environ);
execv(qemu_binary, cmdline);
log_error_errno(errno, "Failed to execve %s: %m", qemu_binary);
_exit(EXIT_FAILURE);
}
/* Close the vsock fd we passed to qemu in the parent. We don't need it anymore. */
child_vsock_fd = safe_close(child_vsock_fd);
int exit_status = INT_MAX;
if (use_vsock) {
r = setup_notify_parent(event, vsock_fd, &exit_status, &notify_event_source);
@ -693,13 +747,13 @@ static int run_virtual_machine(void) {
}
/* shutdown qemu when we are shutdown */
(void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, PID_TO_PTR(child_pid));
(void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, PID_TO_PTR(child_pid));
(void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
(void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
(void) sd_event_add_signal(event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
/* Exit when the child exits */
(void) sd_event_add_child(event, NULL, child_pid, WEXITED, on_child_exit, NULL);
(void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL);
r = sd_event_loop(event);
if (r < 0)
@ -757,6 +811,16 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
if (!arg_quiet) {
_cleanup_free_ char *u = NULL;
(void) terminal_urlify_path(arg_image, arg_image, &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 ?: arg_image, ansi_normal(),
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
return run_virtual_machine();