Skip to content

Commit 7bdf56a

Browse files
committed
systemd-sleep: always prefer resume device or file
This change checks each swap partition or file reported in /proc/swaps to see if it matches the values configured with resume= and resume_offset= kernel parameters. If a match is found, the matching swap entry is used as the hibernation location regardless of swap priority.
1 parent 10b843e commit 7bdf56a

File tree

3 files changed

+236
-119
lines changed

3 files changed

+236
-119
lines changed

src/shared/sleep-config.c

Lines changed: 180 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <unistd.h>
1818

1919
#include "alloc-util.h"
20+
#include "btrfs-util.h"
2021
#include "conf-parser.h"
2122
#include "def.h"
2223
#include "env-util.h"
@@ -28,6 +29,7 @@
2829
#include "parse-util.h"
2930
#include "path-util.h"
3031
#include "sleep-config.h"
32+
#include "stdio-util.h"
3133
#include "string-util.h"
3234
#include "strv.h"
3335
#include "time-util.h"
@@ -165,16 +167,7 @@ int can_sleep_disk(char **types) {
165167

166168
#define HIBERNATION_SWAP_THRESHOLD 0.98
167169

168-
/* entry in /proc/swaps */
169-
typedef struct SwapEntry {
170-
char *device;
171-
char *type;
172-
uint64_t size;
173-
uint64_t used;
174-
int priority;
175-
} SwapEntry;
176-
177-
static SwapEntry* swap_entry_free(SwapEntry *se) {
170+
SwapEntry* swap_entry_free(SwapEntry *se) {
178171
if (!se)
179172
return NULL;
180173

@@ -184,12 +177,141 @@ static SwapEntry* swap_entry_free(SwapEntry *se) {
184177
return mfree(se);
185178
}
186179

187-
DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
180+
HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
181+
if (!hl)
182+
return NULL;
183+
184+
swap_entry_free(hl->swap);
185+
free(hl->resume);
186+
187+
return mfree(hl);
188+
}
189+
190+
static int swap_device_to_major_minor(const SwapEntry *swap, char **ret) {
191+
_cleanup_free_ char *major_minor = NULL;
192+
_cleanup_close_ int fd = -1;
193+
struct stat sb;
194+
dev_t swap_dev;
195+
int r;
196+
197+
assert(swap);
198+
assert(swap->device);
199+
assert(swap->type);
200+
201+
fd = open(swap->device, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
202+
if (fd < 0)
203+
return log_debug_errno(errno, "Unable to open '%s': %m", swap->device);
204+
205+
r = fstat(fd, &sb);
206+
if (r < 0)
207+
return log_debug_errno(errno, "Unable to stat %s: %m", swap->device);
208+
209+
swap_dev = streq(swap->type, "partition") ? sb.st_rdev : sb.st_dev;
210+
if (asprintf(&major_minor, "%u:%u", major(swap_dev), minor(swap_dev)) < 0)
211+
return log_oom();
212+
213+
*ret = TAKE_PTR(major_minor);
214+
215+
return 0;
216+
}
217+
218+
static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
219+
_cleanup_close_ int fd = -1;
220+
_cleanup_free_ struct fiemap *fiemap = NULL;
221+
struct stat sb;
222+
int r, btrfs;
223+
224+
assert(swap);
225+
assert(swap->device);
226+
assert(streq(swap->type, "file"));
227+
228+
fd = open(swap->device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
229+
if (!fd)
230+
return log_error_errno(errno, "Failed to open %s: %m", swap->device);
231+
232+
r = fstat(fd, &sb);
233+
if (r < 0)
234+
return log_error_errno(errno, "Failed to stat %s: %m", swap->device);
235+
236+
btrfs = btrfs_is_filesystem(fd);
237+
if (btrfs < 0)
238+
return log_error_errno(r, "Error checking %s for Btrfs filesystem: %m", swap->device);
239+
else if (btrfs > 0) {
240+
log_debug("Detection of swap file offset on Btrfs is not supported: %s; skipping", swap->device);
241+
*ret_offset = 0;
242+
return 0;
243+
}
244+
245+
r = read_fiemap(fd, &fiemap);
246+
if (r < 0)
247+
return log_debug_errno(r, "Unable to read extent map for '%s': %m", swap->device);
248+
249+
*ret_offset = fiemap->fm_extents[0].fe_physical / page_size();
250+
251+
return 0;
252+
}
253+
254+
static int read_resume_files(char **ret_resume, uint64_t *ret_resume_offset) {
255+
_cleanup_free_ char *resume, *resume_offset_str = NULL;
256+
uint64_t resume_offset = 0;
257+
int r;
258+
259+
r = read_one_line_file("/sys/power/resume", &resume);
260+
if (r < 0)
261+
return log_debug_errno(r, "Error reading from /sys/power/resume: %m");
262+
263+
r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
264+
if (r < 0) {
265+
if (r == -ENOENT)
266+
log_debug("Kernel does not support resume_offset; swap file offset detection will be skipped.");
267+
else
268+
return log_debug_errno(r, "Error reading from /sys/power/resume_offset: %m");
269+
} else {
270+
r = safe_atou64(resume_offset_str, &resume_offset);
271+
if (r < 0)
272+
return log_error_errno(r, "Failed to parse 'resume_offset' from string: %s", resume_offset_str);
273+
}
274+
275+
if (resume_offset > 0 && streq(*ret_resume, "0:0")) {
276+
log_debug("Found offset in /sys/power/resume_offset: %" PRIu64 "; no device id found in /sys/power/resume; ignoring resume_offset", resume_offset);
277+
resume_offset = 0;
278+
}
279+
280+
*ret_resume = TAKE_PTR(resume);
281+
*ret_resume_offset = resume_offset;
282+
283+
return 0;
284+
}
285+
286+
static bool location_is_resume_device(const HibernateLocation *location, const char *sys_resume, const uint64_t sys_offset) {
287+
assert(location);
288+
assert(location->resume);
289+
assert(sys_resume);
290+
291+
return streq(sys_resume, location->resume) && sys_offset == location->resume_offset;
292+
}
188293

189-
int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t *used) {
294+
/*
295+
* Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
296+
* /sys/power/resume_offset.
297+
*
298+
* Returns:
299+
* 1 - HibernateLocation matches values found in /sys/power/resume & /sys/power/resume_offset
300+
* 0 - HibernateLocation is highest priority swap with most remaining space; no valid values exist in /sys/power/resume & /sys/power/resume_offset
301+
* negative value in the case of error
302+
*/
303+
int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
190304
_cleanup_fclose_ FILE *f;
191-
_cleanup_(swap_entry_freep) SwapEntry *selected_swap = NULL;
305+
_cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
306+
_cleanup_free_ char *sys_resume = NULL;
307+
uint64_t sys_offset = 0;
192308
unsigned i;
309+
int r;
310+
311+
/* read the /sys/power/resume & /sys/power/resume_offset values */
312+
r = read_resume_files(&sys_resume, &sys_offset);
313+
if (r < 0)
314+
return r;
193315

194316
f = fopen("/proc/swaps", "re");
195317
if (!f) {
@@ -199,9 +321,9 @@ int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t
199321
}
200322

201323
(void) fscanf(f, "%*s %*s %*s %*s %*s\n");
202-
203324
for (i = 1;; i++) {
204325
_cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
326+
uint64_t swap_offset = 0;
205327
int k;
206328

207329
swap = new0(SwapEntry, 1);
@@ -223,12 +345,14 @@ int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t
223345
}
224346

225347
if (streq(swap->type, "file")) {
226-
227348
if (endswith(swap->device, "\\040(deleted)")) {
228349
log_warning("Ignoring deleted swap file '%s'.", swap->device);
229350
continue;
230351
}
231352

353+
r = calculate_swap_file_offset(swap, &swap_offset);
354+
if (r < 0)
355+
return r;
232356
} else if (streq(swap->type, "partition")) {
233357
const char *fn;
234358

@@ -237,45 +361,64 @@ int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t
237361
log_debug("Ignoring compressed RAM swap device '%s'.", swap->device);
238362
continue;
239363
}
364+
} else {
365+
log_debug("Swap type %s is unsupported for hibernation: %s; skipping", swap->type, swap->device);
366+
continue;
240367
}
241368

242-
/* prefer highest priority or swap with most remaining space when same priority */
243-
if (!selected_swap || swap->priority > selected_swap->priority
244-
|| ((swap->priority == selected_swap->priority)
245-
&& (swap->size - swap->used) > (selected_swap->size - selected_swap->used))) {
246-
selected_swap = swap_entry_free(selected_swap);
247-
selected_swap = TAKE_PTR(swap);
369+
/* prefer resume device or highest priority swap with most remaining space */
370+
if (!hibernate_location || swap->priority > hibernate_location->swap->priority
371+
|| ((swap->priority == hibernate_location->swap->priority)
372+
&& (swap->size - swap->used) > (hibernate_location->swap->size - hibernate_location->swap->used))) {
373+
374+
_cleanup_free_ char *swap_device_id = NULL;
375+
r = swap_device_to_major_minor(swap, &swap_device_id);
376+
if (r < 0)
377+
return r;
378+
379+
hibernate_location = hibernate_location_free(hibernate_location);
380+
hibernate_location = new0(HibernateLocation, 1);
381+
if (!hibernate_location)
382+
return log_oom();
383+
384+
hibernate_location->resume = TAKE_PTR(swap_device_id);
385+
hibernate_location->resume_offset = swap_offset;
386+
hibernate_location->swap = TAKE_PTR(swap);
387+
388+
/* if the swap is the resume device, stop looping swaps */
389+
if (location_is_resume_device(hibernate_location, sys_resume, sys_offset))
390+
break;
248391
}
249392
}
250393

251-
if (!selected_swap)
252-
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found.");
394+
if (!hibernate_location)
395+
return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "No swap partitions or files were found");
396+
397+
if (!streq(sys_resume, "0:0") && !location_is_resume_device(hibernate_location, sys_resume, sys_offset))
398+
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,
399+
sys_resume, sys_offset);
400+
401+
log_debug("Hibernation will attempt to use swap entry with path: %s, device: %s, offset: %" PRIu64 ", priority: %i",
402+
hibernate_location->swap->device, hibernate_location->resume, hibernate_location->resume_offset, hibernate_location->swap->priority);
253403

254-
/* use the swap entry with the highest priority */
255-
if (device)
256-
*device = TAKE_PTR(selected_swap->device);
257-
if (type)
258-
*type = TAKE_PTR(selected_swap->type);
259-
if (size)
260-
*size = selected_swap->size;
261-
if (used)
262-
*used = selected_swap->used;
404+
*ret_hibernate_location = TAKE_PTR(hibernate_location);
263405

264-
log_debug("Highest priority swap entry found %s: %i", selected_swap->device, selected_swap->priority);
406+
if (location_is_resume_device(*ret_hibernate_location, sys_resume, sys_offset))
407+
return 1;
265408

266409
return 0;
267410
}
268411

269412
static bool enough_swap_for_hibernation(void) {
270413
_cleanup_free_ char *active = NULL;
414+
_cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
271415
unsigned long long act = 0;
272-
uint64_t size = 0, used = 0;
273416
int r;
274417

275418
if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
276419
return true;
277420

278-
r = find_hibernate_location(NULL, NULL, &size, &used);
421+
r = find_hibernate_location(&hibernate_location);
279422
if (r < 0)
280423
return false;
281424

@@ -291,9 +434,9 @@ static bool enough_swap_for_hibernation(void) {
291434
return false;
292435
}
293436

294-
r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
437+
r = act <= (hibernate_location->swap->size - hibernate_location->swap->used) * HIBERNATION_SWAP_THRESHOLD;
295438
log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
296-
r ? "Enough" : "Not enough", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
439+
r ? "Enough" : "Not enough", act, hibernate_location->swap->size, hibernate_location->swap->used, 100*HIBERNATION_SWAP_THRESHOLD);
297440

298441
return r;
299442
}

src/shared/sleep-config.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,36 @@ typedef struct SleepConfig {
2323
void free_sleep_config(SleepConfig *sc);
2424
DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, free_sleep_config);
2525

26+
/* entry in /proc/swaps */
27+
typedef struct SwapEntry {
28+
char *device;
29+
char *type;
30+
uint64_t size;
31+
uint64_t used;
32+
int priority;
33+
} SwapEntry;
34+
35+
SwapEntry* swap_entry_free(SwapEntry *se);
36+
DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
37+
38+
/*
39+
* represents values for /sys/power/resume & /sys/power/resume_offset
40+
* and the matching /proc/swap entry.
41+
*/
42+
typedef struct HibernateLocation {
43+
char *resume;
44+
uint64_t resume_offset;
45+
SwapEntry *swap;
46+
} HibernateLocation;
47+
48+
HibernateLocation* hibernate_location_free(HibernateLocation *hl);
49+
DEFINE_TRIVIAL_CLEANUP_FUNC(HibernateLocation*, hibernate_location_free);
50+
2651
int sleep_settings(const char *verb, const SleepConfig *sleep_config, bool *ret_allow, char ***ret_modes, char ***ret_states);
2752

2853
int read_fiemap(int fd, struct fiemap **ret);
2954
int parse_sleep_config(SleepConfig **sleep_config);
30-
int find_hibernate_location(char **device, char **type, uint64_t *size, uint64_t *used);
55+
int find_hibernate_location(HibernateLocation **ret_hibernate_location);
3156

3257
int can_sleep(const char *verb);
3358
int can_sleep_disk(char **types);

0 commit comments

Comments
 (0)