]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hibernate: fix swap selection and prefer swap that can hold image
authordongshengyuan <545258830@qq.com>
Wed, 17 Jun 2026 05:06:21 +0000 (13:06 +0800)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 19 Jun 2026 18:40:12 +0000 (19:40 +0100)
When multiple swap devices exist, prefer one with enough free space
to hold the hibernation image over one that cannot, regardless of
priority. If no swap can fit, fall back to priority-first selection.

This avoids deterministically failing hibernation when the
highest-priority swap is too small but a lower-priority one fits.

Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
Co-developed-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
src/shared/hibernate-util.c

index f9e095f27384d2bd495caa98a8415e04d6e3f232..e40ca8a66298b659f9b9faade923fa7c191b2ad7 100644 (file)
@@ -317,6 +317,35 @@ static int read_swap_entries(SwapEntries *ret) {
         return 0;
 }
 
+static int get_proc_meminfo_active(uint64_t *ret) {
+        _cleanup_free_ char *active_str = NULL;
+        uint64_t active;
+        int r;
+
+        assert(ret);
+
+        r = get_proc_field("/proc/meminfo", "Active(anon)", &active_str);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
+
+        r = safe_atou64(active_str, &active);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse Active(anon) '%s' from /proc/meminfo: %m", active_str);
+
+        *ret = active;
+        return 0;
+}
+
+static uint64_t swap_free(const SwapEntry *s) {
+        assert(s);
+        return LESS_BY(s->size, s->used);
+}
+
+static bool swap_can_hold_image(const SwapEntry *s, uint64_t active) {
+        assert(s);
+        return active > 0 && active <= swap_free(s) * HIBERNATION_SWAP_THRESHOLD;
+}
+
 /* Attempt to find a suitable device for hibernation by parsing /proc/swaps, /sys/power/resume, and
  * /sys/power/resume_offset.
  *
@@ -332,7 +361,8 @@ static int read_swap_entries(SwapEntries *ret) {
  *  1 - Values are set in /sys/power/resume and /sys/power/resume_offset.
  *
  *  0 - No values are set in /sys/power/resume and /sys/power/resume_offset.
- *      ret will represent the highest priority swap with most remaining space discovered in /proc/swaps.
+ *      ret will represent the highest priority swap that can hold the hibernation image. If no swap is
+ *      large enough, ret will represent the highest priority swap with most remaining space.
  *
  *  Negative value in the case of error */
 int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_t *ret_size, uint64_t *ret_used) {
@@ -340,10 +370,15 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_
         SwapEntry *entry = NULL;
         uint64_t resume_config_offset;
         dev_t resume_config_devno;
+        uint64_t active = 0;
         int r;
 
         assert(!ret_size == !ret_used);
 
+        /* Best-effort: used to prefer swaps that can hold the hibernation image.
+         * On failure, selection falls back to priority + free space only. */
+        (void) get_proc_meminfo_active(&active);
+
         r = read_resume_config(&resume_config_devno, &resume_config_offset);
         if (r < 0)
                 return r;
@@ -379,8 +414,14 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_
                 }
 
                 if (!entry ||
-                    swap->priority > entry->priority ||
-                    swap->size - swap->used > entry->size - entry->used)
+                    /* prefer a swap that can hold the image over one that cannot */
+                    (swap_can_hold_image(swap, active) && !swap_can_hold_image(entry, active)) ||
+                    /* among equal capacity fitness: higher priority wins */
+                    (swap_can_hold_image(swap, active) == swap_can_hold_image(entry, active) &&
+                     (swap->priority > entry->priority ||
+                      /* equal priority: more free space wins */
+                      (swap->priority == entry->priority &&
+                       swap_free(swap) > swap_free(entry)))))
                         entry = swap;
         }
 
@@ -418,28 +459,8 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_
         return resume_config_devno > 0;
 }
 
-static int get_proc_meminfo_active(unsigned long long *ret) {
-        _cleanup_free_ char *active_str = NULL;
-        unsigned long long active;
-        int r;
-
-        assert(ret);
-
-        r = get_proc_field("/proc/meminfo", "Active(anon)", &active_str);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
-
-        r = safe_atollu(active_str, &active);
-        if (r < 0)
-                return log_debug_errno(r, "Failed to parse Active(anon) '%s' from /proc/meminfo: %m", active_str);
-
-        *ret = active;
-        return 0;
-}
-
 int hibernation_is_safe(void) {
-        unsigned long long active;
-        uint64_t size, used;
+        uint64_t active, size, used;
         bool resume_set, bypass_space_check;
         int r;
 
@@ -468,7 +489,7 @@ int hibernation_is_safe(void) {
                 return r;
 
         r = active <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
-        log_debug("Detected %s swap for hibernation: Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
+        log_debug("Detected %s swap for hibernation: Active(anon)=%" PRIu64 " kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
                   r ? "enough" : "not enough", active, size, used, 100 * HIBERNATION_SWAP_THRESHOLD);
         if (!r)
                 return -ENOSPC;