mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
Merge pull request #10262 from keszybz/hibres-disable
Switches to disable hibernation and/or resuming
This commit is contained in:
7
TODO
7
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,11 +28,13 @@
|
||||
<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>
|
||||
@@ -54,6 +56,12 @@
|
||||
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>
|
||||
|
||||
|
||||
@@ -113,6 +113,24 @@
|
||||
<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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user