Skip to content

Commit

Permalink
systemd-sleep: always attempt hibernation if configured
Browse files Browse the repository at this point in the history
When calculation of swap file offset is unsupported, rely on the
/sys/power/resume & /sys/power/resume_offset values if configured
rather than requiring a matching swap entry to be identified.

Refactor to use dev_t for comparison of resume= device instead of string.
  • Loading branch information
zachsmith committed Dec 21, 2019
1 parent 65ca546 commit e9b6fbd
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 37 deletions.
114 changes: 82 additions & 32 deletions src/shared/sleep-config.c
Expand Up @@ -15,6 +15,7 @@
#include <unistd.h>

#include "alloc-util.h"
#include "blockdev-util.h"
#include "btrfs-util.h"
#include "conf-parser.h"
#include "def.h"
Expand Down Expand Up @@ -180,13 +181,11 @@ HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
return NULL;

swap_entry_free(hl->swap);
free(hl->resume);

return mfree(hl);
}

static int swap_device_to_major_minor(const SwapEntry *swap, char **ret) {
_cleanup_free_ char *major_minor = NULL;
static int swap_device_to_device_id(const SwapEntry *swap, dev_t *ret_dev) {
_cleanup_close_ int fd = -1;
struct stat sb;
dev_t swap_dev;
Expand All @@ -204,15 +203,23 @@ static int swap_device_to_major_minor(const SwapEntry *swap, char **ret) {
if (r < 0)
return log_debug_errno(errno, "Unable to stat %s: %m", swap->device);

swap_dev = streq(swap->type, "partition") ? sb.st_rdev : sb.st_dev;
if (asprintf(&major_minor, "%u:%u", major(swap_dev), minor(swap_dev)) < 0)
return log_oom();
if (streq(swap->type, "partition"))
swap_dev = sb.st_rdev;
else {
r = get_block_device(swap->device, &swap_dev);
if (r < 0)
return r;
}

*ret = TAKE_PTR(major_minor);
*ret_dev = swap_dev;

return 0;
}

/*
* Attempt to calculate the swap file offset on supported filesystems. On unsuported
* filesystems, a debug message is logged and the ret_offset is set to 0.
*/
static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
_cleanup_close_ int fd = -1;
_cleanup_free_ struct fiemap *fiemap = NULL;
Expand Down Expand Up @@ -248,15 +255,20 @@ static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offse
return 0;
}

static int read_resume_files(char **ret_resume, uint64_t *ret_resume_offset) {
_cleanup_free_ char *resume = NULL, *resume_offset_str = NULL;
static int read_resume_files(dev_t *ret_resume, uint64_t *ret_resume_offset) {
_cleanup_free_ char *resume_str = NULL, *resume_offset_str = NULL;
dev_t resume;
uint64_t resume_offset = 0;
int r;

r = read_one_line_file("/sys/power/resume", &resume);
r = read_one_line_file("/sys/power/resume", &resume_str);
if (r < 0)
return log_debug_errno(r, "Error reading /sys/power/resume: %m");

r = parse_dev(resume_str, &resume);
if (r < 0)
return log_debug_errno(r, "Error parsing /sys/power/resume device: %s: %m", resume_str);

r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
if (r == -ENOENT)
log_debug("Kernel does not support resume_offset; swap file offset detection will be skipped.");
Expand All @@ -268,39 +280,49 @@ static int read_resume_files(char **ret_resume, uint64_t *ret_resume_offset) {
return log_error_errno(r, "Failed to parse value in /sys/power/resume_offset \"%s\": %m", resume_offset_str);
}

if (resume_offset > 0 && streq(resume, "0:0")) {
if (resume_offset > 0 && resume == 0) {
log_debug("Found offset in /sys/power/resume_offset: %" PRIu64 "; no device id found in /sys/power/resume; ignoring resume_offset",
resume_offset);
resume_offset = 0;
}

*ret_resume = TAKE_PTR(resume);
*ret_resume = resume;
*ret_resume_offset = resume_offset;

return 0;
}

static bool location_is_resume_device(const HibernateLocation *location, const char *sys_resume, const uint64_t sys_offset) {
assert(location);
assert(location->resume);
assert(sys_resume);
/*
* Determine if the HibernateLocation matches the resume= (device) and resume_offset= (file).
*/
static bool location_is_resume_device(const HibernateLocation *location, const dev_t sys_resume, const uint64_t sys_offset) {
if (!location)
return false;

if (sys_resume > 0 && sys_resume == location->resume && sys_offset == location->resume_offset)
return true;

return streq(sys_resume, location->resume) && sys_offset == location->resume_offset;
return false;
}

/*
* Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
* /sys/power/resume_offset.
*
* Returns:
* 1 - HibernateLocation matches values found in /sys/power/resume & /sys/power/resume_offset
* 0 - HibernateLocation is highest priority swap with most remaining space; no valid values exist in /sys/power/resume & /sys/power/resume_offset
* negative value in the case of error
* 1 - /sys/power/resume and /sys/power/resume_offset are set and a matching swap was found in /proc/swaps - (HibernateLocation represents
* the matching /proc/swaps entry) OR /sys/power/resume and /sys/power/resume_offset are set but a matching
* /proc/swaps entry was not found - (HibernateLocation is NULL).
*
* 0 - No values are set in /sys/power/resume and /sys/power/resume_offset. HibernateLocation represents the highest
* priority swap with most remaining space discovered in /proc/swaps.
*
* Negative value in the case of error.
*/
int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
_cleanup_free_ char *sys_resume = NULL;
dev_t sys_resume;
uint64_t sys_offset = 0;
unsigned i;
int r;
Expand Down Expand Up @@ -350,6 +372,10 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
r = calculate_swap_file_offset(swap, &swap_offset);
if (r < 0)
return r;

/* if the offset was not calculated, continue without considering the swap file */
if (swap_offset == 0)
continue;
} else if (streq(swap->type, "partition")) {
const char *fn;

Expand All @@ -368,8 +394,8 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
|| ((swap->priority == hibernate_location->swap->priority)
&& (swap->size - swap->used) > (hibernate_location->swap->size - hibernate_location->swap->used))) {

_cleanup_free_ char *swap_device_id = NULL;
r = swap_device_to_major_minor(swap, &swap_device_id);
dev_t swap_device;
r = swap_device_to_device_id(swap, &swap_device);
if (r < 0)
return r;

Expand All @@ -379,7 +405,7 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
return log_oom();

*hibernate_location = (HibernateLocation) {
.resume = TAKE_PTR(swap_device_id),
.resume = swap_device,
.resume_offset = swap_offset,
.swap = TAKE_PTR(swap),
};
Expand All @@ -390,19 +416,31 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
}
}

if (!hibernate_location)
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found");
bool resume_match = location_is_resume_device(hibernate_location, sys_resume, sys_offset);

/* resume= is set, but a matching /proc/swaps entry was not found */
if (!resume_match && sys_resume > 0) {
log_debug("/sys/power/resume appears to be configured but a matching swap in /proc/swaps could not be found; Hibernation may fail");
*ret_hibernate_location = NULL;

if (!streq(sys_resume, "0:0") && !location_is_resume_device(hibernate_location, sys_resume, sys_offset))
return log_warning_errno(SYNTHETIC_ERRNO(ENOSYS), "/sys/power/resume and /sys/power/resume_offset has no matching entry in /proc/swaps; Hibernation will fail: resume=%s, resume_offset=%" PRIu64,
sys_resume, sys_offset);
return 1;
}

if (!hibernate_location)
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files suitable for hibernation were found in /proc/swaps");

log_debug("Hibernation will attempt to use swap entry with path: %s, device: %s, offset: %" PRIu64 ", priority: %i",
hibernate_location->swap->device, hibernate_location->resume, hibernate_location->resume_offset, hibernate_location->swap->priority);
if (resume_match)
log_debug("Hibernation will attempt to use swap entry with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
hibernate_location->swap->device, major(hibernate_location->resume), minor(hibernate_location->resume),
hibernate_location->resume_offset, hibernate_location->swap->priority);
else
log_debug("/sys/power/resume and /sys/power/resume_offset are not configured; attempting to hibernate with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
hibernate_location->swap->device, major(hibernate_location->resume), minor(hibernate_location->resume),
hibernate_location->resume_offset, hibernate_location->swap->priority);

*ret_hibernate_location = TAKE_PTR(hibernate_location);

if (location_is_resume_device(*ret_hibernate_location, sys_resume, sys_offset))
if (resume_match)
return 1;

return 0;
Expand All @@ -421,6 +459,18 @@ static bool enough_swap_for_hibernation(void) {
if (r < 0)
return false;

/* This means /sys/power/{resume,resume_offset} is configured but a matching entry in
* could not be identified in /proc/swaps - return true and let the system attempt
* hibernation. User is likely using a swap file on Btrfs.
*/
if (r > 0 && !hibernate_location) {
log_debug("Unable to determine remaining swap space; hibernation may fail");
return true;
}

if (!hibernate_location)
return false;

r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
if (r < 0) {
log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
Expand Down
2 changes: 1 addition & 1 deletion src/shared/sleep-config.h
Expand Up @@ -40,7 +40,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
* and the matching /proc/swap entry.
*/
typedef struct HibernateLocation {
char *resume;
dev_t resume;
uint64_t resume_offset;
SwapEntry *swap;
} HibernateLocation;
Expand Down
9 changes: 5 additions & 4 deletions src/sleep/sleep.c
Expand Up @@ -39,18 +39,19 @@ STATIC_DESTRUCTOR_REGISTER(arg_verb, freep);

static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
char offset_str[DECIMAL_STR_MAX(uint64_t)];
char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")];
int r;

assert(hibernate_location);
assert(hibernate_location->swap);
assert(hibernate_location->resume);

r = write_string_file("/sys/power/resume", hibernate_location->resume, WRITE_STRING_FILE_DISABLE_BUFFER);
xsprintf(resume_str, "%u:%u", major(hibernate_location->resume), minor(hibernate_location->resume));
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->device, hibernate_location->resume);
hibernate_location->swap->device, resume_str);

log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, hibernate_location->resume);
log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, resume_str);

/* if it's a swap partition, we're done */
if (streq(hibernate_location->swap->type, "partition"))
Expand Down

0 comments on commit e9b6fbd

Please sign in to comment.