diff --git a/man/bootup.xml b/man/bootup.xml index 31f7a31518..c872b13c68 100644 --- a/man/bootup.xml +++ b/man/bootup.xml @@ -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 - systemd-hibernate-resume@.service which must be finished before + systemd-hibernate-resume.service which must be finished before local-fs-pre.target, so no filesystems can be mounted before the check is complete. When the root device becomes available, diff --git a/man/rules/meson.build b/man/rules/meson.build index ac32891731..74027f35a5 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -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'], diff --git a/man/systemd-hibernate-resume-generator.xml b/man/systemd-hibernate-resume-generator.xml index 910fcaeb25..539b92cb3a 100644 --- a/man/systemd-hibernate-resume-generator.xml +++ b/man/systemd-hibernate-resume-generator.xml @@ -29,10 +29,11 @@ systemd-hibernate-resume-generator is a generator that initiates the procedure to resume the system from hibernation. - It instantiates the - systemd-hibernate-resume@.service8 + It creates the + systemd-hibernate-resume.service8 unit according to the value of parameter - specified on the kernel command line, which will instruct the kernel + specified on the kernel command line, or the value of EFI variable + HibernateLocation, which will instruct the kernel to resume the system from the hibernation image on that device. @@ -55,6 +56,13 @@ supported. + + resume_offset= + + Takes the page offset of the swap space from the resume device. + Defaults to 0. + + resumeflags= @@ -75,7 +83,7 @@ See Also systemd1, - systemd-hibernate-resume@.service8, + systemd-hibernate-resume.service8, kernel-command-line7 diff --git a/man/systemd-hibernate-resume@.service.xml b/man/systemd-hibernate-resume.service.xml similarity index 55% rename from man/systemd-hibernate-resume@.service.xml rename to man/systemd-hibernate-resume.service.xml index b6ae1f93de..6f457f34ab 100644 --- a/man/systemd-hibernate-resume@.service.xml +++ b/man/systemd-hibernate-resume.service.xml @@ -3,46 +3,43 @@ - + - systemd-hibernate-resume@.service + systemd-hibernate-resume.service systemd - systemd-hibernate-resume@.service + systemd-hibernate-resume.service 8 - systemd-hibernate-resume@.service + systemd-hibernate-resume.service systemd-hibernate-resume Resume from hibernation - systemd-hibernate-resume@.service + systemd-hibernate-resume.service /usr/lib/systemd/systemd-hibernate-resume Description - systemd-hibernate-resume@.service - initiates the resume from hibernation. It is instantiated with the - device to resume from as the template argument. + systemd-hibernate-resume.service initiates the resume from hibernation. - systemd-hibernate-resume only supports - the in-kernel hibernation implementation, see - Swap suspend. - Internally, it works by writing the major:minor of specified - device node to /sys/power/resume. + systemd-hibernate-resume only supports the in-kernel hibernation + implementation, see Swap suspend. + Internally, it works by writing the major:minor of specified device node to + /sys/power/resume, along with the offset in memory pages + (/sys/power/resume_offset) if supported. - 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. + 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. diff --git a/src/basic/special.h b/src/basic/special.h index 98fcddf631..bc9c9eb011 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -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" diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 36ca830f8a..160cf83ce6 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -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; diff --git a/src/core/mount.c b/src/core/mount.c index 36ea9bbefb..542c39e186 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -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; diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index 1bcf9d69df..fac57d38c4 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -2,16 +2,25 @@ #include #include +#include #include +#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(); } diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 9a9df5d22f..28cab191fc 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -1,58 +1,55 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include -#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); diff --git a/src/shared/sleep-util.c b/src/shared/sleep-util.c index eb9a43f507..1027ecce05 100644 --- a/src/shared/sleep-util.c +++ b/src/shared/sleep-util.c @@ -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) { diff --git a/src/shared/sleep-util.h b/src/shared/sleep-util.h index 5f8bf090f9..6480ca8637 100644 --- a/src/shared/sleep-util.h +++ b/src/shared/sleep-util.h @@ -2,6 +2,7 @@ #pragma once #include +#include #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); diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 46f46914b5..7fe99a37a4 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -12,9 +12,12 @@ #include #include #include +#include #include #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"); diff --git a/units/meson.build b/units/meson.build index df6da78abe..dff7b3904f 100644 --- a/units/meson.build +++ b/units/meson.build @@ -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'], diff --git a/units/systemd-hibernate-resume@.service.in b/units/systemd-hibernate-resume@.service.in deleted file mode 100644 index 142bb339e1..0000000000 --- a/units/systemd-hibernate-resume@.service.in +++ /dev/null @@ -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