diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index cd46f823ad..351b7c0772 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -135,7 +135,18 @@ - + + + PATH + + Takes an absolute path, or a relative path beginning with + ./. 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 list is specified lists all discovered firmwares. + + + + System Identity Options diff --git a/src/libsystemd/sd-event/event-util.c b/src/libsystemd/sd-event/event-util.c index a3101227af..2338df1d62 100644 --- a/src/libsystemd/sd-event/event-util.c +++ b/src/libsystemd/sd-event/event-util.c @@ -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); +} diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h index c185584412..6259d5ae25 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 445d85da6f..c48b51323b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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); diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index 16ece31809..79e1e5399a 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -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; } diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index c931e66aac..f2d1b621e2 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -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); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index e6ab63a6cd..a08bc4c679 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -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, ¬ify_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();