Skip to content

Commit

Permalink
Merge pull request #32043 from YHNdnzj/resume-clear-efi
Browse files Browse the repository at this point in the history
units: introduce systemd-hibernate-clear.service that clears stale HibernateLocation EFI variable
  • Loading branch information
yuwata committed Apr 3, 2024
2 parents a33a636 + 5f0cd57 commit 3a6bee0
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 59 deletions.
7 changes: 3 additions & 4 deletions man/kernel-command-line.xml
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,9 @@
<term><varname>resumeflags=</varname></term>

<listitem>
<para>Enables resume from hibernation using the specified
device and mount options. All
<citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>-like
paths are supported. For details, see
<para>Enable resume from hibernation using the specified device and timeout options. All
<citerefentry project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>-style
device identifiers are supported. For details, see
<citerefentry><refentrytitle>systemd-hibernate-resume-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>

<xi:include href="version-info.xml" xpointer="v217"/>
Expand Down
2 changes: 1 addition & 1 deletion man/rules/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ manpages = [
['systemd-hibernate-resume-generator', '8', [], 'ENABLE_HIBERNATE'],
['systemd-hibernate-resume.service',
'8',
['systemd-hibernate-resume'],
['systemd-hibernate-resume', 'systemd-hibernate-clear.service'],
'ENABLE_HIBERNATE'],
['systemd-homed.service', '8', ['systemd-homed'], 'ENABLE_HOMED'],
['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'],
Expand Down
8 changes: 8 additions & 0 deletions man/systemd-hibernate-resume.service.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

<refnamediv>
<refname>systemd-hibernate-resume.service</refname>
<refname>systemd-hibernate-clear.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-clear.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-hibernate-resume</filename></para>
</refsynopsisdiv>

Expand All @@ -37,6 +39,12 @@
<filename>/sys/power/resume</filename>, along with the offset in memory pages
(<filename>/sys/power/resume_offset</filename>) if supported.</para>

<para>The resume device node is either passed directly through arguments, or automatically acquired
from kernel command line options and/or <varname>HibernateLocation</varname> EFI variable. The latter
will normally be cleared by <filename>systemd-hibernate-resume.service</filename> on resumption.
If a stale variable is detected, it would be cleared by
<filename>systemd-hibernate-clear.service</filename>.</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>
Expand Down
35 changes: 10 additions & 25 deletions src/hibernate-resume/hibernate-resume-config.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,7 @@ static KernelHibernateLocation* kernel_hibernate_location_free(KernelHibernateLo

DEFINE_TRIVIAL_CLEANUP_FUNC(KernelHibernateLocation*, kernel_hibernate_location_free);

typedef struct EFIHibernateLocation {
char *device;

sd_id128_t uuid;
uint64_t offset;

char *kernel_version;
char *id;
char *image_id;
char *version_id;
char *image_version;
} EFIHibernateLocation;

static EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e) {
EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e) {
if (!e)
return NULL;

Expand All @@ -55,8 +42,6 @@ static EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e
return mfree(e);
}

DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free);

void hibernate_info_done(HibernateInfo *info) {
assert(info);

Expand Down Expand Up @@ -140,7 +125,7 @@ static bool validate_efi_hibernate_location(EFIHibernateLocation *e) {

if (!streq_ptr(id, e->id) ||
!streq_ptr(image_id, e->image_id)) {
log_notice("HibernateLocation system identifier doesn't match currently running system, not resuming from it.");
log_notice("HibernateLocation system identifier doesn't match currently running system, would not resume from it.");
return false;
}

Expand All @@ -152,8 +137,9 @@ static bool validate_efi_hibernate_location(EFIHibernateLocation *e) {

return true;
}
#endif

static int get_efi_hibernate_location(EFIHibernateLocation **ret) {
int get_efi_hibernate_location(EFIHibernateLocation **ret) {

static const JsonDispatch dispatch_table[] = {
{ "uuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(EFIHibernateLocation, uuid), JSON_MANDATORY },
Expand All @@ -171,8 +157,6 @@ static int get_efi_hibernate_location(EFIHibernateLocation **ret) {
_cleanup_free_ char *location_str = NULL;
int r;

assert(ret);

if (!is_efi_boot())
goto skip;

Expand Down Expand Up @@ -211,15 +195,18 @@ static int get_efi_hibernate_location(EFIHibernateLocation **ret) {
if (asprintf(&e->device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(e->uuid)) < 0)
return log_oom();

*ret = TAKE_PTR(e);
if (ret)
*ret = TAKE_PTR(e);
return 1;

skip:
*ret = NULL;
if (ret)
*ret = NULL;
return 0;
}

void compare_hibernate_location_and_warn(const HibernateInfo *info) {
#if ENABLE_EFI
int r;

assert(info);
Expand All @@ -243,8 +230,8 @@ void compare_hibernate_location_and_warn(const HibernateInfo *info) {
if (info->cmdline->offset != info->efi->offset)
log_warning("resume_offset=%" PRIu64 " doesn't match with EFI HibernateLocation offset %" PRIu64 ", proceeding anyway with resume_offset=.",
info->cmdline->offset, info->efi->offset);
}
#endif
}

int acquire_hibernate_info(HibernateInfo *ret) {
_cleanup_(hibernate_info_done) HibernateInfo i = {};
Expand All @@ -254,11 +241,9 @@ int acquire_hibernate_info(HibernateInfo *ret) {
if (r < 0)
return r;

#if ENABLE_EFI
r = get_efi_hibernate_location(&i.efi);
if (r < 0)
return r;
#endif

if (i.cmdline) {
i.device = i.cmdline->device;
Expand Down
31 changes: 20 additions & 11 deletions src/hibernate-resume/hibernate-resume-config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,27 @@

#include "sd-id128.h"

#include "macro.h"

typedef struct KernelHibernateLocation KernelHibernateLocation;
typedef struct EFIHibernateLocation EFIHibernateLocation;

typedef struct EFIHibernateLocation {
char *device;

sd_id128_t uuid;
uint64_t offset;

char *kernel_version;
char *id;
char *image_id;
char *version_id;
char *image_version;
} EFIHibernateLocation;

EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e);
DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free);

int get_efi_hibernate_location(EFIHibernateLocation **ret);

typedef struct HibernateInfo {
const char *device;
Expand All @@ -20,14 +39,4 @@ void hibernate_info_done(HibernateInfo *info);

int acquire_hibernate_info(HibernateInfo *ret);

#if ENABLE_EFI

void compare_hibernate_location_and_warn(const HibernateInfo *info);

#else

static inline void compare_hibernate_location_and_warn(const HibernateInfo *info) {
return;
}

#endif
126 changes: 113 additions & 13 deletions src/hibernate-resume/hibernate-resume.c
Original file line number Diff line number Diff line change
@@ -1,21 +1,95 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <errno.h>
#include <getopt.h>
#include <sys/stat.h>

#include "build.h"
#include "devnum-util.h"
#include "hibernate-resume-config.h"
#include "hibernate-util.h"
#include "initrd-util.h"
#include "log.h"
#include "main-func.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "static-destruct.h"
#include "terminal-util.h"

static HibernateInfo arg_info = {};
static bool arg_clear = false;

STATIC_DESTRUCTOR_REGISTER(arg_info, hibernate_info_done);

static int help(void) {
_cleanup_free_ char *link = NULL;
int r;

r = terminal_urlify_man("systemd-hibernate-resume", "8", &link);
if (r < 0)
return log_oom();

printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n"
"\n%sInitiate resume from hibernation.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --clear Clear hibernation storage information from EFI and exit\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);

return 0;
}

static int parse_argv(int argc, char *argv[]) {

enum {
ARG_VERSION = 0x100,
ARG_CLEAR,
};

static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "clear", no_argument, NULL, ARG_CLEAR },
{}
};

int c;

assert(argc >= 0);
assert(argv);

while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)

switch (c) {

case 'h':
return help();

case ARG_VERSION:
return version();

case ARG_CLEAR:
arg_clear = true;
break;

case '?':
return -EINVAL;

default:
assert_not_reached();
}

if (argc > optind && arg_clear)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Extraneous arguments specified with --clear, refusing.");

return 1;
}

static int setup_hibernate_info_and_warn(void) {
int r;

Expand All @@ -32,42 +106,68 @@ static int setup_hibernate_info_and_warn(void) {
return 1;
}

static int action_clear(void) {
int r;

assert(arg_clear);

/* Let's insist that the system identifier is verified still. After all if things don't match,
* the resume wouldn't get triggered in the first place. We should not erase the var if booted
* from LiveCD/portable systems/... */
r = get_efi_hibernate_location(/* ret = */ NULL);
if (r <= 0)
return r;

r = clear_efi_hibernate_location_and_warn();
if (r > 0)
log_notice("Successfully cleared HibernateLocation EFI variable.");
return r;
}

static int run(int argc, char *argv[]) {
struct stat st;
int r;

log_setup();

if (argc < 1 || argc > 3)
r = parse_argv(argc, argv);
if (r <= 0)
return r;

if (argc - optind > 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects zero, one, or two arguments.");

umask(0022);

if (!in_initrd())
return 0;
if (arg_clear)
return action_clear();

if (argc > 1) {
arg_info.device = argv[1];
if (!in_initrd())
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Not running in initrd, refusing to initiate resume from hibernation.");

if (argc == 3) {
r = safe_atou64(argv[2], &arg_info.offset);
if (r < 0)
return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[2]);
}
} else {
if (argc <= optind) {
r = setup_hibernate_info_and_warn();
if (r <= 0)
return r;

if (arg_info.efi)
clear_efi_hibernate_location_and_warn();
(void) clear_efi_hibernate_location_and_warn();
} else {
arg_info.device = ASSERT_PTR(argv[optind]);

if (argc - optind == 2) {
r = safe_atou64(argv[optind + 1], &arg_info.offset);
if (r < 0)
return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[optind + 1]);
}
}

if (stat(arg_info.device, &st) < 0)
return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_info.device);

if (!S_ISBLK(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK),
"Resume device '%s' is not a block device.", arg_info.device);

/* The write shall not return if a resume takes place. */
Expand Down
10 changes: 7 additions & 3 deletions src/shared/hibernate-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -513,13 +513,17 @@ int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
return 0;
}

void clear_efi_hibernate_location_and_warn(void) {
int clear_efi_hibernate_location_and_warn(void) {
int r;

if (!is_efi_boot())
return;
return 0;

r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);
if (r == -ENOENT)
return 0;
if (r < 0)
log_warning_errno(r, "Failed to clear EFI variable HibernateLocation, ignoring: %m");
return log_warning_errno(r, "Failed to clear EFI variable HibernateLocation: %m");

return 1;
}

0 comments on commit 3a6bee0

Please sign in to comment.