From edda44605f06a41fb86b7ab8128dcf99161d2344 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Jul 2018 22:19:44 +0200 Subject: [PATCH] sleep: offer hibernation only if the kernel image still exists This makes hibernation unavailable if the kernel image we are currently running was removed. This is supposed to be superficial protection against hibernating a system we can never return from because the kernel has been updated and the kernel we currently run is not available anymore. We look at a couple of places for the kernel, which should cover all distributions I know off. Should I have missed a path I am sure people will quickly notice and we can add more places to check. (or maybe convince those distros to stick their kernels at a standard place) --- src/login/logind-dbus.c | 10 +++--- src/shared/sleep-config.c | 76 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 13298cc855..38e36f20b3 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1770,11 +1770,11 @@ static int method_do_shutdown_or_sleep( if (sleep_verb) { r = can_sleep(sleep_verb); if (r == -ENOSPC) - return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Not enough swap space for hibernation"); + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not enough swap space for hibernation"); + if (r == -ENOMEDIUM) + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Kernel image has been removed, can't hibernate"); if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Sleep verb \"%s\" not supported", sleep_verb); + return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Sleep verb \"%s\" not supported", sleep_verb); if (r < 0) return r; } @@ -2199,7 +2199,7 @@ static int method_can_shutdown_or_sleep( if (sleep_verb) { r = can_sleep(sleep_verb); - if (IN_SET(r, 0, -ENOSPC)) + if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM)) return sd_bus_reply_method_return(message, "s", "na"); if (r < 0) return r; diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 2577dd84c8..cbaef4c857 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -9,9 +9,12 @@ #include #include #include +#include #include #include +#include "sd-id128.h" + #include "alloc-util.h" #include "conf-parser.h" #include "def.h" @@ -270,6 +273,72 @@ static bool enough_swap_for_hibernation(void) { return r; } +static int kernel_exists(void) { + struct utsname u; + sd_id128_t m; + int i, r; + + /* Do some superficial checks whether the kernel we are currently running is still around. If it isn't we + * shouldn't offer hibernation as we couldn't possible resume from hibernation again. Of course, this check is + * very superficial, as the kernel's mere existance is hardly enough to know whether the hibernate/resume cycle + * will succeed. However, the common case of kernel updates can be caught this way, and it's definitely worth + * covering that. */ + + for (i = 0;; i++) { + _cleanup_free_ char *path = NULL; + + switch (i) { + + case 0: + /* First, let's look in /lib/modules/`uname -r`/vmlinuz. This is where current Fedora places + * its RPM-managed kernels. It's a good place, as it means compiled vendor code is monopolized + * in /usr, and then the kernel image is stored along with its modules in the same + * hierarchy. It's also what our 'kernel-install' script is written for. */ + if (uname(&u) < 0) + return log_debug_errno(errno, "Failed to acquire kernel release: %m"); + + path = strjoin("/lib/modules/", u.release, "/vmlinuz"); + break; + + case 1: + /* Secondly, let's look in /boot/vmlinuz-`uname -r`. This is where older Fedora and other + * distributions tend to place the kernel. */ + path = strjoin("/boot/vmlinuz-", u.release); + break; + + case 2: + /* For the other cases, we look in the EFI/boot partition, at the place where our + * "kernel-install" script copies the kernel on install by default. */ + r = sd_id128_get_machine(&m); + if (r < 0) + return log_debug_errno(r, "Failed to read machine ID: %m"); + + (void) asprintf(&path, "/efi/" SD_ID128_FORMAT_STR "/%s/linux", SD_ID128_FORMAT_VAL(m), u.release); + break; + case 3: + (void) asprintf(&path, "/boot/" SD_ID128_FORMAT_STR "/%s/linux", SD_ID128_FORMAT_VAL(m), u.release); + break; + case 4: + (void) asprintf(&path, "/boot/efi/" SD_ID128_FORMAT_STR "/%s/linux", SD_ID128_FORMAT_VAL(m), u.release); + break; + + default: + return false; + } + + if (!path) + return -ENOMEM; + + log_debug("Testing whether %s exists.", path); + + if (access(path, F_OK) >= 0) + return true; + + if (errno != ENOENT) + log_debug_errno(errno, "Failed to determine whether '%s' exists, ignoring: %m", path); + } +} + int read_fiemap(int fd, struct fiemap **ret) { _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL; struct stat statinfo; @@ -367,7 +436,7 @@ static bool can_s2h(void) { FOREACH_STRING(p, "suspend", "hibernate") { r = can_sleep(p); - if (IN_SET(r, 0, -ENOSPC)) { + if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM)) { log_debug("Unable to %s system.", p); return false; } @@ -397,6 +466,11 @@ int can_sleep(const char *verb) { if (streq(verb, "suspend")) return true; + if (kernel_exists() <= 0) { + log_debug_errno(errno, "Couldn't find kernel, not offering hibernation."); + return -ENOMEDIUM; + } + if (!enough_swap_for_hibernation()) return -ENOSPC;