Merge pull request #27330 from YHNdnzj/hibernate-resume-auto

sleep/hibernate-resume: pass hibernate location through efivar for resume without kernel cmdline
This commit is contained in:
Lennart Poettering
2023-06-23 23:03:32 +02:00
committed by GitHub
14 changed files with 356 additions and 144 deletions

View File

@@ -210,7 +210,7 @@ emergency.service | | |
Before any file systems are mounted, the manager will determine whether the system shall resume from
hibernation or proceed with normal boot. This is accomplished by
<filename>systemd-hibernate-resume@.service</filename> which must be finished before
<filename>systemd-hibernate-resume.service</filename> which must be finished before
<filename>local-fs-pre.target</filename>, so no filesystems can be mounted before the check is complete.
When the root device becomes available,

View File

@@ -931,7 +931,7 @@ manpages = [
['systemd-getty-generator', '8', [], ''],
['systemd-gpt-auto-generator', '8', [], 'HAVE_BLKID'],
['systemd-hibernate-resume-generator', '8', [], 'ENABLE_HIBERNATE'],
['systemd-hibernate-resume@.service',
['systemd-hibernate-resume.service',
'8',
['systemd-hibernate-resume'],
'ENABLE_HIBERNATE'],

View File

@@ -29,10 +29,11 @@
<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>
It creates 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, which will instruct the kernel
specified on the kernel command line, or the value of EFI variable
<varname>HibernateLocation</varname>, which will instruct the kernel
to resume the system from the hibernation image on that device.</para>
</refsect1>
@@ -55,6 +56,13 @@
supported.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>resume_offset=</varname></term>
<listitem><para>Takes the page offset of the swap space from the resume device.
Defaults to <literal>0</literal>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>resumeflags=</varname></term>
@@ -75,7 +83,7 @@
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-hibernate-resume.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>
</para>
</refsect1>

View File

@@ -3,46 +3,43 @@
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-hibernate-resume@.service" conditional='ENABLE_HIBERNATE'>
<refentry id="systemd-hibernate-resume.service" conditional='ENABLE_HIBERNATE'>
<refentryinfo>
<title>systemd-hibernate-resume@.service</title>
<title>systemd-hibernate-resume.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-hibernate-resume@.service</refentrytitle>
<refentrytitle>systemd-hibernate-resume.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-hibernate-resume@.service</refname>
<refname>systemd-hibernate-resume.service</refname>
<refname>systemd-hibernate-resume</refname>
<refpurpose>Resume from hibernation</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-hibernate-resume@.service</filename></para>
<para><filename>systemd-hibernate-resume.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-hibernate-resume</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-hibernate-resume@.service</filename>
initiates the resume from hibernation. It is instantiated with the
device to resume from as the template argument.</para>
<para><filename>systemd-hibernate-resume.service</filename> initiates the resume from hibernation.</para>
<para><filename>systemd-hibernate-resume</filename> only supports
the in-kernel hibernation implementation, see
<ulink url="https://docs.kernel.org/power/swsusp.html">Swap suspend</ulink>.
Internally, it works by writing the major:minor of specified
device node to <filename>/sys/power/resume</filename>.</para>
<para><filename>systemd-hibernate-resume</filename> only supports the in-kernel hibernation
implementation, see <ulink url="https://docs.kernel.org/power/swsusp.html">Swap suspend</ulink>.
Internally, it works by writing the major:minor of specified device node to
<filename>/sys/power/resume</filename>, along with the offset in memory pages
(<filename>/sys/power/resume_offset</filename>) if supported.</para>
<para>Failing to initiate a resume is not an error condition. It
may mean that there was no resume image (e. g. if the system has
been simply powered off and not hibernated). In such case, the
boot is ordinarily continued.</para>
<para>Failing to initiate a resume is not an error condition. It may mean that there was
no resume image (e. g. if the system has been simply powered off and not hibernated).
In such cases, the boot is ordinarily continued.</para>
</refsect1>
<refsect1>

View File

@@ -93,6 +93,7 @@
#define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service"
#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service"
#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service"
#define SPECIAL_HIBERNATE_RESUME_SERVICE "systemd-hibernate-resume.service"
/* Services systemd relies on */
#define SPECIAL_DBUS_SERVICE "dbus.service"

View File

@@ -479,7 +479,9 @@ static int run(int argc, char *argv[]) {
r = device_path_make_canonical(S_IFBLK, devno, &path);
if (r < 0)
return log_oom();
return log_error_errno(r,
"Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
DEVNUM_FORMAT_VAL(devno));
puts(path);
return 0;

View File

@@ -502,7 +502,7 @@ static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p,
* it's not technically part of the basic initrd filesystem itself, and so
* shouldn't inherit the default Before=local-fs.target dependency. However,
* these mounts still need to start after local-fs-pre.target, as a sync point
* for things like systemd-hibernate-resume@.service that should start before
* for things like systemd-hibernate-resume.service that should start before
* any mounts. */
after = SPECIAL_LOCAL_FS_PRE_TARGET;

View File

@@ -2,16 +2,25 @@
#include <errno.h>
#include <stdio.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "sd-id128.h"
#include "alloc-util.h"
#include "dropin.h"
#include "efivars.h"
#include "fd-util.h"
#include "fileio.h"
#include "fstab-util.h"
#include "generator.h"
#include "id128-util.h"
#include "initrd-util.h"
#include "json.h"
#include "log.h"
#include "main-func.h"
#include "mkdir-label.h"
#include "os-util.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "string-util.h"
@@ -22,14 +31,28 @@ static char *arg_resume_device = NULL;
static char *arg_resume_options = NULL;
static char *arg_root_options = NULL;
static bool arg_noresume = false;
static uint64_t arg_resume_offset = 0;
STATIC_DESTRUCTOR_REGISTER(arg_resume_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_resume_options, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
#if ENABLE_EFI
typedef struct EFIHibernateLocation {
sd_id128_t uuid;
uint64_t offset;
const char *kernel_version;
const char *id;
const char *image_id;
const char *version_id;
const char *image_version;
} EFIHibernateLocation;
#endif
if (streq(key, "resume")) {
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
if (proc_cmdline_key_streq(key, "resume")) {
char *s;
if (proc_cmdline_value_missing(key, value))
@@ -41,7 +64,16 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
free_and_replace(arg_resume_device, s);
} else if (streq(key, "resumeflags")) {
} else if (proc_cmdline_key_streq(key, "resume_offset")) {
if (proc_cmdline_value_missing(key, value))
return 0;
r = safe_atou64(value, &arg_resume_offset);
if (r < 0)
return log_error_errno(r, "Failed to parse resume_offset=%s: %m", value);
} else if (proc_cmdline_key_streq(key, "resumeflags")) {
if (proc_cmdline_value_missing(key, value))
return 0;
@@ -49,7 +81,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
if (!strextend_with_separator(&arg_resume_options, ",", value))
return log_oom();
} else if (streq(key, "rootflags")) {
} else if (proc_cmdline_key_streq(key, "rootflags")) {
if (proc_cmdline_value_missing(key, value))
return 0;
@@ -57,7 +89,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
if (!strextend_with_separator(&arg_root_options, ",", value))
return log_oom();
} else if (streq(key, "noresume")) {
} else if (proc_cmdline_key_streq(key, "noresume")) {
if (value) {
log_warning("\"noresume\" kernel command line switch specified with an argument, ignoring.");
return 0;
@@ -69,36 +101,136 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
return 0;
}
static int parse_efi_hibernate_location(void) {
int r = 0;
#if ENABLE_EFI
static const JsonDispatch dispatch_table[] = {
{ "uuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(EFIHibernateLocation, uuid), JSON_MANDATORY },
{ "offset", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(EFIHibernateLocation, offset), JSON_MANDATORY },
{ "kernelVersion", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, kernel_version), JSON_PERMISSIVE|JSON_DEBUG },
{ "osReleaseId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, id), JSON_PERMISSIVE|JSON_DEBUG },
{ "osReleaseImageId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, image_id), JSON_PERMISSIVE|JSON_DEBUG },
{ "osReleaseVersionId", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, version_id), JSON_PERMISSIVE|JSON_DEBUG },
{ "osReleaseImageVersion", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(EFIHibernateLocation, image_version), JSON_PERMISSIVE|JSON_DEBUG },
{},
};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *location_str = NULL, *device = NULL, *id = NULL, *image_id = NULL,
*version_id = NULL, *image_version = NULL;
struct utsname uts = {};
EFIHibernateLocation location = {};
r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), &location_str);
if (r == -ENOENT) {
log_debug_errno(r, "EFI variable HibernateLocation is not set, skipping.");
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to get EFI variable HibernateLocation: %m");
r = json_parse(location_str, 0, &v, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse HibernateLocation JSON object: %m");
r = json_dispatch(v, dispatch_table, NULL, JSON_LOG, &location);
if (r < 0)
return r;
if (uname(&uts) < 0)
log_warning_errno(errno, "Failed to get kernel info, ignoring: %m");
r = parse_os_release(NULL,
"ID", &id,
"IMAGE_ID", &image_id,
"VERSION_ID", &version_id,
"IMAGE_VERSION", &image_version);
if (r < 0)
log_warning_errno(r, "Failed to parse os-release, ignoring: %m");
if (!streq(uts.release, strempty(location.kernel_version)) ||
!streq_ptr(id, location.id) ||
!streq_ptr(image_id, location.image_id) ||
!streq_ptr(version_id, location.version_id) ||
!streq_ptr(image_version, location.image_version)) {
log_notice("HibernateLocation system info doesn't match with current running system, not resuming from it.");
return 0;
}
if (asprintf(&device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(location.uuid)) < 0)
return log_oom();
if (!arg_resume_device) {
arg_resume_device = TAKE_PTR(device);
arg_resume_offset = location.offset;
} else {
if (!streq(arg_resume_device, device))
log_warning("resume=%s doesn't match with HibernateLocation device '%s', proceeding anyway with resume=.",
arg_resume_device, device);
if (arg_resume_offset != location.offset)
log_warning("resume_offset=%" PRIu64 " doesn't match with HibernateLocation offset %" PRIu64 ", proceeding anyway with resume_offset=.",
arg_resume_offset, location.offset);
}
r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);
if (r < 0)
log_warning_errno(r, "Failed to clear EFI variable HibernateLocation, ignoring: %m");
#endif
return r;
}
static int process_resume(void) {
_cleanup_free_ char *service_unit = NULL, *device_unit = NULL, *lnk = NULL;
_cleanup_free_ char *device_unit = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
if (!arg_resume_device)
return 0;
r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_device, ".service",
&service_unit);
if (r < 0)
return log_error_errno(r, "Failed to generate unit name: %m");
lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", service_unit);
if (!lnk)
return log_oom();
(void) mkdir_parents_label(lnk, 0755);
if (symlink(SYSTEM_DATA_UNIT_DIR "/systemd-hibernate-resume@.service", lnk) < 0)
return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
r = unit_name_from_path(arg_resume_device, ".device", &device_unit);
if (r < 0)
return log_error_errno(r, "Failed to generate unit name: %m");
return log_error_errno(r, "Failed to generate device unit name from path '%s': %m", arg_resume_device);
r = write_drop_in(arg_dest, device_unit, 40, "device-timeout",
"# Automatically generated by systemd-hibernate-resume-generator\n\n"
"[Unit]\n"
"JobTimeoutSec=infinity\n");
if (r < 0)
log_warning_errno(r, "Failed to write device timeout drop-in: %m");
log_warning_errno(r, "Failed to write device timeout drop-in, ignoring: %m");
r = generator_open_unit_file(arg_dest, NULL, SPECIAL_HIBERNATE_RESUME_SERVICE, &f);
if (r < 0)
return r;
fprintf(f,
"[Unit]\n"
"Description=Resume from hibernation\n"
"Documentation=man:systemd-hibernate-resume.service(8)\n"
"DefaultDependencies=no\n"
"BindsTo=%1$s\n"
"Wants=local-fs-pre.target\n"
"After=%1$s\n"
"Before=local-fs-pre.target\n"
"AssertPathExists=/etc/initrd-release\n"
"\n"
"[Service]\n"
"Type=oneshot\n"
"ExecStart=" ROOTLIBEXECDIR "/systemd-hibernate-resume %2$s %3$" PRIu64,
device_unit,
arg_resume_device,
arg_resume_offset);
r = fflush_and_check(f);
if (r < 0)
return log_error_errno(r, "Failed to create " SPECIAL_HIBERNATE_RESUME_SERVICE ": %m");
r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SPECIAL_HIBERNATE_RESUME_SERVICE);
if (r < 0)
return r;
r = generator_write_timeouts(arg_dest,
arg_resume_device,
@@ -112,7 +244,7 @@ static int process_resume(void) {
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
int r = 0;
int r;
arg_dest = ASSERT_PTR(dest);
@@ -131,6 +263,10 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
return 0;
}
r = parse_efi_hibernate_location();
if (r == -ENOMEM)
return r;
return process_resume();
}

View File

@@ -1,58 +1,55 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include "alloc-util.h"
#include "devnum-util.h"
#include "fileio.h"
#include "initrd-util.h"
#include "log.h"
#include "main-func.h"
#include "parse-util.h"
#include "sleep-util.h"
int main(int argc, char *argv[]) {
static const char *arg_resume_device = NULL;
static uint64_t arg_resume_offset = 0; /* in memory pages */
static int run(int argc, char *argv[]) {
struct stat st;
const char *device;
int r;
if (argc != 2) {
log_error("This program expects one argument.");
return EXIT_FAILURE;
}
log_setup();
if (argc < 2 || argc > 3)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects one or two arguments.");
umask(0022);
/* Refuse to run unless we are in an initrd() */
if (!in_initrd())
return EXIT_SUCCESS;
return 0;
device = argv[1];
arg_resume_device = argv[1];
if (stat(device, &st) < 0) {
log_error_errno(errno, "Failed to stat '%s': %m", device);
return EXIT_FAILURE;
if (argc == 3) {
r = safe_atou64(argv[2], &arg_resume_offset);
if (r < 0)
return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[2]);
}
if (!S_ISBLK(st.st_mode)) {
log_error("Resume device '%s' is not a block device.", device);
return EXIT_FAILURE;
}
if (stat(arg_resume_device, &st) < 0)
return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_resume_device);
r = write_string_file("/sys/power/resume", FORMAT_DEVNUM(st.st_rdev), WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0) {
log_error_errno(r, "Failed to write '" DEVNUM_FORMAT_STR "' to /sys/power/resume: %m", DEVNUM_FORMAT_VAL(st.st_rdev));
return EXIT_FAILURE;
}
if (!S_ISBLK(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Resume device '%s' is not a block device.", arg_resume_device);
/*
* The write above shall not return.
*
* However, failed resume is a normal condition (may mean that there is
* no hibernation image).
*/
/* The write shall not return if a resume takes place. */
r = write_resume_config(st.st_rdev, arg_resume_offset, arg_resume_device);
log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG,
r < 0 ? r : SYNTHETIC_ERRNO(ENOENT),
"Unable to resume from device '%s' (" DEVNUM_FORMAT_STR ") offset %" PRIu64 ", continuing boot process.",
arg_resume_device, DEVNUM_FORMAT_VAL(st.st_rdev), arg_resume_offset);
log_info("Could not resume from '%s' (" DEVNUM_FORMAT_STR ").", device, DEVNUM_FORMAT_VAL(st.st_rdev));
return EXIT_SUCCESS;
return r;
}
DEFINE_MAIN_FUNCTION(run);

View File

@@ -1028,6 +1028,52 @@ int read_fiemap(int fd, struct fiemap **ret) {
return 0;
}
int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
_cleanup_free_ char *path = NULL;
const char *devno_str;
int r;
devno_str = FORMAT_DEVNUM(devno);
xsprintf(offset_str, "%" PRIu64, offset);
if (!device) {
r = device_path_make_canonical(S_IFBLK, devno, &path);
if (r < 0)
return log_error_errno(r,
"Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
DEVNUM_FORMAT_VAL(devno));
device = path;
}
/* We write the offset first since it's safer. Note that this file is only available in 4.17+, so
* fail gracefully if it doesn't exist and we're only overwriting it with 0. */
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r == -ENOENT) {
if (offset != 0)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Can't configure hibernation offset %" PRIu64 ", kernel does not support /sys/power/resume_offset. Refusing.",
offset);
log_warning_errno(r, "/sys/power/resume_offset is unavailable, skipping writing swap file offset.");
} else if (r < 0)
return log_error_errno(r,
"Failed to write swap file offset %s to /sys/power/resume_offset for device '%s': %m",
offset_str, device);
else
log_debug("Wrote resume_offset=%s for device '%s' to /sys/power/resume_offset.",
offset_str, device);
r = write_string_file("/sys/power/resume", devno_str, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return log_error_errno(r,
"Failed to write device '%s' (%s) to /sys/power/resume: %m",
device, devno_str);
log_debug("Wrote resume=%s for device '%s' to /sys/power/resume.", devno_str, device);
return 0;
}
static int can_sleep_internal(const SleepConfig *sleep_config, SleepOperation operation, bool check_allowed);
static bool can_s2h(const SleepConfig *sleep_config) {

View File

@@ -2,6 +2,7 @@
#pragma once
#include <linux/fiemap.h>
#include <sys/types.h>
#include "hashmap.h"
#include "time-util.h"
@@ -63,6 +64,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(HibernateLocation*, hibernate_location_free);
int read_fiemap(int fd, struct fiemap **ret);
int parse_sleep_config(SleepConfig **sleep_config);
int find_hibernate_location(HibernateLocation **ret_hibernate_location);
int write_resume_config(dev_t devno, uint64_t offset, const char *device);
int can_sleep(SleepOperation operation);
int can_sleep_disk(char **types);

View File

@@ -12,9 +12,12 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/timerfd.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-device.h"
#include "sd-id128.h"
#include "sd-messages.h"
#include "battery-util.h"
@@ -25,13 +28,17 @@
#include "bus-util.h"
#include "constants.h"
#include "devnum-util.h"
#include "efivars.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "id128-util.h"
#include "io-util.h"
#include "json.h"
#include "log.h"
#include "main-func.h"
#include "os-util.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "sleep-util.h"
@@ -45,49 +52,85 @@
static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID;
static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
const char *resume_str;
int r;
static int write_efi_hibernate_location(const HibernateLocation *hibernate_location, bool required) {
int r = 0;
#if ENABLE_EFI
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_free_ char *formatted = NULL, *id = NULL, *image_id = NULL,
*version_id = NULL, *image_version = NULL;
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
const char *uuid_str;
sd_id128_t uuid;
struct utsname uts = {};
int log_level, log_level_ignore;
assert(hibernate_location);
assert(hibernate_location->swap);
resume_str = FORMAT_DEVNUM(hibernate_location->devno);
r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
if (r < 0)
return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
hibernate_location->swap->path, resume_str);
log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->path, resume_str);
/* if it's a swap partition, we're done */
if (hibernate_location->swap->type == SWAP_BLOCK)
if (!is_efi_boot())
return 0;
assert(hibernate_location->swap->type == SWAP_FILE);
log_level = required ? LOG_ERR : LOG_DEBUG;
log_level_ignore = required ? LOG_WARNING : LOG_DEBUG;
/* Only available in 4.17+ */
if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) {
if (errno == ENOENT) {
log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64,
hibernate_location->swap->path, hibernate_location->offset);
return 0;
}
return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m");
}
xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
r = sd_device_new_from_devnum(&device, 'b', hibernate_location->devno);
if (r < 0)
return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m",
hibernate_location->swap->path, offset_str);
return log_full_errno(log_level, r, "Failed to create sd-device object for '%s': %m",
hibernate_location->swap->path);
log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->path, offset_str);
r = sd_device_get_property_value(device, "ID_FS_UUID", &uuid_str);
if (r < 0)
return log_full_errno(log_level, r, "Failed to get filesystem UUID for device '%s': %m",
hibernate_location->swap->path);
return 0;
r = sd_id128_from_string(uuid_str, &uuid);
if (r < 0)
return log_full_errno(log_level, r, "Failed to parse ID_FS_UUID '%s' for device '%s': %m",
uuid_str, hibernate_location->swap->path);
if (uname(&uts) < 0)
log_full_errno(log_level_ignore, errno, "Failed to get kernel info, ignoring: %m");
r = parse_os_release(NULL,
"ID", &id,
"IMAGE_ID", &image_id,
"VERSION_ID", &version_id,
"IMAGE_VERSION", &image_version);
if (r < 0)
log_full_errno(log_level_ignore, r, "Failed to parse os-release, ignoring: %m");
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UUID("uuid", uuid),
JSON_BUILD_PAIR_UNSIGNED("offset", hibernate_location->offset),
JSON_BUILD_PAIR_CONDITION(!isempty(uts.release), "kernelVersion", JSON_BUILD_STRING(uts.release)),
JSON_BUILD_PAIR_CONDITION(id, "osReleaseId", JSON_BUILD_STRING(id)),
JSON_BUILD_PAIR_CONDITION(image_id, "osReleaseImageId", JSON_BUILD_STRING(image_id)),
JSON_BUILD_PAIR_CONDITION(version_id, "osReleaseVersionId", JSON_BUILD_STRING(version_id)),
JSON_BUILD_PAIR_CONDITION(image_version, "osReleaseImageVersion", JSON_BUILD_STRING(image_version))));
if (r < 0)
return log_full_errno(log_level, r, "Failed to build JSON object: %m");
r = json_variant_format(v, 0, &formatted);
if (r < 0)
return log_full_errno(log_level, r, "Failed to format JSON object: %m");
r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), formatted);
if (r < 0)
return log_full_errno(log_level, r, "Failed to set EFI variable HibernateLocation: %m");
log_debug("Set EFI variable HibernateLocation to '%s'.", formatted);
#endif
return r;
}
static int write_kernel_hibernate_location(const HibernateLocation *hibernate_location) {
assert(hibernate_location);
assert(hibernate_location->swap);
assert(IN_SET(hibernate_location->swap->type, SWAP_BLOCK, SWAP_FILE));
return write_resume_config(hibernate_location->devno, hibernate_location->offset, hibernate_location->swap->path);
}
static int write_mode(char **modes) {
@@ -212,17 +255,23 @@ static int execute(
/* Configure hibernation settings if we are supposed to hibernate */
if (!strv_isempty(modes)) {
bool resume_set;
r = find_hibernate_location(&hibernate_location);
if (r < 0)
return log_error_errno(r, "Failed to find location to hibernate to: %m");
if (r == 0) { /* 0 means: no hibernation location was configured in the kernel so far, let's
* do it ourselves then. > 0 means: kernel already had a configured hibernation
* location which we shouldn't touch. */
r = write_hibernate_location_info(hibernate_location);
resume_set = r > 0;
if (!resume_set) {
r = write_kernel_hibernate_location(hibernate_location);
if (r < 0)
return log_error_errno(r, "Failed to prepare for hibernation: %m");
}
r = write_efi_hibernate_location(hibernate_location, !resume_set);
if (r < 0 && !resume_set)
return r;
r = write_mode(modes);
if (r < 0)
return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");

View File

@@ -281,10 +281,6 @@ units = [
{ 'file' : 'systemd-growfs-root.service.in' },
{ 'file' : 'systemd-growfs@.service.in' },
{ 'file' : 'systemd-halt.service' },
{
'file' : 'systemd-hibernate-resume@.service.in',
'conditions' : ['ENABLE_HIBERNATE'],
},
{
'file' : 'systemd-hibernate.service.in',
'conditions' : ['ENABLE_HIBERNATE'],

View File

@@ -1,22 +0,0 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Resume from hibernation using device %f
Documentation=man:systemd-hibernate-resume@.service(8)
DefaultDependencies=no
BindsTo=%i.device
Wants=local-fs-pre.target
After=%i.device
Before=local-fs-pre.target
AssertPathExists=/etc/initrd-release
[Service]
Type=oneshot
ExecStart={{ROOTLIBEXECDIR}}/systemd-hibernate-resume %f