From 5fdf2d51c244288ac41443d1bd81365fab7b7b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 26 Sep 2018 10:15:46 +0200 Subject: [PATCH] shared/sleep-config: forbid hibernation if resume= is not configured --- src/login/logind-dbus.c | 4 +- src/shared/sleep-config.c | 95 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 5035bb5bdf..8e22538ac3 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -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; diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 2ffd32bf99..fba6851d28 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -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; }