]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared/sleep-config: forbid hibernation if resume= is not configured
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 26 Sep 2018 08:15:46 +0000 (10:15 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 8 Oct 2018 16:20:58 +0000 (18:20 +0200)
src/login/logind-dbus.c
src/shared/sleep-config.c

index 5035bb5bdfed1c57960fa486a492060a05c6ad89..8e22538ac31e88a07da875aa573e5d575dcd52bb 100644 (file)
@@ -1773,6 +1773,8 @@ static int method_do_shutdown_or_sleep(
                         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 == -EADV)
+                        return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Resume not configured, 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);
                 if (r < 0)
@@ -2199,7 +2201,7 @@ static int method_can_shutdown_or_sleep(
 
         if (sleep_verb) {
                 r = can_sleep(sleep_verb);
-                if (IN_SET(r,  0, -ENOSPC, -ENOMEDIUM))
+                if (IN_SET(r,  0, -ENOSPC, -ENOMEDIUM, -EADV))
                         return sd_bus_reply_method_return(message, "s", "na");
                 if (r < 0)
                         return r;
index 2ffd32bf999155bb83d861a6da23855a8c13cd4b..fba6851d28d96cc7bc6aa73b0dcefa1f237bd328 100644 (file)
@@ -16,6 +16,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "bootspec.h"
 #include "conf-parser.h"
 #include "def.h"
 #include "env-util.h"
@@ -25,6 +26,7 @@
 #include "macro.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "proc-cmdline.h"
 #include "sleep-config.h"
 #include "string-util.h"
 #include "strv.h"
@@ -266,12 +268,94 @@ static bool enough_swap_for_hibernation(void) {
         }
 
         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
-        log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
-                  r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
+        log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
+                  r ? "Enough" : "Not enough", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
 
         return r;
 }
 
+static int check_resume_keys(const char *key, const char *value, void *data) {
+        assert_se(key);
+        assert_se(data);
+
+        int *resume = data;
+
+        if (*resume == 0)
+                /* Exit if we already know we can't resume. */
+                return 0;
+
+        if (streq(key, "noresume")) {
+                log_debug("Found \"noresume\" on the kernel command line, hibernation is disabled.");
+                *resume = 0;
+
+        } else if (streq(key, "resume")) {
+                log_debug("Found resume= option on the kernel command line, hibernation is possible.");
+                *resume = 1;
+        }
+
+        return 0;
+}
+
+static int resume_configured_in_options(const char *options) {
+        int resume = -1, r;
+
+        /* We don't use PROC_CMDLINE_STRIP_RD_PREFIX here, so rd.resume is *not* supported. */
+        r = proc_cmdline_parse_given(options, check_resume_keys, &resume, 0);
+        if (r < 0)
+                return r;
+
+        if (resume < 0)
+                log_debug("Couldn't find resume= option, hibernation is disabled.");
+        return resume > 0;
+}
+
+static int resume_configured(void) {
+        _cleanup_(boot_config_free) BootConfig config = {};
+        const BootEntry *e;
+        int r;
+
+        /* Check whether a valid resume= option is present. If possible, we query the boot options
+         * for the default kernel. If the system is not using sd-boot, fall back to checking the
+         * current kernel command line. This is not perfect, but should suffice for most cases. */
+
+        r = find_default_boot_entry(NULL, NULL, &config, &e);
+        if (r == -ENOKEY)
+                log_debug_errno(r, "Cannot find the ESP partition mount point, falling back to other checks.");
+        else if (r < 0)
+                return log_debug_errno(r, "Cannot read boot configuration from ESP, assuming hibernation is not possible.");
+        else {
+                _cleanup_free_ char *options = NULL;
+
+                options = strv_join(e->options, " ");
+                if (!options)
+                        return log_oom();
+
+                r = resume_configured_in_options(options);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse kernel options in \"%s\": %m",
+                                               strnull(e->path));
+                return r;
+        }
+
+        /* If we can't figure out the default boot entry, let's fall back to current kernel cmdline */
+        _cleanup_free_ char *line = NULL;
+        r = proc_cmdline(&line);
+        if (IN_SET(r, -EPERM, -EACCES, -ENOENT))
+                log_debug_errno(r, "Cannot access /proc/cmdline: %m");
+        else if (r < 0)
+                return log_error_errno(r, "Failed to query /proc/cmdline: %m");
+        else {
+                r = resume_configured_in_options(line);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse kernel proc cmdline: %m");
+
+                return r;
+        }
+
+        log_debug("Couldn't detect any resume mechanism, hibernation is disabled.");
+        return false;
+}
+
 static int kernel_exists(void) {
         struct utsname u;
         sd_id128_t m;
@@ -435,7 +519,7 @@ static bool can_s2h(void) {
 
         FOREACH_STRING(p, "suspend", "hibernate") {
                 r = can_sleep(p);
-                if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM)) {
+                if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM, -EADV)) {
                         log_debug("Unable to %s system.", p);
                         return false;
                 }
@@ -473,5 +557,10 @@ int can_sleep(const char *verb) {
         if (!enough_swap_for_hibernation())
                 return -ENOSPC;
 
+        r = resume_configured();
+        if (r <= 0)
+                /* We squash all errors (e.g. EPERM) into a single value for reporting. */
+                return -EADV;
+
         return true;
 }