diff --git a/TODO b/TODO
index 13af83dfe6..f72dd0f718 100644
--- 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.
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 0c991520c4..b4363ba58b 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -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
diff --git a/man/systemd-hibernate-resume-generator.xml b/man/systemd-hibernate-resume-generator.xml
index 86d45dc4af..8f0cc5d044 100644
--- a/man/systemd-hibernate-resume-generator.xml
+++ b/man/systemd-hibernate-resume-generator.xml
@@ -28,11 +28,13 @@
Description
- systemd-hibernate-resume-generator is a
- generator that instantiates
+ systemd-hibernate-resume-generator is a
+ generator that initiates the procedure to resume the system from hibernation.
+ It instantiates the
systemd-hibernate-resume@.service8
unit according to the value of parameter
- specified on the kernel command line.
+ specified on the kernel command line, which will instruct the kernel
+ to resume the system from the hibernation image on that device.
@@ -54,6 +56,12 @@
supported.
+
+ noresume
+
+ Do not try to resume from hibernation. If this parameter is
+ present, resume= is ignored.
+
diff --git a/man/systemd-sleep.conf.xml b/man/systemd-sleep.conf.xml
index 10bbc8c76a..96e6d5e452 100644
--- a/man/systemd-sleep.conf.xml
+++ b/man/systemd-sleep.conf.xml
@@ -113,6 +113,24 @@
sleep.conf.d file:
+
+ AllowSuspend=
+ AllowHibernation=
+ AllowSuspendThenHibernate=
+ AllowHybridSleep=
+
+ 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.
+
+ If AllowHibernation=no or AllowSuspend=no is
+ used, this implies AllowSuspendThenHibernate=no and
+ AllowHybridSleep=no, since those methods use both suspend and hibernation
+ internally. AllowSuspendThenHibernate=yes and
+ AllowHybridSleep=yes can be used to override and enable those specific
+ modes.
+
+
SuspendMode=
HibernateMode=
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c
index add481c2ae..b386c705e1 100644
--- a/src/basic/proc-cmdline.c
+++ b/src/basic/proc-cmdline.c
@@ -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);
diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h
index 4a9e6e0f62..ffc45fddb9 100644
--- a/src/basic/proc-cmdline.h
+++ b/src/basic/proc-cmdline.h
@@ -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);
diff --git a/src/basic/util.c b/src/basic/util.c
index 081c63c898..0da963f4af 100644
--- a/src/basic/util.c
+++ b/src/basic/util.c
@@ -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;
}
diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c
index 8680b3a6ae..827927f18b 100644
--- a/src/boot/bootctl.c
+++ b/src/boot/bootctl.c
@@ -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)
diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c
index 4b79e3def4..036493a389 100644
--- a/src/hibernate-resume/hibernate-resume-generator.c
+++ b/src/hibernate-resume/hibernate-resume-generator.c
@@ -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);
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/bootspec.c b/src/shared/bootspec.c
index 8d5a421144..5d8471c51a 100644
--- a/src/shared/bootspec.c
+++ b/src/shared/bootspec.c
@@ -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;
+}
diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h
index f47c073a46..c39d773cb3 100644
--- a/src/shared/bootspec.h
+++ b/src/shared/bootspec.h
@@ -2,11 +2,15 @@
#pragma once
-#include
+#include
+#include
+#include
+
+#include "sd-id128.h"
typedef struct BootEntry {
- char *filename;
-
+ 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);
diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c
index 2ffd32bf99..6778399188 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,12 +26,15 @@
#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);
+}
diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h
index 6bf035969a..c584f44d39 100644
--- a/src/shared/sleep-config.h
+++ b/src/shared/sleep-config.h
@@ -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);
diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c
index 042a44656f..0085cb0196 100644
--- a/src/sleep/sleep.c
+++ b/src/sleep/sleep.c
@@ -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
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index ce5cbe7c13..be1b7375af 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -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.");
diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c
index 8f77e084b6..5db103bd22 100644
--- a/src/test/test-proc-cmdline.c
+++ b/src/test/test-proc-cmdline.c
@@ -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();
diff --git a/src/test/test-sleep.c b/src/test/test-sleep.c
index 2ce79f8345..442541a298 100644
--- a/src/test/test-sleep.c
+++ b/src/test/test-sleep.c
@@ -14,8 +14,10 @@
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");