From: Sam Leonard Date: Thu, 25 Apr 2024 16:16:20 +0000 (+0100) Subject: vmspawn: forward signals to VM PID 1 via D-BUS when available X-Git-Tag: v256-rc2~65^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=521e9286aa54fd9f225e12376a6e0ab538737c2d;p=thirdparty%2Fsystemd.git vmspawn: forward signals to VM PID 1 via D-BUS when available Allow to gracefully shutdown by initiating the operation from the guest --- diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 51f622d0a8e..05661961ed0 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -18,6 +18,9 @@ #include "architecture.h" #include "bootspec.h" #include "build.h" +#include "bus-internal.h" +#include "bus-locator.h" +#include "bus-wait-for-jobs.h" #include "chase.h" #include "common-signal.h" #include "copy.h" @@ -58,6 +61,7 @@ #include "signal-util.h" #include "socket-util.h" #include "stat-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "time-util.h" @@ -71,6 +75,12 @@ #define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) +typedef struct SSHInfo { + unsigned cid; + char *private_key_path; + unsigned port; +} SSHInfo; + static bool arg_quiet = false; static PagerFlags arg_pager_flags = 0; static char *arg_directory = NULL; @@ -697,11 +707,61 @@ static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_eve return 0; } +static int bus_open_in_machine(sd_bus **ret, unsigned cid, unsigned port, const char *private_key_path) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *ssh_escaped = NULL, *bus_address = NULL; + char port_str[DECIMAL_STR_MAX(unsigned)], cid_str[DECIMAL_STR_MAX(unsigned)]; + int r; + + assert(ret); + assert(private_key_path); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + ssh_escaped = bus_address_escape(ssh); + if (!ssh_escaped) + return -ENOMEM; + + xsprintf(port_str, "%u", port); + xsprintf(cid_str, "%u", cid); + + bus_address = strjoin( + "unixexec:path=", ssh_escaped, + /* -x: Disable X11 forwarding + * -T: Disable PTY allocation */ + ",argv1=-xT", + ",argv2=-o,argv3=IdentitiesOnly yes", + ",argv4=-o,argv5=IdentityFile=", private_key_path, + ",argv6=-p,argv7=", port_str, + ",argv8=--", + ",argv9=root@vsock/", cid_str, + ",argv10=systemd-stdio-bridge" + ); + if (!bus_address) + return -ENOMEM; + + free_and_replace(bus->address, bus_address); + bus->bus_client = true; + bus->trusted = true; + bus->runtime_scope = RUNTIME_SCOPE_SYSTEM; + bus->is_local = false; + + r = sd_bus_start(bus); + if (r < 0) + return r; + + *ret = TAKE_PTR(bus); + return 0; +} + static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { PidRef *pidref = userdata; int r; - /* TODO: actually talk to qemu and ask the guest to shutdown here */ + /* Backup method to shut down the VM when D-BUS access over SSH is not available */ if (pidref) { r = pidref_kill(pidref, SIGKILL); @@ -718,6 +778,61 @@ static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo return 0; } +static int forward_signal_to_vm_pid1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + SSHInfo *ssh_info = ASSERT_PTR(userdata); + const char *vm_pid1; + int r; + + assert(s); + assert(si); + + r = bus_open_in_machine(&bus, ssh_info->cid, ssh_info->port, ssh_info->private_key_path); + if (r < 0) + return log_error_errno(r, "Failed to connect to VM to forward signal: %m"); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + r = bus_call_method( + bus, + bus_systemd_mgr, + "GetUnitByPID", + &error, + NULL, + ""); + if (r < 0) + return log_error_errno(r, "Failed to get init process of VM: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &vm_pid1); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, vm_pid1, /* quiet */ false, NULL); + if (r < 0) + return r; + + r = bus_call_method( + bus, + bus_systemd_mgr, + "KillUnit", + &error, + NULL, + "ssi", + vm_pid1, + "leader", + si->ssi_signo); + if (r < 0) + return log_error_errno(r, "Failed to forward signal to PID 1 of the VM: %s", bus_error_message(&error, r)); + log_info("Sent signal %"PRIu32" to the VM's PID 1.", si->ssi_signo); + + return 0; +} + static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { sd_event_exit(sd_event_source_get_event(s), 0); return 0; @@ -1200,6 +1315,7 @@ static int generate_ssh_keypair(const char *key_path, const char *key_type) { } static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { + SSHInfo ssh_info; /* Used when talking to pid1 via SSH, but must survive until the function ends. */ _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL, *kernel = NULL; @@ -1985,9 +2101,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { return log_error_errno(r, "Failed to setup event loop to handle VSOCK notify events: %m"); } - /* shutdown qemu when we are shutdown */ - (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); + /* If we have the vsock address and the SSH key, ask pid1 inside the guest to shutdown. */ + if (child_cid != VMADDR_CID_ANY && ssh_private_key_path) { + ssh_info = (SSHInfo) { + .cid = child_cid, + .private_key_path = ssh_private_key_path, + .port = 22, + }; + + (void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info); + (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info); + } else { + /* As a fallback in case SSH cannot be used, send a shutdown signal to the VMM instead. */ + (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);