mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #27330 from YHNdnzj/hibernate-resume-auto
sleep/hibernate-resume: pass hibernate location through efivar for resume without kernel cmdline
This commit is contained in:
@@ -210,7 +210,7 @@ emergency.service | | |
|
||||
|
||||
Before any file systems are mounted, the manager will determine whether the system shall resume from
|
||||
hibernation or proceed with normal boot. This is accomplished by
|
||||
<filename>systemd-hibernate-resume@.service</filename> which must be finished before
|
||||
<filename>systemd-hibernate-resume.service</filename> which must be finished before
|
||||
<filename>local-fs-pre.target</filename>, so no filesystems can be mounted before the check is complete.
|
||||
|
||||
When the root device becomes available,
|
||||
|
||||
@@ -931,7 +931,7 @@ manpages = [
|
||||
['systemd-getty-generator', '8', [], ''],
|
||||
['systemd-gpt-auto-generator', '8', [], 'HAVE_BLKID'],
|
||||
['systemd-hibernate-resume-generator', '8', [], 'ENABLE_HIBERNATE'],
|
||||
['systemd-hibernate-resume@.service',
|
||||
['systemd-hibernate-resume.service',
|
||||
'8',
|
||||
['systemd-hibernate-resume'],
|
||||
'ENABLE_HIBERNATE'],
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
<para><command>systemd-hibernate-resume-generator</command> is a
|
||||
generator that initiates the procedure to resume the system from hibernation.
|
||||
It instantiates the
|
||||
<citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
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>
|
||||
|
||||
@@ -55,6 +56,13 @@
|
||||
supported.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>resume_offset=</varname></term>
|
||||
|
||||
<listitem><para>Takes the page offset of the swap space from the resume device.
|
||||
Defaults to <literal>0</literal>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>resumeflags=</varname></term>
|
||||
|
||||
@@ -75,7 +83,7 @@
|
||||
<title>See Also</title>
|
||||
<para>
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-hibernate-resume@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-hibernate-resume.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
@@ -3,46 +3,43 @@
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
<refentry id="systemd-hibernate-resume@.service" conditional='ENABLE_HIBERNATE'>
|
||||
<refentry id="systemd-hibernate-resume.service" conditional='ENABLE_HIBERNATE'>
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-hibernate-resume@.service</title>
|
||||
<title>systemd-hibernate-resume.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-hibernate-resume@.service</refentrytitle>
|
||||
<refentrytitle>systemd-hibernate-resume.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-hibernate-resume@.service</refname>
|
||||
<refname>systemd-hibernate-resume.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-resume.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-hibernate-resume</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><filename>systemd-hibernate-resume@.service</filename>
|
||||
initiates the resume from hibernation. It is instantiated with the
|
||||
device to resume from as the template argument.</para>
|
||||
<para><filename>systemd-hibernate-resume.service</filename> initiates the resume from hibernation.</para>
|
||||
|
||||
<para><filename>systemd-hibernate-resume</filename> only supports
|
||||
the in-kernel hibernation implementation, see
|
||||
<ulink url="https://docs.kernel.org/power/swsusp.html">Swap suspend</ulink>.
|
||||
Internally, it works by writing the major:minor of specified
|
||||
device node to <filename>/sys/power/resume</filename>.</para>
|
||||
<para><filename>systemd-hibernate-resume</filename> only supports the in-kernel hibernation
|
||||
implementation, see <ulink url="https://docs.kernel.org/power/swsusp.html">Swap suspend</ulink>.
|
||||
Internally, it works by writing the major:minor of specified device node to
|
||||
<filename>/sys/power/resume</filename>, along with the offset in memory pages
|
||||
(<filename>/sys/power/resume_offset</filename>) if supported.</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 case, the
|
||||
boot is ordinarily continued.</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>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@@ -93,6 +93,7 @@
|
||||
#define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service"
|
||||
#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service"
|
||||
#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service"
|
||||
#define SPECIAL_HIBERNATE_RESUME_SERVICE "systemd-hibernate-resume.service"
|
||||
|
||||
/* Services systemd relies on */
|
||||
#define SPECIAL_DBUS_SERVICE "dbus.service"
|
||||
|
||||
@@ -479,7 +479,9 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
r = device_path_make_canonical(S_IFBLK, devno, &path);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
return log_error_errno(r,
|
||||
"Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
|
||||
DEVNUM_FORMAT_VAL(devno));
|
||||
|
||||
puts(path);
|
||||
return 0;
|
||||
|
||||
@@ -502,7 +502,7 @@ static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p,
|
||||
* it's not technically part of the basic initrd filesystem itself, and so
|
||||
* shouldn't inherit the default Before=local-fs.target dependency. However,
|
||||
* these mounts still need to start after local-fs-pre.target, as a sync point
|
||||
* for things like systemd-hibernate-resume@.service that should start before
|
||||
* for things like systemd-hibernate-resume.service that should start before
|
||||
* any mounts. */
|
||||
|
||||
after = SPECIAL_LOCAL_FS_PRE_TARGET;
|
||||
|
||||
@@ -2,16 +2,25 @@
|
||||
|
||||
#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 "mkdir-label.h"
|
||||
#include "os-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "special.h"
|
||||
#include "string-util.h"
|
||||
@@ -22,14 +31,28 @@ static char *arg_resume_device = NULL;
|
||||
static char *arg_resume_options = NULL;
|
||||
static char *arg_root_options = NULL;
|
||||
static bool arg_noresume = false;
|
||||
static uint64_t arg_resume_offset = 0;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_resume_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_resume_options, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
|
||||
|
||||
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
|
||||
#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
|
||||
|
||||
if (streq(key, "resume")) {
|
||||
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
|
||||
int r;
|
||||
|
||||
if (proc_cmdline_key_streq(key, "resume")) {
|
||||
char *s;
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
@@ -41,7 +64,16 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
|
||||
free_and_replace(arg_resume_device, s);
|
||||
|
||||
} else if (streq(key, "resumeflags")) {
|
||||
} else if (proc_cmdline_key_streq(key, "resume_offset")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
|
||||
r = safe_atou64(value, &arg_resume_offset);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse resume_offset=%s: %m", value);
|
||||
|
||||
} else if (proc_cmdline_key_streq(key, "resumeflags")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
@@ -49,7 +81,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
if (!strextend_with_separator(&arg_resume_options, ",", value))
|
||||
return log_oom();
|
||||
|
||||
} else if (streq(key, "rootflags")) {
|
||||
} else if (proc_cmdline_key_streq(key, "rootflags")) {
|
||||
|
||||
if (proc_cmdline_value_missing(key, value))
|
||||
return 0;
|
||||
@@ -57,7 +89,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
|
||||
if (!strextend_with_separator(&arg_root_options, ",", value))
|
||||
return log_oom();
|
||||
|
||||
} else if (streq(key, "noresume")) {
|
||||
} else if (proc_cmdline_key_streq(key, "noresume")) {
|
||||
if (value) {
|
||||
log_warning("\"noresume\" kernel command line switch specified with an argument, ignoring.");
|
||||
return 0;
|
||||
@@ -69,36 +101,136 @@ 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 *service_unit = NULL, *device_unit = NULL, *lnk = NULL;
|
||||
_cleanup_free_ char *device_unit = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
|
||||
if (!arg_resume_device)
|
||||
return 0;
|
||||
|
||||
r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_device, ".service",
|
||||
&service_unit);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
|
||||
lnk = strjoin(arg_dest, "/" SPECIAL_SYSINIT_TARGET ".wants/", service_unit);
|
||||
if (!lnk)
|
||||
return log_oom();
|
||||
|
||||
(void) mkdir_parents_label(lnk, 0755);
|
||||
if (symlink(SYSTEM_DATA_UNIT_DIR "/systemd-hibernate-resume@.service", lnk) < 0)
|
||||
return log_error_errno(errno, "Failed to create symlink %s: %m", lnk);
|
||||
|
||||
r = unit_name_from_path(arg_resume_device, ".device", &device_unit);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate unit name: %m");
|
||||
return log_error_errno(r, "Failed to generate device unit name from path '%s': %m", arg_resume_device);
|
||||
|
||||
r = write_drop_in(arg_dest, device_unit, 40, "device-timeout",
|
||||
"# Automatically generated by systemd-hibernate-resume-generator\n\n"
|
||||
"[Unit]\n"
|
||||
"JobTimeoutSec=infinity\n");
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to write device timeout drop-in: %m");
|
||||
log_warning_errno(r, "Failed to write device timeout drop-in, ignoring: %m");
|
||||
|
||||
r = generator_open_unit_file(arg_dest, NULL, SPECIAL_HIBERNATE_RESUME_SERVICE, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
fprintf(f,
|
||||
"[Unit]\n"
|
||||
"Description=Resume from hibernation\n"
|
||||
"Documentation=man:systemd-hibernate-resume.service(8)\n"
|
||||
"DefaultDependencies=no\n"
|
||||
"BindsTo=%1$s\n"
|
||||
"Wants=local-fs-pre.target\n"
|
||||
"After=%1$s\n"
|
||||
"Before=local-fs-pre.target\n"
|
||||
"AssertPathExists=/etc/initrd-release\n"
|
||||
"\n"
|
||||
"[Service]\n"
|
||||
"Type=oneshot\n"
|
||||
"ExecStart=" ROOTLIBEXECDIR "/systemd-hibernate-resume %2$s %3$" PRIu64,
|
||||
device_unit,
|
||||
arg_resume_device,
|
||||
arg_resume_offset);
|
||||
|
||||
r = fflush_and_check(f);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create " SPECIAL_HIBERNATE_RESUME_SERVICE ": %m");
|
||||
|
||||
r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SPECIAL_HIBERNATE_RESUME_SERVICE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = generator_write_timeouts(arg_dest,
|
||||
arg_resume_device,
|
||||
@@ -112,7 +244,7 @@ static int process_resume(void) {
|
||||
}
|
||||
|
||||
static int run(const char *dest, const char *dest_early, const char *dest_late) {
|
||||
int r = 0;
|
||||
int r;
|
||||
|
||||
arg_dest = ASSERT_PTR(dest);
|
||||
|
||||
@@ -131,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,55 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "devnum-util.h"
|
||||
#include "fileio.h"
|
||||
#include "initrd-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "parse-util.h"
|
||||
#include "sleep-util.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
static const char *arg_resume_device = NULL;
|
||||
static uint64_t arg_resume_offset = 0; /* in memory pages */
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
struct stat st;
|
||||
const char *device;
|
||||
int r;
|
||||
|
||||
if (argc != 2) {
|
||||
log_error("This program expects one argument.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
log_setup();
|
||||
|
||||
if (argc < 2 || argc > 3)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects one or two arguments.");
|
||||
|
||||
umask(0022);
|
||||
|
||||
/* Refuse to run unless we are in an initrd() */
|
||||
if (!in_initrd())
|
||||
return EXIT_SUCCESS;
|
||||
return 0;
|
||||
|
||||
device = argv[1];
|
||||
arg_resume_device = argv[1];
|
||||
|
||||
if (stat(device, &st) < 0) {
|
||||
log_error_errno(errno, "Failed to stat '%s': %m", device);
|
||||
return EXIT_FAILURE;
|
||||
if (argc == 3) {
|
||||
r = safe_atou64(argv[2], &arg_resume_offset);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[2]);
|
||||
}
|
||||
|
||||
if (!S_ISBLK(st.st_mode)) {
|
||||
log_error("Resume device '%s' is not a block device.", device);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (stat(arg_resume_device, &st) < 0)
|
||||
return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_resume_device);
|
||||
|
||||
r = write_string_file("/sys/power/resume", FORMAT_DEVNUM(st.st_rdev), WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to write '" DEVNUM_FORMAT_STR "' to /sys/power/resume: %m", DEVNUM_FORMAT_VAL(st.st_rdev));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!S_ISBLK(st.st_mode))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Resume device '%s' is not a block device.", arg_resume_device);
|
||||
|
||||
/*
|
||||
* The write above shall not return.
|
||||
*
|
||||
* However, failed resume is a normal condition (may mean that there is
|
||||
* no hibernation image).
|
||||
*/
|
||||
/* The write shall not return if a resume takes place. */
|
||||
r = write_resume_config(st.st_rdev, arg_resume_offset, arg_resume_device);
|
||||
log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG,
|
||||
r < 0 ? r : SYNTHETIC_ERRNO(ENOENT),
|
||||
"Unable to resume from device '%s' (" DEVNUM_FORMAT_STR ") offset %" PRIu64 ", continuing boot process.",
|
||||
arg_resume_device, DEVNUM_FORMAT_VAL(st.st_rdev), arg_resume_offset);
|
||||
|
||||
log_info("Could not resume from '%s' (" DEVNUM_FORMAT_STR ").", device, DEVNUM_FORMAT_VAL(st.st_rdev));
|
||||
return EXIT_SUCCESS;
|
||||
return r;
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
@@ -1028,6 +1028,52 @@ int read_fiemap(int fd, struct fiemap **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
|
||||
char offset_str[DECIMAL_STR_MAX(uint64_t)];
|
||||
_cleanup_free_ char *path = NULL;
|
||||
const char *devno_str;
|
||||
int r;
|
||||
|
||||
devno_str = FORMAT_DEVNUM(devno);
|
||||
xsprintf(offset_str, "%" PRIu64, offset);
|
||||
|
||||
if (!device) {
|
||||
r = device_path_make_canonical(S_IFBLK, devno, &path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r,
|
||||
"Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m",
|
||||
DEVNUM_FORMAT_VAL(devno));
|
||||
device = path;
|
||||
}
|
||||
|
||||
/* We write the offset first since it's safer. Note that this file is only available in 4.17+, so
|
||||
* fail gracefully if it doesn't exist and we're only overwriting it with 0. */
|
||||
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
if (r == -ENOENT) {
|
||||
if (offset != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Can't configure hibernation offset %" PRIu64 ", kernel does not support /sys/power/resume_offset. Refusing.",
|
||||
offset);
|
||||
|
||||
log_warning_errno(r, "/sys/power/resume_offset is unavailable, skipping writing swap file offset.");
|
||||
} else if (r < 0)
|
||||
return log_error_errno(r,
|
||||
"Failed to write swap file offset %s to /sys/power/resume_offset for device '%s': %m",
|
||||
offset_str, device);
|
||||
else
|
||||
log_debug("Wrote resume_offset=%s for device '%s' to /sys/power/resume_offset.",
|
||||
offset_str, device);
|
||||
|
||||
r = write_string_file("/sys/power/resume", devno_str, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
if (r < 0)
|
||||
return log_error_errno(r,
|
||||
"Failed to write device '%s' (%s) to /sys/power/resume: %m",
|
||||
device, devno_str);
|
||||
log_debug("Wrote resume=%s for device '%s' to /sys/power/resume.", devno_str, device);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int can_sleep_internal(const SleepConfig *sleep_config, SleepOperation operation, bool check_allowed);
|
||||
|
||||
static bool can_s2h(const SleepConfig *sleep_config) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <linux/fiemap.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "time-util.h"
|
||||
@@ -63,6 +64,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(HibernateLocation*, hibernate_location_free);
|
||||
int read_fiemap(int fd, struct fiemap **ret);
|
||||
int parse_sleep_config(SleepConfig **sleep_config);
|
||||
int find_hibernate_location(HibernateLocation **ret_hibernate_location);
|
||||
int write_resume_config(dev_t devno, uint64_t offset, const char *device);
|
||||
|
||||
int can_sleep(SleepOperation operation);
|
||||
int can_sleep_disk(char **types);
|
||||
|
||||
@@ -12,9 +12,12 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-device.h"
|
||||
#include "sd-id128.h"
|
||||
#include "sd-messages.h"
|
||||
|
||||
#include "battery-util.h"
|
||||
@@ -25,13 +28,17 @@
|
||||
#include "bus-util.h"
|
||||
#include "constants.h"
|
||||
#include "devnum-util.h"
|
||||
#include "efivars.h"
|
||||
#include "exec-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "id128-util.h"
|
||||
#include "io-util.h"
|
||||
#include "json.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "os-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "sleep-util.h"
|
||||
@@ -45,49 +52,85 @@
|
||||
|
||||
static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID;
|
||||
|
||||
static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
|
||||
char offset_str[DECIMAL_STR_MAX(uint64_t)];
|
||||
const char *resume_str;
|
||||
int r;
|
||||
static int write_efi_hibernate_location(const HibernateLocation *hibernate_location, bool required) {
|
||||
int r = 0;
|
||||
|
||||
#if ENABLE_EFI
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||
_cleanup_free_ char *formatted = NULL, *id = NULL, *image_id = NULL,
|
||||
*version_id = NULL, *image_version = NULL;
|
||||
_cleanup_(sd_device_unrefp) sd_device *device = NULL;
|
||||
const char *uuid_str;
|
||||
sd_id128_t uuid;
|
||||
struct utsname uts = {};
|
||||
int log_level, log_level_ignore;
|
||||
|
||||
assert(hibernate_location);
|
||||
assert(hibernate_location->swap);
|
||||
|
||||
resume_str = FORMAT_DEVNUM(hibernate_location->devno);
|
||||
|
||||
r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
|
||||
hibernate_location->swap->path, resume_str);
|
||||
|
||||
log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->path, resume_str);
|
||||
|
||||
/* if it's a swap partition, we're done */
|
||||
if (hibernate_location->swap->type == SWAP_BLOCK)
|
||||
if (!is_efi_boot())
|
||||
return 0;
|
||||
|
||||
assert(hibernate_location->swap->type == SWAP_FILE);
|
||||
log_level = required ? LOG_ERR : LOG_DEBUG;
|
||||
log_level_ignore = required ? LOG_WARNING : LOG_DEBUG;
|
||||
|
||||
/* Only available in 4.17+ */
|
||||
if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) {
|
||||
if (errno == ENOENT) {
|
||||
log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64,
|
||||
hibernate_location->swap->path, hibernate_location->offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m");
|
||||
}
|
||||
|
||||
xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
|
||||
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
r = sd_device_new_from_devnum(&device, 'b', hibernate_location->devno);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m",
|
||||
hibernate_location->swap->path, offset_str);
|
||||
return log_full_errno(log_level, r, "Failed to create sd-device object for '%s': %m",
|
||||
hibernate_location->swap->path);
|
||||
|
||||
log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->path, offset_str);
|
||||
r = sd_device_get_property_value(device, "ID_FS_UUID", &uuid_str);
|
||||
if (r < 0)
|
||||
return log_full_errno(log_level, r, "Failed to get filesystem UUID for device '%s': %m",
|
||||
hibernate_location->swap->path);
|
||||
|
||||
return 0;
|
||||
r = sd_id128_from_string(uuid_str, &uuid);
|
||||
if (r < 0)
|
||||
return log_full_errno(log_level, r, "Failed to parse ID_FS_UUID '%s' for device '%s': %m",
|
||||
uuid_str, hibernate_location->swap->path);
|
||||
|
||||
if (uname(&uts) < 0)
|
||||
log_full_errno(log_level_ignore, 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_full_errno(log_level_ignore, r, "Failed to parse os-release, ignoring: %m");
|
||||
|
||||
r = json_build(&v, JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR_UUID("uuid", uuid),
|
||||
JSON_BUILD_PAIR_UNSIGNED("offset", hibernate_location->offset),
|
||||
JSON_BUILD_PAIR_CONDITION(!isempty(uts.release), "kernelVersion", JSON_BUILD_STRING(uts.release)),
|
||||
JSON_BUILD_PAIR_CONDITION(id, "osReleaseId", JSON_BUILD_STRING(id)),
|
||||
JSON_BUILD_PAIR_CONDITION(image_id, "osReleaseImageId", JSON_BUILD_STRING(image_id)),
|
||||
JSON_BUILD_PAIR_CONDITION(version_id, "osReleaseVersionId", JSON_BUILD_STRING(version_id)),
|
||||
JSON_BUILD_PAIR_CONDITION(image_version, "osReleaseImageVersion", JSON_BUILD_STRING(image_version))));
|
||||
if (r < 0)
|
||||
return log_full_errno(log_level, r, "Failed to build JSON object: %m");
|
||||
|
||||
r = json_variant_format(v, 0, &formatted);
|
||||
if (r < 0)
|
||||
return log_full_errno(log_level, r, "Failed to format JSON object: %m");
|
||||
|
||||
r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE(HibernateLocation), formatted);
|
||||
if (r < 0)
|
||||
return log_full_errno(log_level, r, "Failed to set EFI variable HibernateLocation: %m");
|
||||
|
||||
log_debug("Set EFI variable HibernateLocation to '%s'.", formatted);
|
||||
#endif
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int write_kernel_hibernate_location(const HibernateLocation *hibernate_location) {
|
||||
assert(hibernate_location);
|
||||
assert(hibernate_location->swap);
|
||||
assert(IN_SET(hibernate_location->swap->type, SWAP_BLOCK, SWAP_FILE));
|
||||
|
||||
return write_resume_config(hibernate_location->devno, hibernate_location->offset, hibernate_location->swap->path);
|
||||
}
|
||||
|
||||
static int write_mode(char **modes) {
|
||||
@@ -212,17 +255,23 @@ static int execute(
|
||||
|
||||
/* Configure hibernation settings if we are supposed to hibernate */
|
||||
if (!strv_isempty(modes)) {
|
||||
bool resume_set;
|
||||
|
||||
r = find_hibernate_location(&hibernate_location);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to find location to hibernate to: %m");
|
||||
if (r == 0) { /* 0 means: no hibernation location was configured in the kernel so far, let's
|
||||
* do it ourselves then. > 0 means: kernel already had a configured hibernation
|
||||
* location which we shouldn't touch. */
|
||||
r = write_hibernate_location_info(hibernate_location);
|
||||
resume_set = r > 0;
|
||||
|
||||
if (!resume_set) {
|
||||
r = write_kernel_hibernate_location(hibernate_location);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to prepare for hibernation: %m");
|
||||
}
|
||||
|
||||
r = write_efi_hibernate_location(hibernate_location, !resume_set);
|
||||
if (r < 0 && !resume_set)
|
||||
return r;
|
||||
|
||||
r = write_mode(modes);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");
|
||||
|
||||
@@ -281,10 +281,6 @@ units = [
|
||||
{ 'file' : 'systemd-growfs-root.service.in' },
|
||||
{ 'file' : 'systemd-growfs@.service.in' },
|
||||
{ 'file' : 'systemd-halt.service' },
|
||||
{
|
||||
'file' : 'systemd-hibernate-resume@.service.in',
|
||||
'conditions' : ['ENABLE_HIBERNATE'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-hibernate.service.in',
|
||||
'conditions' : ['ENABLE_HIBERNATE'],
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# 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=Resume from hibernation using device %f
|
||||
Documentation=man:systemd-hibernate-resume@.service(8)
|
||||
DefaultDependencies=no
|
||||
BindsTo=%i.device
|
||||
Wants=local-fs-pre.target
|
||||
After=%i.device
|
||||
Before=local-fs-pre.target
|
||||
AssertPathExists=/etc/initrd-release
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart={{ROOTLIBEXECDIR}}/systemd-hibernate-resume %f
|
||||
Reference in New Issue
Block a user