From 9deeca127520b1098c3dfab9cdfd3b9c6bf983a4 Mon Sep 17 00:00:00 2001 From: Mike Yuan Date: Tue, 25 Apr 2023 00:34:19 +0800 Subject: [PATCH] hibernate-resume: support resuming through efivar HibernateLocation --- man/systemd-hibernate-resume-generator.xml | 3 +- .../hibernate-resume-generator.c | 105 ++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/man/systemd-hibernate-resume-generator.xml b/man/systemd-hibernate-resume-generator.xml index 7d0039dcf2e..539b92cb3a6 100644 --- a/man/systemd-hibernate-resume-generator.xml +++ b/man/systemd-hibernate-resume-generator.xml @@ -32,7 +32,8 @@ 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. diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index db23a1d5262..fac57d38c4e 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -2,17 +2,24 @@ #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 "os-util.h" #include "parse-util.h" #include "proc-cmdline.h" #include "special.h" @@ -30,6 +37,18 @@ 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; @@ -82,6 +101,88 @@ 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 *device_unit = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -162,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(); }