sleep/hibernate-resume: pass hibernate location through efivar for resume without kernel cmdline
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,
['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'],
<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>
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>
<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>
<!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>
#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"
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;
* 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;
#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"
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);
+#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
+
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
+ int r;
- if (streq(key, "resume")) {
+ if (proc_cmdline_key_streq(key, "resume")) {
char *s;
if (proc_cmdline_value_missing(key, value))
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;
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;
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;
return 0;
}
-static int process_resume(void) {
- _cleanup_free_ char *service_unit = NULL, *device_unit = NULL, *lnk = NULL;
- int r;
+static int parse_efi_hibernate_location(void) {
+ int r = 0;
- if (!arg_resume_device)
+#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 = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_device, ".service",
- &service_unit);
+ r = parse_os_release(NULL,
+ "ID", &id,
+ "IMAGE_ID", &image_id,
+ "VERSION_ID", &version_id,
+ "IMAGE_VERSION", &image_version);
if (r < 0)
- return log_error_errno(r, "Failed to generate unit name: %m");
+ 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;
+ }
- lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", service_unit);
- if (!lnk)
+ if (asprintf(&device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(location.uuid)) < 0)
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);
+ 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 *device_unit = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ if (!arg_resume_device)
+ return 0;
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,
}
static int run(const char *dest, const char *dest_early, const char *dest_late) {
- int r = 0;
+ int r;
arg_dest = ASSERT_PTR(dest);
return 0;
}
+ r = parse_efi_hibernate_location();
+ if (r == -ENOMEM)
+ return r;
+
return process_resume();
}
/* 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);
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) {
#pragma once
#include <linux/fiemap.h>
+#include <sys/types.h>
#include "hashmap.h"
#include "time-util.h"
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);
#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"
#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"
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);
+ if (!is_efi_boot())
+ return 0;
- r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
+ log_level = required ? LOG_ERR : LOG_DEBUG;
+ log_level_ignore = required ? LOG_WARNING : LOG_DEBUG;
+
+ r = sd_device_new_from_devnum(&device, 'b', hibernate_location->devno);
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);
+ return log_full_errno(log_level, r, "Failed to create sd-device object for '%s': %m",
+ hibernate_location->swap->path);
- log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->path, resume_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);
- /* if it's a swap partition, we're done */
- if (hibernate_location->swap->type == SWAP_BLOCK)
- 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);
- assert(hibernate_location->swap->type == SWAP_FILE);
+ if (uname(&uts) < 0)
+ log_full_errno(log_level_ignore, errno, "Failed to get kernel info, ignoring: %m");
- /* 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;
- }
+ 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");
- return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m");
- }
+ r = json_variant_format(v, 0, &formatted);
+ if (r < 0)
+ return log_full_errno(log_level, r, "Failed to format JSON object: %m");
- xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
- r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
+ r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), formatted);
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 set EFI variable HibernateLocation: %m");
- log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->path, offset_str);
+ log_debug("Set EFI variable HibernateLocation to '%s'.", formatted);
+#endif
- return 0;
+ 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) {
/* 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");
{ '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'],
+++ /dev/null
-# 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