hibernate-resume: support resuming through efivar HibernateLocation

This commit is contained in:
Mike Yuan 2023-04-25 00:34:19 +08:00
parent 90efe8a6d4
commit 9deeca1275
No known key found for this signature in database
GPG key ID: 417471C0A40F58B3
2 changed files with 107 additions and 1 deletions

View 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>

View 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();
}