mirror of
https://github.com/systemd/systemd.git
synced 2024-11-05 15:21:37 +03:00
Merge pull request #14511 from keszybz/sleep-check-rework
Sleep check rework
This commit is contained in:
commit
a1edbc5118
@ -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"
|
||||
@ -180,39 +181,36 @@ 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;
|
||||
_cleanup_close_ int fd = -1;
|
||||
static int swap_device_to_device_id(const SwapEntry *swap, dev_t *ret_dev) {
|
||||
struct stat sb;
|
||||
dev_t swap_dev;
|
||||
int r;
|
||||
|
||||
assert(swap);
|
||||
assert(swap->device);
|
||||
assert(swap->type);
|
||||
|
||||
fd = open(swap->device, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(errno, "Unable to open '%s': %m", swap->device);
|
||||
|
||||
r = fstat(fd, &sb);
|
||||
r = stat(swap->device, &sb);
|
||||
if (r < 0)
|
||||
return log_debug_errno(errno, "Unable to stat %s: %m", swap->device);
|
||||
return r;
|
||||
|
||||
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")) {
|
||||
if (!S_ISBLK(sb.st_mode))
|
||||
return -ENOTBLK;
|
||||
*ret_dev = sb.st_rdev;
|
||||
return 0;
|
||||
|
||||
*ret = TAKE_PTR(major_minor);
|
||||
|
||||
return 0;
|
||||
} else
|
||||
return get_block_device(swap->device, ret_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to calculate the swap file offset on supported filesystems. On unsuported
|
||||
* filesystems, a debug message is logged and ret_offset is set to UINT64_MAX.
|
||||
*/
|
||||
static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
_cleanup_free_ struct fiemap *fiemap = NULL;
|
||||
@ -234,8 +232,8 @@ static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offse
|
||||
if (btrfs < 0)
|
||||
return log_error_errno(btrfs, "Error checking %s for Btrfs filesystem: %m", swap->device);
|
||||
else if (btrfs > 0) {
|
||||
log_debug("Detection of swap file offset on Btrfs is not supported: %s; skipping", swap->device);
|
||||
*ret_offset = 0;
|
||||
log_debug("%s: detection of swap file offset on Btrfs is not supported", swap->device);
|
||||
*ret_offset = UINT64_MAX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -248,15 +246,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.");
|
||||
@ -268,24 +271,26 @@ 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")) {
|
||||
log_debug("Found offset in /sys/power/resume_offset: %" PRIu64 "; no device id found in /sys/power/resume; ignoring resume_offset",
|
||||
if (resume_offset > 0 && resume == 0)
|
||||
log_debug("Warning: found /sys/power/resume_offset==%" PRIu64 ", but /sys/power/resume unset. Misconfiguration?",
|
||||
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, dev_t sys_resume, uint64_t sys_offset) {
|
||||
if (!location)
|
||||
return false;
|
||||
|
||||
return streq(sys_resume, location->resume) && sys_offset == location->resume_offset;
|
||||
return sys_resume > 0 &&
|
||||
sys_resume == location->devno &&
|
||||
(sys_offset == location->offset || (sys_offset > 0 && location->offset == UINT64_MAX));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -293,16 +298,20 @@ static bool location_is_resume_device(const HibernateLocation *location, const c
|
||||
* /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 - Values are set in /sys/power/resume and /sys/power/resume_offset.
|
||||
* ret_hibernate_location will represent matching /proc/swap entry if identified or NULL if not.
|
||||
*
|
||||
* 0 - No values are set in /sys/power/resume and /sys/power/resume_offset.
|
||||
ret_hibernate_location will represent 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;
|
||||
bool resume_match = false;
|
||||
int r;
|
||||
|
||||
/* read the /sys/power/resume & /sys/power/resume_offset values */
|
||||
@ -318,7 +327,7 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
|
||||
}
|
||||
|
||||
(void) fscanf(f, "%*s %*s %*s %*s %*s\n");
|
||||
for (i = 1;; i++) {
|
||||
for (unsigned i = 1;; i++) {
|
||||
_cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
|
||||
uint64_t swap_offset = 0;
|
||||
int k;
|
||||
@ -350,59 +359,88 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
|
||||
r = calculate_swap_file_offset(swap, &swap_offset);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} else if (streq(swap->type, "partition")) {
|
||||
const char *fn;
|
||||
|
||||
fn = path_startswith(swap->device, "/dev/");
|
||||
if (fn && startswith(fn, "zram")) {
|
||||
log_debug("Ignoring compressed RAM swap device '%s'.", swap->device);
|
||||
log_debug("%s: ignoring zram swap", swap->device);
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
log_debug("Swap type %s is unsupported for hibernation: %s; skipping", swap->type, swap->device);
|
||||
log_debug("%s: swap type %s is unsupported for hibernation, ignoring", swap->device, swap->type);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* prefer resume device or highest priority swap with most remaining space */
|
||||
if (!hibernate_location || swap->priority > hibernate_location->swap->priority
|
||||
|| ((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);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
hibernate_location = hibernate_location_free(hibernate_location);
|
||||
hibernate_location = new(HibernateLocation, 1);
|
||||
if (!hibernate_location)
|
||||
return log_oom();
|
||||
|
||||
*hibernate_location = (HibernateLocation) {
|
||||
.resume = TAKE_PTR(swap_device_id),
|
||||
.resume_offset = swap_offset,
|
||||
.swap = TAKE_PTR(swap),
|
||||
};
|
||||
|
||||
/* if the swap is the resume device, stop looping swaps */
|
||||
if (location_is_resume_device(hibernate_location, sys_resume, sys_offset))
|
||||
break;
|
||||
if (hibernate_location && swap->priority < hibernate_location->swap->priority) {
|
||||
log_debug("%s: ignoring device with lower priority", swap->device);
|
||||
continue;
|
||||
}
|
||||
if (hibernate_location &&
|
||||
(swap->priority == hibernate_location->swap->priority
|
||||
&& swap->size - swap->used < hibernate_location->swap->size - hibernate_location->swap->used)) {
|
||||
log_debug("%s: ignoring device with lower usable space", swap->device);
|
||||
continue;
|
||||
}
|
||||
|
||||
dev_t swap_device;
|
||||
r = swap_device_to_device_id(swap, &swap_device);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "%s: failed to query device number: %m", swap->device);
|
||||
|
||||
hibernate_location = hibernate_location_free(hibernate_location);
|
||||
hibernate_location = new(HibernateLocation, 1);
|
||||
if (!hibernate_location)
|
||||
return log_oom();
|
||||
|
||||
*hibernate_location = (HibernateLocation) {
|
||||
.devno = swap_device,
|
||||
.offset = swap_offset,
|
||||
.swap = TAKE_PTR(swap),
|
||||
};
|
||||
|
||||
/* if the swap is the resume device, stop the loop */
|
||||
if (location_is_resume_device(hibernate_location, sys_resume, sys_offset)) {
|
||||
log_debug("%s: device matches configured resume settings.", hibernate_location->swap->device);
|
||||
resume_match = true;
|
||||
break;
|
||||
}
|
||||
|
||||
log_debug("%s: is a candidate device.", hibernate_location->swap->device);
|
||||
}
|
||||
|
||||
/* We found nothing at all */
|
||||
if (!hibernate_location)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found");
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
|
||||
"No possible swap partitions or files suitable for hibernation were found in /proc/swaps.");
|
||||
|
||||
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);
|
||||
/* resume= is set but a matching /proc/swaps entry was not found */
|
||||
if (sys_resume != 0 && !resume_match)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
|
||||
"No swap partitions or files matching resume config 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 (hibernate_location->offset == UINT64_MAX) {
|
||||
if (sys_offset == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "Offset detection failed and /sys/power/resume_offset is not set.");
|
||||
|
||||
hibernate_location->offset = sys_offset;
|
||||
}
|
||||
|
||||
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->devno), minor(hibernate_location->devno),
|
||||
hibernate_location->offset, hibernate_location->swap->priority);
|
||||
else
|
||||
log_debug("/sys/power/resume is not configured; attempting to hibernate with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
|
||||
hibernate_location->swap->device, major(hibernate_location->devno), minor(hibernate_location->devno),
|
||||
hibernate_location->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;
|
||||
@ -421,6 +459,18 @@ static bool enough_swap_for_hibernation(void) {
|
||||
if (r < 0)
|
||||
return false;
|
||||
|
||||
/* If /sys/power/{resume,resume_offset} is configured but a matching entry
|
||||
* could not be identified in /proc/swaps, user is likely using Btrfs with a swapfile;
|
||||
* return true and let the system attempt hibernation.
|
||||
*/
|
||||
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");
|
||||
|
@ -40,8 +40,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
|
||||
* and the matching /proc/swap entry.
|
||||
*/
|
||||
typedef struct HibernateLocation {
|
||||
char *resume;
|
||||
uint64_t resume_offset;
|
||||
dev_t devno;
|
||||
uint64_t offset;
|
||||
SwapEntry *swap;
|
||||
} HibernateLocation;
|
||||
|
||||
|
@ -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->devno), minor(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->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"))
|
||||
@ -61,17 +62,17 @@ static int write_hibernate_location_info(const HibernateLocation *hibernate_loca
|
||||
"Invalid hibernate type: %s", hibernate_location->swap->type);
|
||||
|
||||
/* Only available in 4.17+ */
|
||||
if (hibernate_location->resume_offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) {
|
||||
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->device, hibernate_location->resume_offset);
|
||||
hibernate_location->swap->device, hibernate_location->offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_debug_errno(errno, "/sys/power/resume_offset not writeable: %m");
|
||||
}
|
||||
|
||||
xsprintf(offset_str, "%" PRIu64, hibernate_location->resume_offset);
|
||||
xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
|
||||
r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m",
|
||||
|
Loading…
Reference in New Issue
Block a user