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"
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
269412static 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}
0 commit comments