Merge pull request #32043 from YHNdnzj/resume-clear-efi

units: introduce systemd-hibernate-clear.service that clears stale HibernateLocation EFI variable
This commit is contained in:
Yu Watanabe 2024-04-04 02:43:00 +09:00 committed by GitHub
commit 3a6bee0510
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 193 additions and 59 deletions

View file

@ -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"/>

View file

@ -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'],

View file

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

View file

@ -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;
@ -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);
@ -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;
}
@ -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 },
@ -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;
@ -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);
@ -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 = {};
@ -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;

View file

@ -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;
@ -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

View file

@ -1,8 +1,10 @@
/* 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"
@ -10,12 +12,84 @@
#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;
@ -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 (arg_clear)
return action_clear();
if (!in_initrd())
return 0;
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Not running in initrd, refusing to initiate resume from hibernation.");
if (argc > 1) {
arg_info.device = argv[1];
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. */

View file

@ -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;
}

View file

@ -22,7 +22,7 @@ int hibernation_is_safe(void);
int write_resume_config(dev_t devno, uint64_t offset, const char *device);
void clear_efi_hibernate_location_and_warn(void);
int clear_efi_hibernate_location_and_warn(void);
/* Only for test-fiemap */
int read_fiemap(int fd, struct fiemap **ret);

View file

@ -308,7 +308,7 @@ static int execute(
fail:
if (SLEEP_OPERATION_IS_HIBERNATION(operation))
clear_efi_hibernate_location_and_warn();
(void) clear_efi_hibernate_location_and_warn();
return r;
}

View file

@ -312,6 +312,11 @@ units = [
{ 'file' : 'systemd-growfs-root.service.in' },
{ 'file' : 'systemd-growfs@.service.in' },
{ 'file' : 'systemd-halt.service' },
{
'file' : 'systemd-hibernate-clear.service.in',
'conditions' : ['ENABLE_HIBERNATE', 'ENABLE_EFI'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-hibernate-resume.service.in',
'conditions' : ['ENABLE_HIBERNATE'],

View file

@ -0,0 +1,24 @@
# 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=Clear Stale Hibernate Storage Info
Documentation=man:systemd-hibernate-clear.service(8)
ConditionPathExists=/sys/firmware/efi/efivars/HibernateLocation-8cf2644b-4b0b-428f-9387-6d876050dc67
ConditionPathExists=!/etc/initrd-release
DefaultDependencies=no
Before=sysinit.target shutdown.target
Conflicts=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-hibernate-resume --clear