From 4bb2e9d466a0f2ed28608e09639b2cedeb08a814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 21 Oct 2017 10:55:32 +0200 Subject: [PATCH] systemctl: make sure the kernel is loaded before kexec'ing We just load the same kernel that would be loaded by default by sd-boot, with the same options. Changing the kernel or initramfs or options is left for later. Now we will refuse to continue if loading fails. This makes 'systemctl kexec' more predictable: it will not fall back to normal reboot if the kernel is not loaded. --- src/systemctl/systemctl.c | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index b8c0718686421..5ec977cb96389 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include "sd-login.h" #include "alloc-util.h" +#include "bootspec.h" #include "bus-common-errors.h" #include "bus-error.h" #include "bus-message.h" @@ -143,6 +145,7 @@ static const char *arg_kill_who = NULL; static int arg_signal = SIGTERM; static char *arg_root = NULL; static usec_t arg_when = 0; +static char *arg_esp_path = NULL; static char *argv_cmdline = NULL; static enum action { ACTION_SYSTEMCTL, @@ -3497,6 +3500,75 @@ static int prepare_firmware_setup(void) { return logind_prepare_firmware_setup(); } +static int load_kexec_kernel(void) { + _cleanup_(boot_config_free) BootConfig config = {}; + _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; + const BootEntry *e; + pid_t pid; + int r; + + if (kexec_loaded()) { + log_debug("Kexec kernel already loaded."); + return 0; + } + + r = find_esp(&arg_esp_path, NULL, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Cannot find the ESP partition mount point: %m"); + + r = boot_entries_load_config(arg_esp_path, &config); + if (r < 0) + return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", + arg_esp_path); + + if (config.default_entry < 0) { + log_error("No entry suitable as default, refusing to guess."); + return -ENOENT; + } + e = &config.entries[config.default_entry]; + + if (strv_length(e->initrd) > 1) { + log_error("Boot entry specifies multiple initrds, which is not supported currently."); + return -EINVAL; + } + + kernel = path_join(NULL, arg_esp_path, e->kernel); + if (!strv_isempty(e->initrd)) + initrd = path_join(NULL, arg_esp_path, *e->initrd); + options = strv_join(e->options, " "); + if (!options) + return log_oom(); + + log_debug("%s kexec kernel %s initrd %s options \"%s\".", + arg_dry_run ? "Would load" : "loading", + kernel, initrd, options); + if (arg_dry_run) + return 0; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + const char* const args[] = { + KEXEC, + "--load", kernel, + "--append", options, + initrd ? "--initrd" : NULL, initrd, + NULL }; + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execv(args[0], (char * const *) args); + _exit(EXIT_FAILURE); + } else + return wait_for_terminate_and_warn("kexec", pid, true); +} + static int set_exit_code(uint8_t code) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus; @@ -3549,6 +3621,11 @@ static int start_special(int argc, char *argv[], void *userdata) { if (r < 0) return r; + } else if (a == ACTION_KEXEC) { + r = load_kexec_kernel(); + if (r < 0) + return r; + } else if (a == ACTION_EXIT && argc > 1) { uint8_t code; @@ -8723,6 +8800,7 @@ int main(int argc, char*argv[]) { strv_free(arg_wall); free(arg_root); + free(arg_esp_path); /* Note that we return r here, not EXIT_SUCCESS, so that we can implement the LSB-like return codes */ return r < 0 ? EXIT_FAILURE : r;