]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #10262 from keszybz/hibres-disable
authorLennart Poettering <lennart@poettering.net>
Mon, 8 Oct 2018 19:39:54 +0000 (21:39 +0200)
committerGitHub <noreply@github.com>
Mon, 8 Oct 2018 19:39:54 +0000 (21:39 +0200)
Switches to disable hibernation and/or resuming

18 files changed:
TODO
docs/ENVIRONMENT.md
man/systemd-hibernate-resume-generator.xml
man/systemd-sleep.conf.xml
src/basic/proc-cmdline.c
src/basic/proc-cmdline.h
src/basic/util.c
src/boot/bootctl.c
src/hibernate-resume/hibernate-resume-generator.c
src/login/logind-dbus.c
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/sleep-config.c
src/shared/sleep-config.h
src/sleep/sleep.c
src/systemctl/systemctl.c
src/test/test-proc-cmdline.c
src/test/test-sleep.c

diff --git a/TODO b/TODO
index 13af83dfe62b930c4e87271138b4d0c7f74d45d7..f72dd0f7181a0fc9a4e340d1a38a8a4531887b21 100644 (file)
--- a/TODO
+++ b/TODO
@@ -96,11 +96,8 @@ Features:
 
   1. add resume_offset support to the resume code (i.e. support swap files
      properly)
-  2. check of swap is on weird storage and refuse if so
-  3. add env-var based option to disable hibernation
-  4. figure out what to do with swap-on-luks
-  5. add autodetection of hibernation images, and add "noresume" to disable
-     this
+  2. check if swap is on weird storage and refuse if so
+  3. add autodetection of hibernation images
 
 * cgroups: use inotify to get notified when somebody else modifies cgroups
   owned by us, then log a friendly warning.
index 0c991520c4a69d67b6d4e34b6a22ffc6082806c7..b4363ba58bcb12f32a5c9ff2bcc3f7c8179b300b 100644 (file)
@@ -37,6 +37,10 @@ All tools:
   useful for debugging, in order to test generators and other code against
   specific kernel command lines.
 
+* `$SYSTEMD_IN_INITRD` — takes a boolean. If set, overrides initrd detection.
+  This is useful for debugging and testing initrd-only programs in the main
+  system.
+
 * `$SYSTEMD_BUS_TIMEOUT=SECS` — specifies the maximum time to wait for method call
   completion. If no time unit is specified, assumes seconds. The usual other units
   are understood, too (us, ms, s, min, h, d, w, month, y). If it is not set or set
index 86d45dc4af3c89b560ad2327d6ad56174580d6c6..8f0cc5d0445a6d5861e47fb73277c95b65c4fd55 100644 (file)
   <refsect1>
     <title>Description</title>
 
-    <para><filename>systemd-hibernate-resume-generator</filename> is a
-    generator that instantiates
+    <para><command>systemd-hibernate-resume-generator</command> is a
+    generator that initiates the procedure to resume the system from hibernation.
+    It instantiates the
     <citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
     unit according to the value of <option>resume=</option> parameter
-    specified on the kernel command line.</para>
+    specified on the kernel command line, which will instruct the kernel
+    to resume the system from the hibernation image on that device.</para>
   </refsect1>
 
   <refsect1>
         supported.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>noresume</varname></term>
+
+        <listitem><para>Do not try to resume from hibernation. If this parameter is
+        present, <varname>resume=</varname> is ignored.</para></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 10bbc8c76a0d43a36540ded1c255b57a83820d14..96e6d5e452369e95edc3190a5ac790a34e651928 100644 (file)
     <filename>sleep.conf.d</filename> file:</para>
 
     <variablelist class='systemd-directives'>
+      <varlistentry>
+        <term><varname>AllowSuspend=</varname></term>
+        <term><varname>AllowHibernation=</varname></term>
+        <term><varname>AllowSuspendThenHibernate=</varname></term>
+        <term><varname>AllowHybridSleep=</varname></term>
+
+        <listitem><para>By default any power-saving mode is advertised if possible (i.e.
+        the kernel supports that mode, the necessary resources are available). Those
+        switches can be used to disable specific modes.</para>
+
+        <para>If <varname>AllowHibernation=no</varname> or <varname>AllowSuspend=no</varname> is
+        used, this implies <varname>AllowSuspendThenHibernate=no</varname> and
+        <varname>AllowHybridSleep=no</varname>, since those methods use both suspend and hibernation
+        internally. <varname>AllowSuspendThenHibernate=yes</varname> and
+        <varname>AllowHybridSleep=yes</varname> can be used to override and enable those specific
+        modes.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>SuspendMode=</varname></term>
         <term><varname>HibernateMode=</varname></term>
index add481c2ae61fd9c91694ef0a3985a5816f629fa..b386c705e118e4ed34612c603c5db9a422c14e83 100644 (file)
@@ -39,18 +39,12 @@ int proc_cmdline(char **ret) {
                 return read_one_line_file("/proc/cmdline", ret);
 }
 
-int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
-
-        _cleanup_free_ char *line = NULL;
+int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
         const char *p;
         int r;
 
         assert(parse_item);
 
-        r = proc_cmdline(&line);
-        if (r < 0)
-                return r;
-
         p = line;
         for (;;) {
                 _cleanup_free_ char *word = NULL;
@@ -86,15 +80,26 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned fla
         return 0;
 }
 
-static bool relaxed_equal_char(char a, char b) {
+int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) {
+        _cleanup_free_ char *line = NULL;
+        int r;
 
+        assert(parse_item);
+
+        r = proc_cmdline(&line);
+        if (r < 0)
+                return r;
+
+        return proc_cmdline_parse_given(line, parse_item, data, flags);
+}
+
+static bool relaxed_equal_char(char a, char b) {
         return a == b ||
                 (a == '_' && b == '-') ||
                 (a == '-' && b == '_');
 }
 
 char *proc_cmdline_key_startswith(const char *s, const char *prefix) {
-
         assert(s);
         assert(prefix);
 
index 4a9e6e0f6201173729a2411de00c22e92d3a2c0a..ffc45fddb964113e33a020f68f5f9d9f6d5a5313 100644 (file)
@@ -14,6 +14,7 @@ typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *da
 
 int proc_cmdline(char **ret);
 
+int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, unsigned flags);
 int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, unsigned flags);
 
 int proc_cmdline_get_key(const char *parameter, unsigned flags, char **value);
index 081c63c898c12318a3c7379e82a3ddf33710d21a..0da963f4af876548846ebc02860aadcefdb07003 100644 (file)
@@ -23,6 +23,7 @@
 #include "def.h"
 #include "device-nodes.h"
 #include "dirent-util.h"
+#include "env-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
@@ -106,6 +107,7 @@ int prot_from_flags(int flags) {
 
 bool in_initrd(void) {
         struct statfs s;
+        int r;
 
         if (saved_in_initrd >= 0)
                 return saved_in_initrd;
@@ -120,9 +122,16 @@ bool in_initrd(void) {
          * emptying when transititioning to the main systemd.
          */
 
-        saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
-                          statfs("/", &s) >= 0 &&
-                          is_temporary_fs(&s);
+        r = getenv_bool_secure("SYSTEMD_IN_INITRD");
+        if (r < 0 && r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
+
+        if (r >= 0)
+                saved_in_initrd = r > 0;
+        else
+                saved_in_initrd = access("/etc/initrd-release", F_OK) >= 0 &&
+                                  statfs("/", &s) >= 0 &&
+                                  is_temporary_fs(&s);
 
         return saved_in_initrd;
 }
index 8680b3a6aeb5c66451be328d9a22101451b6bd32..827927f18b5aa709b7256952f7ff2d3b3423a32d 100644 (file)
@@ -1029,6 +1029,8 @@ static int verb_list(int argc, char *argv[], void *userdata) {
                        ansi_highlight_green(),
                        n == (unsigned) config.default_entry ? " (default)" : "",
                        ansi_normal());
+                if (e->id)
+                        printf("           id: %s\n", e->id);
                 if (e->version)
                         printf("      version: %s\n", e->version);
                 if (e->machine_id)
index 4b79e3def4827b2b0205df18061db7ab92b592aa..036493a38987224655823f40aa89ef52b45ed5b8 100644 (file)
@@ -15,6 +15,7 @@
 
 static const char *arg_dest = "/tmp";
 static char *arg_resume_device = NULL;
+static bool arg_noresume = false;
 
 static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
 
@@ -28,8 +29,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (!s)
                         return log_oom();
 
-                free(arg_resume_device);
-                arg_resume_device = s;
+                free_and_replace(arg_resume_device, s);
+
+        } else if (streq(key, "noresume")) {
+                if (value) {
+                        log_warning("\"noresume\" kernel command line switch specified with an argument, ignoring.");
+                        return 0;
+                }
+
+                arg_noresume = true;
         }
 
         return 0;
@@ -60,6 +68,13 @@ static int process_resume(void) {
 int main(int argc, char *argv[]) {
         int r = 0;
 
+        log_set_prohibit_ipc(true);
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
         if (argc > 1 && argc != 4) {
                 log_error("This program takes three or no arguments.");
                 return EXIT_FAILURE;
@@ -68,21 +83,21 @@ int main(int argc, char *argv[]) {
         if (argc > 1)
                 arg_dest = argv[1];
 
-        log_set_prohibit_ipc(true);
-        log_set_target(LOG_TARGET_AUTO);
-        log_parse_environment();
-        log_open();
-
-        umask(0022);
-
         /* Don't even consider resuming outside of initramfs. */
-        if (!in_initrd())
+        if (!in_initrd()) {
+                log_debug("Not running in an initrd, quitting.");
                 return EXIT_SUCCESS;
+        }
 
         r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
         if (r < 0)
                 log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
 
+        if (arg_noresume) {
+                log_notice("Found \"noresume\" on the kernel command line, quitting.");
+                return EXIT_SUCCESS;
+        }
+
         r = process_resume();
         free(arg_resume_device);
 
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 8d5a4211448298c3f8450a4b8ad1941219f36206..5d8471c51a88144b6eddeffd68e5d7b2ad3ec285 100644 (file)
@@ -23,7 +23,8 @@
 void boot_entry_free(BootEntry *entry) {
         assert(entry);
 
-        free(entry->filename);
+        free(entry->id);
+        free(entry->path);
         free(entry->title);
         free(entry->show_title);
         free(entry->version);
@@ -53,8 +54,12 @@ int boot_entry_load(const char *path, BootEntry *entry) {
         }
 
         b = basename(path);
-        tmp.filename = strndup(b, c - b);
-        if (!tmp.filename)
+        tmp.id = strndup(b, c - b);
+        if (!tmp.id)
+                return log_oom();
+
+        tmp.path = strdup(path);
+        if (!tmp.path)
                 return log_oom();
 
         f = fopen(path, "re");
@@ -203,7 +208,7 @@ int boot_loader_read_conf(const char *path, BootConfig *config) {
 }
 
 static int boot_entry_compare(const BootEntry *a, const BootEntry *b) {
-        return str_verscmp(a->filename, b->filename);
+        return str_verscmp(a->id, b->id);
 }
 
 int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) {
@@ -300,7 +305,7 @@ static int boot_entries_uniquify(BootEntry *entries, size_t n_entries) {
         /* Add file name to non-unique titles */
         for (i = 0; i < n_entries; i++)
                 if (arr[i]) {
-                        r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].filename);
+                        r = asprintf(&s, "%s (%s)", boot_entry_title(entries + i), entries[i].id);
                         if (r < 0)
                                 return -ENOMEM;
 
@@ -317,30 +322,30 @@ static int boot_entries_select_default(const BootConfig *config) {
 
         if (config->entry_oneshot)
                 for (i = config->n_entries - 1; i >= 0; i--)
-                        if (streq(config->entry_oneshot, config->entries[i].filename)) {
-                                log_debug("Found default: filename \"%s\" is matched by LoaderEntryOneShot",
-                                          config->entries[i].filename);
+                        if (streq(config->entry_oneshot, config->entries[i].id)) {
+                                log_debug("Found default: id \"%s\" is matched by LoaderEntryOneShot",
+                                          config->entries[i].id);
                                 return i;
                         }
 
         if (config->entry_default)
                 for (i = config->n_entries - 1; i >= 0; i--)
-                        if (streq(config->entry_default, config->entries[i].filename)) {
-                                log_debug("Found default: filename \"%s\" is matched by LoaderEntryDefault",
-                                          config->entries[i].filename);
+                        if (streq(config->entry_default, config->entries[i].id)) {
+                                log_debug("Found default: id \"%s\" is matched by LoaderEntryDefault",
+                                          config->entries[i].id);
                                 return i;
                         }
 
         if (config->default_pattern)
                 for (i = config->n_entries - 1; i >= 0; i--)
-                        if (fnmatch(config->default_pattern, config->entries[i].filename, FNM_CASEFOLD) == 0) {
-                                log_debug("Found default: filename \"%s\" is matched by pattern \"%s\"",
-                                          config->entries[i].filename, config->default_pattern);
+                        if (fnmatch(config->default_pattern, config->entries[i].id, FNM_CASEFOLD) == 0) {
+                                log_debug("Found default: id \"%s\" is matched by pattern \"%s\"",
+                                          config->entries[i].id, config->default_pattern);
                                 return i;
                         }
 
         if (config->n_entries > 0)
-                log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].filename);
+                log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
         else
                 log_debug("Found no default boot entry :(");
 
@@ -598,3 +603,37 @@ found:
 
         return 0;
 }
+
+int find_default_boot_entry(
+                const char *esp_path,
+                char **esp_where,
+                BootConfig *config,
+                const BootEntry **e) {
+
+        _cleanup_free_ char *where = NULL;
+        int r;
+
+        assert(config);
+        assert(e);
+
+        r = find_esp_and_warn(esp_path, false, &where, NULL, NULL, NULL, NULL);
+        if (r < 0)
+                return r;
+
+        r = boot_entries_load_config(where, config);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", where);
+
+        if (config->default_entry < 0) {
+                log_error("No entry suitable as default, refusing to guess.");
+                return -ENOENT;
+        }
+
+        *e = &config->entries[config->default_entry];
+        log_debug("Found default boot entry in file \"%s\"", (*e)->path);
+
+        if (esp_where)
+                *esp_where = TAKE_PTR(where);
+
+        return 0;
+}
index f47c073a4688ca11227f0902856ea4e7239330aa..c39d773cb3402dd4b0b58c89e239d34b1b0470a8 100644 (file)
@@ -2,11 +2,15 @@
 
 #pragma once
 
-#include <stdlib.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/types.h>
 
-typedef struct BootEntry {
-        char *filename;
+#include "sd-id128.h"
 
+typedef struct BootEntry {
+        char *id;       /* This is the file basename without extension */
+        char *path;     /* This is the full path to the file */
         char *title;
         char *show_title;
         char *version;
@@ -44,7 +48,9 @@ void boot_config_free(BootConfig *config);
 int boot_entries_load_config(const char *esp_path, BootConfig *config);
 
 static inline const char* boot_entry_title(const BootEntry *entry) {
-        return entry->show_title ?: entry->title ?: entry->filename;
+        return entry->show_title ?: entry->title ?: entry->id;
 }
 
 int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid);
+
+int find_default_boot_entry(const char *esp_path, char **esp_where, BootConfig *config, const BootEntry **e);
index 2ffd32bf999155bb83d861a6da23855a8c13cd4b..67783991887de1eab32e36f50728cd4bcdbaeb24 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"
 #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"
 
-int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
-
+int parse_sleep_config(const char *verb, bool *ret_allow, char ***ret_modes, char ***ret_states, usec_t *ret_delay) {
+        int allow_suspend = -1, allow_hibernate = -1,
+            allow_s2h = -1, allow_hybrid_sleep = -1;
+        bool allow;
         _cleanup_strv_free_ char
                 **suspend_mode = NULL, **suspend_state = NULL,
                 **hibernate_mode = NULL, **hibernate_state = NULL,
@@ -39,13 +43,19 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
         usec_t delay = 180 * USEC_PER_MINUTE;
 
         const ConfigTableItem items[] = {
-                { "Sleep",   "SuspendMode",      config_parse_strv,  0, &suspend_mode  },
-                { "Sleep",   "SuspendState",     config_parse_strv,  0, &suspend_state },
-                { "Sleep",   "HibernateMode",    config_parse_strv,  0, &hibernate_mode  },
-                { "Sleep",   "HibernateState",   config_parse_strv,  0, &hibernate_state },
-                { "Sleep",   "HybridSleepMode",  config_parse_strv,  0, &hybrid_mode  },
-                { "Sleep",   "HybridSleepState", config_parse_strv,  0, &hybrid_state },
-                { "Sleep",   "HibernateDelaySec", config_parse_sec,  0, &delay},
+                { "Sleep", "AllowSuspend",              config_parse_tristate, 0, &allow_suspend },
+                { "Sleep", "AllowHibernation",          config_parse_tristate, 0, &allow_hibernate },
+                { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
+                { "Sleep", "AllowHybridSleep",          config_parse_tristate, 0, &allow_hybrid_sleep },
+
+                { "Sleep", "SuspendMode",               config_parse_strv, 0, &suspend_mode  },
+                { "Sleep", "SuspendState",              config_parse_strv, 0, &suspend_state },
+                { "Sleep", "HibernateMode",             config_parse_strv, 0, &hibernate_mode  },
+                { "Sleep", "HibernateState",            config_parse_strv, 0, &hibernate_state },
+                { "Sleep", "HybridSleepMode",           config_parse_strv, 0, &hybrid_mode  },
+                { "Sleep", "HybridSleepState",          config_parse_strv, 0, &hybrid_state },
+
+                { "Sleep", "HibernateDelaySec",         config_parse_sec,  0, &delay},
                 {}
         };
 
@@ -55,6 +65,8 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
                                         CONFIG_PARSE_WARN, NULL);
 
         if (streq(verb, "suspend")) {
+                allow = allow_suspend != 0;
+
                 /* empty by default */
                 modes = TAKE_PTR(suspend_mode);
 
@@ -64,6 +76,8 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
                         states = strv_new("mem", "standby", "freeze", NULL);
 
         } else if (streq(verb, "hibernate")) {
+                allow = allow_hibernate != 0;
+
                 if (hibernate_mode)
                         modes = TAKE_PTR(hibernate_mode);
                 else
@@ -75,6 +89,9 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
                         states = strv_new("disk", NULL);
 
         } else if (streq(verb, "hybrid-sleep")) {
+                allow = allow_hybrid_sleep > 0 ||
+                        (allow_suspend != 0 && allow_hibernate != 0);
+
                 if (hybrid_mode)
                         modes = TAKE_PTR(hybrid_mode);
                 else
@@ -85,21 +102,26 @@ int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t
                 else
                         states = strv_new("disk", NULL);
 
-        } else if (streq(verb, "suspend-then-hibernate"))
+        } else if (streq(verb, "suspend-then-hibernate")) {
+                allow = allow_s2h > 0 ||
+                        (allow_suspend != 0 && allow_hibernate != 0);
+
                 modes = states = NULL;
-        else
+        else
                 assert_not_reached("what verb");
 
         if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
             (!states && !streq(verb, "suspend-then-hibernate")))
                 return log_oom();
 
-        if (_modes)
-                *_modes = TAKE_PTR(modes);
-        if (_states)
-                *_states = TAKE_PTR(states);
-        if (_delay)
-                *_delay = delay;
+        if (ret_allow)
+                *ret_allow = allow;
+        if (ret_modes)
+                *ret_modes = TAKE_PTR(modes);
+        if (ret_states)
+                *ret_states = TAKE_PTR(states);
+        if (ret_delay)
+                *ret_delay = delay;
 
         return 0;
 }
@@ -266,12 +288,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;
@@ -422,6 +526,8 @@ int read_fiemap(int fd, struct fiemap **ret) {
         return 0;
 }
 
+static int can_sleep_internal(const char *verb, bool check_allowed);
+
 static bool can_s2h(void) {
         const char *p;
         int r;
@@ -434,8 +540,8 @@ static bool can_s2h(void) {
         }
 
         FOREACH_STRING(p, "suspend", "hibernate") {
-                r = can_sleep(p);
-                if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM)) {
+                r = can_sleep_internal(p, false);
+                if (IN_SET(r, 0, -ENOSPC, -ENOMEDIUM, -EADV)) {
                         log_debug("Unable to %s system.", p);
                         return false;
                 }
@@ -446,19 +552,25 @@ static bool can_s2h(void) {
         return true;
 }
 
-int can_sleep(const char *verb) {
+static int can_sleep_internal(const char *verb, bool check_allowed) {
+        bool allow;
         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
         int r;
 
         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
 
-        if (streq(verb, "suspend-then-hibernate"))
-                return can_s2h();
-
-        r = parse_sleep_config(verb, &modes, &states, NULL);
+        r = parse_sleep_config(verb, &allow, &modes, &states, NULL);
         if (r < 0)
                 return false;
 
+        if (check_allowed && !allow) {
+                log_debug("Sleep mode \"%s\" is disabled by configuration.", verb);
+                return false;
+        }
+
+        if (streq(verb, "suspend-then-hibernate"))
+                return can_s2h();
+
         if (!can_sleep_state(states) || !can_sleep_disk(modes))
                 return false;
 
@@ -473,5 +585,14 @@ 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;
 }
+
+int can_sleep(const char *verb) {
+        return can_sleep_internal(verb, true);
+}
index 6bf035969a6c578cea6c41d039c2eeb15701d1c7..c584f44d39dbc57d23e548d995c1f31ae276e5ae 100644 (file)
@@ -5,7 +5,7 @@
 #include "time-util.h"
 
 int read_fiemap(int fd, struct fiemap **ret);
-int parse_sleep_config(const char *verb, char ***modes, char ***states, usec_t *delay);
+int parse_sleep_config(const char *verb, bool *ret_allow, char ***ret_modes, char ***ret_states, usec_t *ret_delay);
 int find_hibernate_location(char **device, char **type, size_t *size, size_t *used);
 
 int can_sleep(const char *verb);
index 042a44656fe1ff2462c177a73057ae7ab36638c2..0085cb0196fe8698bbd52c68f0b3868cb6cb933e 100644 (file)
@@ -136,7 +136,6 @@ static int write_state(FILE **f, char **states) {
 }
 
 static int execute(char **modes, char **states) {
-
         char *arguments[] = {
                 NULL,
                 (char*) "pre",
@@ -221,13 +220,11 @@ static int execute_s2h(usec_t hibernate_delay_sec) {
         char time_str[DECIMAL_STR_MAX(uint64_t)];
         int r;
 
-        r = parse_sleep_config("suspend", &suspend_modes, &suspend_states,
-                               NULL);
+        r = parse_sleep_config("suspend", NULL, &suspend_modes, &suspend_states, NULL);
         if (r < 0)
                 return r;
 
-        r = parse_sleep_config("hibernate", &hibernate_modes,
-                               &hibernate_states, NULL);
+        r = parse_sleep_config("hibernate", NULL, &hibernate_modes, &hibernate_states, NULL);
         if (r < 0)
                 return r;
 
@@ -340,6 +337,7 @@ static int parse_argv(int argc, char *argv[]) {
 }
 
 int main(int argc, char *argv[]) {
+        bool allow;
         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
         usec_t delay = 0;
         int r;
@@ -352,10 +350,15 @@ int main(int argc, char *argv[]) {
         if (r <= 0)
                 goto finish;
 
-        r = parse_sleep_config(arg_verb, &modes, &states, &delay);
+        r = parse_sleep_config(arg_verb, &allow, &modes, &states, &delay);
         if (r < 0)
                 goto finish;
 
+        if (!allow) {
+                log_error("Sleep mode \"%s\" is disabled by configuration, refusing.", arg_verb);
+                return EXIT_FAILURE;
+        }
+
         if (streq(arg_verb, "suspend-then-hibernate"))
                 r = execute_s2h(delay);
         else
index ce5cbe7c13d11d116d198c86caed0a5fd887c0bf..be1b7375afa17d2ac5af5c81244b0ed4440af766 100644 (file)
@@ -3455,21 +3455,12 @@ static int load_kexec_kernel(void) {
         if (access(KEXEC, X_OK) < 0)
                 return log_error_errno(errno, KEXEC" is not available: %m");
 
-        r = find_esp_and_warn(arg_esp_path, false, &where, NULL, NULL, NULL, NULL);
-        if (r == -ENOKEY) /* find_esp_and_warn() doesn't warn about this case */
+        r = find_default_boot_entry(arg_esp_path, &where, &config, &e);
+        if (r == -ENOKEY) /* find_default_boot_entry() doesn't warn about this case */
                 return log_error_errno(r, "Cannot find the ESP partition mount point.");
-        if (r < 0) /* But it logs about all these cases, hence don't log here again */
-                return r;
-
-        r = boot_entries_load_config(where, &config);
         if (r < 0)
-                return log_error_errno(r, "Failed to load bootspec config from \"%s/loader\": %m", where);
-
-        if (config.default_entry < 0) {
-                log_error("No entry suitable as default, refusing to guess.");
-                return -ENOENT;
-        }
-        e = &config.entries[config.default_entry];
+                /* But it logs about all these cases, hence don't log here again */
+                return r;
 
         if (strv_length(e->initrd) > 1) {
                 log_error("Boot entry specifies multiple initrds, which is not supported currently.");
index 8f77e084b6cba3b2d2f335865828e9380d752937..5db103bd22a7212e1c52954359b8abf6174033c3 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include "alloc-util.h"
+#include "env-util.h"
 #include "log.h"
 #include "macro.h"
 #include "proc-cmdline.h"
@@ -19,28 +20,68 @@ static int parse_item(const char *key, const char *value, void *data) {
 }
 
 static void test_proc_cmdline_parse(void) {
-        assert_se(proc_cmdline_parse(parse_item, &obj, true) >= 0);
+        log_info("/* %s */", __func__);
+
+        assert_se(proc_cmdline_parse(parse_item, &obj, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
 }
 
-static void test_runlevel_to_target(void) {
-        in_initrd_force(false);
-        assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
-        assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
-        assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
-        assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET));
-        assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL));
+static void test_proc_cmdline_override(void) {
+        log_info("/* %s */", __func__);
 
-        in_initrd_force(true);
-        assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
-        assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
-        assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
-        assert_se(streq_ptr(runlevel_to_target("3"), NULL));
-        assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET));
+        assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm") == 0);
+
+        /* Test if the override works */
+        _cleanup_free_ char *line = NULL, *value = NULL;
+        assert_se(proc_cmdline(&line) >= 0);
+
+        /* Test if parsing makes uses of the override */
+        assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm"));
+        assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux"));
+}
+
+static int parse_item_given(const char *key, const char *value, void *data) {
+        assert_se(key);
+        assert_se(data);
+
+        bool *strip = data;
+
+        log_info("%s: option <%s> = <%s>", __func__, key, strna(value));
+        if (streq(key, "foo_bar"))
+                assert_se(streq(value, "quux"));
+        else if (streq(key, "wuff-piep"))
+                assert_se(streq(value, "tuet "));
+        else if (in_initrd() && *strip && streq(key, "zumm"))
+                assert_se(!value);
+        else if (in_initrd() && !*strip && streq(key, "rd.zumm"))
+                assert_se(!value);
+        else
+                assert_not_reached("Bad key!");
+
+        return 0;
+}
+
+static void test_proc_cmdline_given(bool flip_initrd) {
+        log_info("/* %s (flip: %s) */", __func__, yes_no(flip_initrd));
+
+        if (flip_initrd)
+                in_initrd_force(!in_initrd());
+
+        bool t = true, f = false;
+        assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm",
+                                           parse_item_given, &t, PROC_CMDLINE_STRIP_RD_PREFIX) >= 0);
+
+        assert_se(proc_cmdline_parse_given("foo_bar=quux wuff-piep=\"tuet \" rd.zumm",
+                                           parse_item_given, &f, 0) >= 0);
+
+
+        if (flip_initrd)
+                in_initrd_force(!in_initrd());
 }
 
 static void test_proc_cmdline_get_key(void) {
         _cleanup_free_ char *value = NULL;
 
+        log_info("/* %s */", __func__);
         putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm");
 
         assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL);
@@ -78,6 +119,7 @@ static void test_proc_cmdline_get_key(void) {
 static void test_proc_cmdline_get_bool(void) {
         bool value = false;
 
+        log_info("/* %s */", __func__);
         putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep");
 
         assert_se(proc_cmdline_get_bool("", &value) == -EINVAL);
@@ -94,6 +136,7 @@ static void test_proc_cmdline_get_bool(void) {
 }
 
 static void test_proc_cmdline_key_streq(void) {
+        log_info("/* %s */", __func__);
 
         assert_se(proc_cmdline_key_streq("", ""));
         assert_se(proc_cmdline_key_streq("a", "a"));
@@ -110,6 +153,7 @@ static void test_proc_cmdline_key_streq(void) {
 }
 
 static void test_proc_cmdline_key_startswith(void) {
+        log_info("/* %s */", __func__);
 
         assert_se(proc_cmdline_key_startswith("", ""));
         assert_se(proc_cmdline_key_startswith("x", ""));
@@ -124,11 +168,33 @@ static void test_proc_cmdline_key_startswith(void) {
         assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx"));
 }
 
+static void test_runlevel_to_target(void) {
+        log_info("/* %s */", __func__);
+
+        in_initrd_force(false);
+        assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
+        assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+        assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
+        assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET));
+        assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL));
+
+        in_initrd_force(true);
+        assert_se(streq_ptr(runlevel_to_target(NULL), NULL));
+        assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL));
+        assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL));
+        assert_se(streq_ptr(runlevel_to_target("3"), NULL));
+        assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET));
+}
+
 int main(void) {
         log_parse_environment();
         log_open();
 
         test_proc_cmdline_parse();
+        test_proc_cmdline_override();
+        test_proc_cmdline_given(false);
+        /* Repeat the same thing, but now flip our ininitrdness */
+        test_proc_cmdline_given(true);
         test_proc_cmdline_key_streq();
         test_proc_cmdline_key_startswith();
         test_proc_cmdline_get_key();
index 2ce79f8345c460012bfdb0de0c91ebdda1ea63a1..442541a298d1639869de4b627b5e2a048f200a44 100644 (file)
 static void test_parse_sleep_config(void) {
         const char *verb;
 
+        log_info("/* %s */", __func__);
+
         FOREACH_STRING(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate")
-                assert_se(parse_sleep_config(verb, NULL, NULL, NULL) == 0);
+                assert_se(parse_sleep_config(verb, NULL, NULL, NULL, NULL) == 0);
 }
 
 static int test_fiemap(const char *path) {
@@ -23,6 +25,8 @@ static int test_fiemap(const char *path) {
         _cleanup_close_ int fd = -1;
         int r;
 
+        log_info("/* %s */", __func__);
+
         fd = open(path, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
         if (fd < 0)
                 return log_error_errno(errno, "failed to open %s: %m", path);
@@ -56,7 +60,9 @@ static void test_sleep(void) {
                 **freez = strv_new("freeze", NULL);
         int r;
 
-        log_info("/* configuration */");
+        log_info("/* %s */", __func__);
+
+        log_info("/= configuration =/");
         log_info("Standby configured: %s", yes_no(can_sleep_state(standby) > 0));
         log_info("Suspend configured: %s", yes_no(can_sleep_state(mem) > 0));
         log_info("Hibernate configured: %s", yes_no(can_sleep_state(disk) > 0));
@@ -66,7 +72,7 @@ static void test_sleep(void) {
         log_info("Hibernate+Shutdown configured: %s", yes_no(can_sleep_disk(shutdown) > 0));
         log_info("Freeze configured: %s", yes_no(can_sleep_state(freez) > 0));
 
-        log_info("/* running system */");
+        log_info("/= running system =/");
         r = can_sleep("suspend");
         log_info("Suspend configured and possible: %s", r >= 0 ? yes_no(r) : strerror(-r));
         r = can_sleep("hibernate");