]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hibernate-resume: support resuming through efivar HibernateLocation 27330/head
authorMike Yuan <me@yhndnzj.com>
Mon, 24 Apr 2023 16:34:19 +0000 (00:34 +0800)
committerMike Yuan <me@yhndnzj.com>
Fri, 23 Jun 2023 16:04:32 +0000 (00:04 +0800)
man/systemd-hibernate-resume-generator.xml
src/hibernate-resume/hibernate-resume-generator.c

index 7d0039dcf2eb9227f900ca5780ed630e88bb2b87..539b92cb3a65ea53f8647ba5f263de20932d755d 100644 (file)
@@ -32,7 +32,8 @@
     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>
 
index db23a1d5262a904a5e1631c38fa7646b7168248c..fac57d38c4e490e170b5ba209be07bee33cb9830 100644 (file)
@@ -2,17 +2,24 @@
 
 #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 "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();
 }