]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sleep: offer hibernation only if the kernel image still exists
authorLennart Poettering <lennart@poettering.net>
Wed, 25 Jul 2018 20:19:44 +0000 (22:19 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 26 Jul 2018 09:01:29 +0000 (11:01 +0200)
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
src/shared/sleep-config.c

index 13298cc85530bcd05d90ded32e9a3f7c0efe362a..38e36f20b32dc2bdba620a81345fdece83ed579b 100644 (file)
@@ -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;
index 2577dd84c85570b6de1aa33a28c7648dc1de7b47..cbaef4c8575828ba5812ac829a47ae3dcb4e2c92 100644 (file)
@@ -9,9 +9,12 @@
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/utsname.h>
 #include <syslog.h>
 #include <unistd.h>
 
+#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;