]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
hibernate-util: rework find_hibernate_location
authorMike Yuan <me@yhndnzj.com>
Thu, 28 Sep 2023 01:05:41 +0000 (09:05 +0800)
committerMike Yuan <me@yhndnzj.com>
Fri, 20 Oct 2023 15:22:54 +0000 (23:22 +0800)
* "HibernateLocation" struct is renamed to HibernationDevice
  to avoid ambiguity with the EFI variable. Also, it no longer
  takes the reference to a SwapEntry object, since it's really
  unnecessary (only SwapEntry.path is used), but increases complexity.
* SwapEntry is no longer used externally.
* find_hibernate_location is split into read_swap_entries and
  find_suitable_hibernation_device. The former reads all swap entries
  into SwapEntries object for later use.
* Make use of btrfs_get_file_physical_offset_fd

Closes #25130

src/shared/hibernate-util.c
src/shared/hibernate-util.h
src/sleep/sleep.c

index 97568aae6896349eca757a1d462f4ac7237a4515..ba5e5fe857acce4f6f42ce19d26cb57fd8e58f78 100644 (file)
 
 #define HIBERNATION_SWAP_THRESHOLD 0.98
 
-SwapEntry* swap_entry_free(SwapEntry *se) {
-        if (!se)
-                return NULL;
+void hibernation_device_done(HibernationDevice *device) {
+        assert(device);
 
-        free(se->path);
-
-        return mfree(se);
+        free(device->path);
 }
 
-HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
-        if (!hl)
-                return NULL;
+int read_fiemap(int fd, struct fiemap **ret) {
+        _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
+        struct stat statinfo;
+        uint32_t result_extents = 0;
+        uint64_t fiemap_start = 0, fiemap_length;
+        const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
 
-        swap_entry_free(hl->swap);
+        assert(fd >= 0);
+        assert(ret);
 
-        return mfree(hl);
-}
+        if (fstat(fd, &statinfo) < 0)
+                return log_debug_errno(errno, "Cannot determine file size: %m");
+        if (!S_ISREG(statinfo.st_mode))
+                return -ENOTTY;
+        fiemap_length = statinfo.st_size;
 
-static int swap_device_to_devnum(const SwapEntry *swap, dev_t *ret_dev) {
-        _cleanup_close_ int fd = -EBADF;
-        struct stat sb;
-        int r;
+        /* Zero this out in case we run on a file with no extents */
+        fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
+        if (!fiemap)
+                return -ENOMEM;
 
-        assert(swap);
-        assert(swap->path);
+        result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
+        if (!result_fiemap)
+                return -ENOMEM;
 
-        fd = open(swap->path, O_CLOEXEC|O_PATH);
-        if (fd < 0)
-                return -errno;
+        /*  XFS filesystem has incorrect implementation of fiemap ioctl and
+         *  returns extents for only one block-group at a time, so we need
+         *  to handle it manually, starting the next fiemap call from the end
+         *  of the last extent
+         */
+        while (fiemap_start < fiemap_length) {
+                *fiemap = (struct fiemap) {
+                        .fm_start = fiemap_start,
+                        .fm_length = fiemap_length,
+                        .fm_flags = FIEMAP_FLAG_SYNC,
+                };
 
-        if (fstat(fd, &sb) < 0)
-                return -errno;
+                /* Find out how many extents there are */
+                if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
+                        return log_debug_errno(errno, "Failed to read extents: %m");
 
-        if (swap->type == SWAP_BLOCK) {
-                if (!S_ISBLK(sb.st_mode))
-                        return -ENOTBLK;
+                /* Nothing to process */
+                if (fiemap->fm_mapped_extents == 0)
+                        break;
 
-                *ret_dev = sb.st_rdev;
-                return 0;
-        }
+                /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
+                 * the extents for the whole file. Add space for the initial struct fiemap. */
+                if (!greedy_realloc0((void**) &fiemap, n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
+                        return -ENOMEM;
 
-        r = stat_verify_regular(&sb);
-        if (r < 0)
-                return r;
+                fiemap->fm_extent_count = fiemap->fm_mapped_extents;
+                fiemap->fm_mapped_extents = 0;
 
-        return get_block_device_fd(fd, ret_dev);
-}
+                if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
+                        return log_debug_errno(errno, "Failed to read extents: %m");
 
-/*
- * Attempt to calculate the swap file offset on supported filesystems. On unsupported
- * 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 = -EBADF;
-        _cleanup_free_ struct fiemap *fiemap = NULL;
-        int r;
+                /* Resize result_fiemap to allow us to copy in the extents */
+                if (!greedy_realloc((void**) &result_fiemap,
+                                    n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
+                        return -ENOMEM;
 
-        assert(swap);
-        assert(swap->path);
-        assert(swap->type == SWAP_FILE);
-        assert(ret_offset);
+                memcpy(result_fiemap->fm_extents + result_extents,
+                       fiemap->fm_extents,
+                       sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
 
-        fd = open(swap->path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
-        if (fd < 0)
-                return log_debug_errno(errno, "Failed to open swap file %s to determine on-disk offset: %m", swap->path);
+                result_extents += fiemap->fm_mapped_extents;
 
-        r = fd_verify_regular(fd);
-        if (r < 0)
-                return log_debug_errno(r, "Selected swap file is not a regular file.");
+                /* Highly unlikely that it is zero */
+                if (_likely_(fiemap->fm_mapped_extents > 0)) {
+                        uint32_t i = fiemap->fm_mapped_extents - 1;
 
-        r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC);
-        if (r < 0)
-                return log_debug_errno(r, "Error checking %s for Btrfs filesystem: %m", swap->path);
-        if (r > 0) {
-                log_debug("%s: detection of swap file offset on Btrfs is not supported", swap->path);
-                *ret_offset = UINT64_MAX;
-                return 0;
-        }
+                        fiemap_start = fiemap->fm_extents[i].fe_logical +
+                                       fiemap->fm_extents[i].fe_length;
 
-        r = read_fiemap(fd, &fiemap);
-        if (r < 0)
-                return log_debug_errno(r, "Unable to read extent map for '%s': %m", swap->path);
+                        if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
+                                break;
+                }
+        }
 
-        *ret_offset = fiemap->fm_extents[0].fe_physical / page_size();
+        memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
+        result_fiemap->fm_mapped_extents = result_extents;
+        *ret = TAKE_PTR(result_fiemap);
         return 0;
 }
 
-static int read_resume_files(dev_t *ret_resume, uint64_t *ret_resume_offset) {
-        _cleanup_free_ char *resume_str = NULL, *resume_offset_str = NULL;
-        uint64_t resume_offset;
-        dev_t resume;
+static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) {
+        _cleanup_free_ char *devno_str = NULL, *offset_str = NULL;
+        uint64_t offset;
+        dev_t devno;
         int r;
 
-        assert(ret_resume);
-        assert(ret_resume_offset);
+        assert(ret_devno);
+        assert(ret_offset);
 
-        r = read_one_line_file("/sys/power/resume", &resume_str);
+        r = read_one_line_file("/sys/power/resume", &devno_str);
         if (r < 0)
-                return log_debug_errno(r, "Error reading /sys/power/resume: %m");
+                return log_debug_errno(r, "Failed to read /sys/power/resume: %m");
 
-        r = parse_devnum(resume_str, &resume);
+        r = parse_devnum(devno_str, &devno);
         if (r < 0)
-                return log_debug_errno(r, "Error parsing /sys/power/resume device: %s: %m", resume_str);
+                return log_debug_errno(r, "Failed to parse /sys/power/resume devno '%s': %m", devno_str);
 
-        r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
+        r = read_one_line_file("/sys/power/resume_offset", &offset_str);
         if (r == -ENOENT) {
-                log_debug_errno(r, "Kernel does not support resume_offset; swap file offset detection will be skipped.");
-                resume_offset = 0;
+                log_debug_errno(r, "Kernel does not expose resume_offset, skipping.");
+                offset = UINT64_MAX;
         } else if (r < 0)
-                return log_debug_errno(r, "Error reading /sys/power/resume_offset: %m");
+                return log_debug_errno(r, "Failed to read /sys/power/resume_offset: %m");
         else {
-                r = safe_atou64(resume_offset_str, &resume_offset);
+                r = safe_atou64(offset_str, &offset);
                 if (r < 0)
-                        return log_debug_errno(r, "Failed to parse value in /sys/power/resume_offset \"%s\": %m", resume_offset_str);
+                        return log_debug_errno(r,
+                                               "Failed to parse /sys/power/resume_offset '%s': %m", offset_str);
         }
 
-        if (resume_offset > 0 && resume == 0)
-                log_debug("Warning: found /sys/power/resume_offset==%" PRIu64 ", but /sys/power/resume unset. Misconfiguration?",
-                          resume_offset);
+        if (devno == 0 && offset > 0 && offset != UINT64_MAX)
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset);
+
+        *ret_devno = devno;
+        *ret_offset = offset;
 
-        *ret_resume = resume;
-        *ret_resume_offset = resume_offset;
         return 0;
 }
 
-/*
- * 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;
+/* entry in /proc/swaps */
+typedef struct SwapEntry {
+        char *path;
+        bool swapfile;
+
+        uint64_t size;
+        uint64_t used;
+        int priority;
+
+        /* Not present in original entry */
+        dev_t devno;
+        uint64_t offset;
+} SwapEntry;
+
+typedef struct SwapEntries {
+        SwapEntry *swaps;
+        size_t n_swaps;
+} SwapEntries;
+
+static void swap_entry_done(SwapEntry *entry) {
+        assert(entry);
 
-        return  sys_resume > 0 &&
-                sys_resume == location->devno &&
-                (sys_offset == location->offset || (sys_offset > 0 && location->offset == UINT64_MAX));
+        free(entry->path);
 }
 
-/*
- * Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
- * /sys/power/resume_offset.
- *
- * Beware:
- *  Never use a device or file as location that hasn't been somehow specified by a user that would also be
- *  entrusted with full system memory access (for example via /sys/power/resume) or that isn't an already
- *  active swap area!
- *  Otherwise various security attacks might become possible, for example an attacker could silently attach
- *  such a device and circumvent full disk encryption when it would be automatically used for hibernation.
- *  Also, having a swap area on top of encryption is not per se enough to protect from all such attacks.
- *
- * Returns:
- *  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;
-        dev_t sys_resume = 0; /* Unnecessary initialization to appease gcc */
-        uint64_t sys_offset = 0;
-        bool resume_match = false;
+static void swap_entries_done(SwapEntries *entries) {
+        assert(entries);
+
+        FOREACH_ARRAY(i, entries->swaps, entries->n_swaps)
+                swap_entry_done(i);
+
+        free(entries->swaps);
+}
+
+static int swap_entry_get_resume_config(SwapEntry *swap) {
+        _cleanup_close_ int fd = -EBADF;
+        uint64_t offset_raw;
+        struct stat st;
         int r;
 
-        /* read the /sys/power/resume & /sys/power/resume_offset values */
-        r = read_resume_files(&sys_resume, &sys_offset);
+        assert(swap);
+        assert(swap->path);
+
+        fd = open(swap->path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (!swap->swapfile) {
+                if (!S_ISBLK(st.st_mode))
+                        return -ENOTBLK;
+
+                swap->devno = st.st_rdev;
+                swap->offset = 0;
+                return 0;
+        }
+
+        r = stat_verify_regular(&st);
         if (r < 0)
                 return r;
 
-        f = fopen("/proc/swaps", "re");
-        if (!f) {
-                log_debug_errno(errno, "Failed to open /proc/swaps: %m");
-                return errno == ENOENT ? -EOPNOTSUPP : -errno; /* Convert swap not supported to a recognizable error */
+        r = get_block_device_fd(fd, &swap->devno);
+        if (r < 0)
+                return r;
+
+        r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to check if swap file '%s' is on Btrfs: %m", swap->path);
+        if (r > 0) {
+                r = btrfs_get_file_physical_offset_fd(fd, &offset_raw);
+                if (r < 0)
+                        return r;
+        } else {
+                _cleanup_free_ struct fiemap *fiemap = NULL;
+
+                r = read_fiemap(fd, &fiemap);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to read extent map for swap file '%s': %m", swap->path);
+
+                offset_raw = fiemap->fm_extents[0].fe_physical;
         }
 
+        swap->offset = offset_raw / page_size();
+        return 0;
+}
+
+static int read_swap_entries(SwapEntries *ret) {
+        _cleanup_(swap_entries_done) SwapEntries entries = {};
+        _cleanup_fclose_ FILE *f = NULL;
+
+        assert(ret);
+
+        f = fopen("/proc/swaps", "re");
+        if (!f)
+                return log_debug_errno(errno, "Failed to open /proc/swaps: %m");
+
+        /* Remove header */
         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
+
         for (unsigned i = 1;; i++) {
-                _cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
+                _cleanup_(swap_entry_done) SwapEntry swap = {};
                 _cleanup_free_ char *type = NULL;
-                uint64_t swap_offset = 0;
                 int k;
 
-                swap = new(SwapEntry, 1);
-                if (!swap)
-                        return -ENOMEM;
-
-                *swap = (SwapEntry) {
-                        .type = _SWAP_TYPE_INVALID,
-                };
-
                 k = fscanf(f,
                            "%ms "       /* device/file path */
                            "%ms "       /* type of swap */
                            "%" PRIu64   /* swap size */
                            "%" PRIu64   /* used */
-                           "%i\n",      /* priority */
-                           &swap->path, &type, &swap->size, &swap->used, &swap->priority);
+                           "%i"         /* priority */
+                           "\n",
+                           &swap.path, &type, &swap.size, &swap.used, &swap.priority);
                 if (k == EOF)
                         break;
-                if (k != 5) {
-                        log_debug("Failed to parse /proc/swaps:%u, ignoring", i);
-                        continue;
-                }
+                if (k != 5)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse /proc/swaps line %u.", i);
 
                 if (streq(type, "file")) {
-
-                        if (endswith(swap->path, "\\040(deleted)")) {
-                                log_debug("Ignoring deleted swap file '%s'.", swap->path);
+                        if (endswith(swap.path, "\\040(deleted)")) {
+                                log_debug("Swap file '%s' has been deleted, ignoring.", swap.path);
                                 continue;
                         }
 
-                        swap->type = SWAP_FILE;
-
-                        r = calculate_swap_file_offset(swap, &swap_offset);
-                        if (r < 0)
-                                return r;
+                        swap.swapfile = true;
 
                 } else if (streq(type, "partition")) {
-                        const char *fn;
+                        const char *node;
 
-                        fn = path_startswith(swap->path, "/dev/");
-                        if (fn && startswith(fn, "zram")) {
-                                log_debug("%s: ignoring zram swap", swap->path);
+                        node = path_startswith(swap.path, "/dev/");
+                        if (node && startswith(node, "zram")) {
+                                log_debug("Swap partition '%s' is a zram device, ignoring.", swap.path);
                                 continue;
                         }
 
-                        swap->type = SWAP_BLOCK;
+                        swap.swapfile = false;
 
                 } else {
-                        log_debug("%s: swap type %s is unsupported for hibernation, ignoring", swap->path, type);
+                        log_debug("Swap type %s is not supported for hibernation, ignoring device: %s",
+                                  type, swap.path);
                         continue;
                 }
 
-                /* prefer resume device or highest priority swap with most remaining space */
-                if (sys_resume == 0) {
-                        if (hibernate_location && swap->priority < hibernate_location->swap->priority) {
-                                log_debug("%s: ignoring device with lower priority", swap->path);
-                                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->path);
-                                continue;
-                        }
-                }
+                if (!GREEDY_REALLOC(entries.swaps, entries.n_swaps + 1))
+                        return log_oom_debug();
 
-                dev_t swap_devno;
-                r = swap_device_to_devnum(swap, &swap_devno);
-                if (r < 0)
-                        return log_debug_errno(r, "%s: failed to query device number: %m", swap->path);
-                if (swap_devno == 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "%s: not backed by block device.", swap->path);
+                entries.swaps[entries.n_swaps++] = TAKE_STRUCT(swap);
+        }
 
-                hibernate_location = hibernate_location_free(hibernate_location);
-                hibernate_location = new(HibernateLocation, 1);
-                if (!hibernate_location)
-                        return -ENOMEM;
+        *ret = TAKE_STRUCT(entries);
+        return 0;
+}
 
-                *hibernate_location = (HibernateLocation) {
-                        .devno = swap_devno,
-                        .offset = swap_offset,
-                        .swap = TAKE_PTR(swap),
-                };
+/* Attempt to find a suitable device for hibernation by parsing /proc/swaps, /sys/power/resume, and
+ * /sys/power/resume_offset.
+ *
+ * Beware:
+ *  Never use a device or file that hasn't been somehow specified by a user who would also be entrusted
+ *  with full system memory access (for example via /sys/power/resume) or that isn't an already active
+ *  swap area! Otherwise various security attacks might become possible, for example an attacker could
+ *  silently attach such a device and circumvent full disk encryption when it would be automatically used
+ *  for hibernation. Also, having a swap area on top of encryption is not per se enough to protect from all
+ *  such attacks.
+ *
+ * Returns:
+ *  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.
+ *
+ *  Negative value in the case of error */
+int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_t *ret_size, uint64_t *ret_used) {
+        _cleanup_(swap_entries_done) SwapEntries entries = {};
+        SwapEntry *entry = NULL;
+        uint64_t resume_config_offset;
+        dev_t resume_config_devno;
+        int r;
 
-                /* 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->path);
-                        resume_match = true;
-                        break;
-                }
+        assert(!ret_size == !ret_used);
 
-                log_debug("%s: is a candidate device.", hibernate_location->swap->path);
-        }
+        r = read_resume_config(&resume_config_devno, &resume_config_offset);
+        if (r < 0)
+                return r;
 
-        /* We found nothing at all */
-        if (!hibernate_location)
-                return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
-                                       "No possible swap partitions or files suitable for hibernation were found in /proc/swaps.");
+        r = read_swap_entries(&entries);
+        if (r < 0)
+                return r;
+        if (entries.n_swaps == 0)
+                return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "No swap space available for hibernation.");
+
+        FOREACH_ARRAY(swap, entries.swaps, entries.n_swaps) {
+                r = swap_entry_get_resume_config(swap);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to get devno and offset for swap '%s': %m", swap->path);
+                if (swap->devno == 0) {
+                        assert(swap->swapfile);
 
-        /* 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("Swap file '%s' is not backed by block device, ignoring: %m", swap->path);
+                        continue;
+                }
+
+                if (resume_config_devno > 0) {
+                        if (swap->devno == resume_config_devno &&
+                            (!swap->swapfile || resume_config_offset == UINT64_MAX || swap->offset == resume_config_offset)) {
+                                /* /sys/power/resume (resume=) is set, and the calculated swap file offset
+                                 * matches with /sys/power/resume_offset. If /sys/power/resume_offset is not
+                                 * exposed, we can't do proper check anyway, so use the found swap file too. */
+                                entry = swap;
+                                break;
+                        }
 
-        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.");
+                        /* If resume= is set, don't try to use other swap spaces. */
+                        continue;
+                }
 
-                hibernate_location->offset = sys_offset;
+                if (!entry ||
+                    swap->priority > entry->priority ||
+                    swap->size - swap->used > entry->size - entry->used)
+                        entry = swap;
         }
 
-        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->path, 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->path, major(hibernate_location->devno), minor(hibernate_location->devno),
-                          hibernate_location->offset, hibernate_location->swap->priority);
+        if (!entry) {
+                /* No need to check n_swaps == 0, since it's rejected early */
+                assert(resume_config_devno > 0);
+                return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Cannot find swap entry corresponding to /sys/power/resume.");
+        }
 
-        *ret_hibernate_location = TAKE_PTR(hibernate_location);
+        if (ret_device)
+                *ret_device = (HibernationDevice) {
+                        .devno = entry->devno,
+                        .offset = entry->offset,
+                        .path = TAKE_PTR(entry->path),
+                };
 
-        if (resume_match)
-                return 1;
+        if (ret_size) {
+                *ret_size = entry->size;
+                *ret_used = entry->used;
+        }
 
-        return 0;
+        return resume_config_devno > 0;
 }
 
 bool enough_swap_for_hibernation(void) {
-        _cleanup_free_ char *active = NULL;
-        _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
-        unsigned long long act = 0;
+        _cleanup_free_ char *active_str = NULL;
+        unsigned long long active;
+        uint64_t size, used;
         int r;
 
         if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
                 return true;
 
-        r = find_hibernate_location(&hibernate_location);
+        r = find_suitable_hibernation_device_full(NULL, &size, &used);
         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);
+        r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active_str);
         if (r < 0) {
                 log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
                 return false;
         }
 
-        r = safe_atollu(active, &act);
+        r = safe_atollu(active_str, &active);
         if (r < 0) {
-                log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active);
+                log_debug_errno(r, "Failed to parse Active(anon) '%s' from /proc/meminfo: %m", active_str);
                 return false;
         }
 
-        r = act <= (hibernate_location->swap->size - hibernate_location->swap->used) * HIBERNATION_SWAP_THRESHOLD;
-        log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
-                  r ? "Enough" : "Not enough", act, hibernate_location->swap->size, hibernate_location->swap->used, 100*HIBERNATION_SWAP_THRESHOLD);
+        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%%",
+                  r ? "enough" : "not enough", active, size, used, 100 * HIBERNATION_SWAP_THRESHOLD);
 
         return r;
 }
 
-int read_fiemap(int fd, struct fiemap **ret) {
-        _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
-        struct stat statinfo;
-        uint32_t result_extents = 0;
-        uint64_t fiemap_start = 0, fiemap_length;
-        const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
-
-        assert(fd >= 0);
-        assert(ret);
-
-        if (fstat(fd, &statinfo) < 0)
-                return log_debug_errno(errno, "Cannot determine file size: %m");
-        if (!S_ISREG(statinfo.st_mode))
-                return -ENOTTY;
-        fiemap_length = statinfo.st_size;
-
-        /* Zero this out in case we run on a file with no extents */
-        fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
-        if (!fiemap)
-                return -ENOMEM;
-
-        result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
-        if (!result_fiemap)
-                return -ENOMEM;
-
-        /*  XFS filesystem has incorrect implementation of fiemap ioctl and
-         *  returns extents for only one block-group at a time, so we need
-         *  to handle it manually, starting the next fiemap call from the end
-         *  of the last extent
-         */
-        while (fiemap_start < fiemap_length) {
-                *fiemap = (struct fiemap) {
-                        .fm_start = fiemap_start,
-                        .fm_length = fiemap_length,
-                        .fm_flags = FIEMAP_FLAG_SYNC,
-                };
-
-                /* Find out how many extents there are */
-                if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
-                        return log_debug_errno(errno, "Failed to read extents: %m");
-
-                /* Nothing to process */
-                if (fiemap->fm_mapped_extents == 0)
-                        break;
-
-                /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
-                 * the extents for the whole file. Add space for the initial struct fiemap. */
-                if (!greedy_realloc0((void**) &fiemap, n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
-                        return -ENOMEM;
-
-                fiemap->fm_extent_count = fiemap->fm_mapped_extents;
-                fiemap->fm_mapped_extents = 0;
-
-                if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
-                        return log_debug_errno(errno, "Failed to read extents: %m");
-
-                /* Resize result_fiemap to allow us to copy in the extents */
-                if (!greedy_realloc((void**) &result_fiemap,
-                                    n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
-                        return -ENOMEM;
-
-                memcpy(result_fiemap->fm_extents + result_extents,
-                       fiemap->fm_extents,
-                       sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
-
-                result_extents += fiemap->fm_mapped_extents;
-
-                /* Highly unlikely that it is zero */
-                if (_likely_(fiemap->fm_mapped_extents > 0)) {
-                        uint32_t i = fiemap->fm_mapped_extents - 1;
-
-                        fiemap_start = fiemap->fm_extents[i].fe_logical +
-                                       fiemap->fm_extents[i].fe_length;
-
-                        if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
-                                break;
-                }
-        }
-
-        memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
-        result_fiemap->fm_mapped_extents = result_extents;
-        *ret = TAKE_PTR(result_fiemap);
-        return 0;
-}
-
 int write_resume_config(dev_t devno, uint64_t offset, const char *device) {
         char offset_str[DECIMAL_STR_MAX(uint64_t)];
         _cleanup_free_ char *path = NULL;
index 209383c38b3c4434d23dfe38922b8466d8910c9e..8480026ce1801d068095146aee7603cfbc98967c 100644 (file)
@@ -4,39 +4,23 @@
 #include <linux/fiemap.h>
 #include <sys/types.h>
 
-typedef enum SwapType {
-        SWAP_BLOCK,
-        SWAP_FILE,
-        _SWAP_TYPE_MAX,
-        _SWAP_TYPE_INVALID = -EINVAL,
-} SwapType;
-
-/* entry in /proc/swaps */
-typedef struct SwapEntry {
+/* represents values for /sys/power/resume & /sys/power/resume_offset and the corresponding path */
+typedef struct HibernationDevice {
+        dev_t devno;
+        uint64_t offset; /* in memory pages */
         char *path;
-        SwapType type;
-        uint64_t size;
-        uint64_t used;
-        int priority;
-} SwapEntry;
+} HibernationDevice;
 
-SwapEntry* swap_entry_free(SwapEntry *se);
-DEFINE_TRIVIAL_CLEANUP_FUNC(SwapEntry*, swap_entry_free);
+void hibernation_device_done(HibernationDevice *hibernation_device);
 
-/*
- * represents values for /sys/power/resume & /sys/power/resume_offset
- * and the matching /proc/swap entry.
- */
-typedef struct HibernateLocation {
-        dev_t devno;
-        uint64_t offset; /* in memory pages */
-        SwapEntry *swap;
-} HibernateLocation;
+int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_t *ret_size, uint64_t *ret_used);
+static inline int find_suitable_hibernation_device(HibernationDevice *ret) {
+        return find_suitable_hibernation_device_full(ASSERT_PTR(ret), NULL, NULL);
+}
 
-HibernateLocation* hibernate_location_free(HibernateLocation *hl);
-DEFINE_TRIVIAL_CLEANUP_FUNC(HibernateLocation*, hibernate_location_free);
+bool enough_swap_for_hibernation(void);
 
-int read_fiemap(int fd, struct fiemap **ret);
-int find_hibernate_location(HibernateLocation **ret_hibernate_location);
 int write_resume_config(dev_t devno, uint64_t offset, const char *device);
-bool enough_swap_for_hibernation(void);
+
+/* Only for test-fiemap */
+int read_fiemap(int fd, struct fiemap **ret);
index a299568ccf77606488021e6ec6f20536584c15dd..8bfc9ee6cc7b7683426d2644e00c1487e62d73cd 100644 (file)
@@ -54,7 +54,7 @@
 
 static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID;
 
-static int write_efi_hibernate_location(const HibernateLocation *hibernate_location, bool required) {
+static int write_efi_hibernate_location(const HibernationDevice *hibernation_device, bool required) {
         int log_level = required ? LOG_ERR : LOG_DEBUG;
 
 #if ENABLE_EFI
@@ -67,27 +67,26 @@ static int write_efi_hibernate_location(const HibernateLocation *hibernate_locat
         struct utsname uts = {};
         int r, log_level_ignore = required ? LOG_WARNING : LOG_DEBUG;
 
-        assert(hibernate_location);
-        assert(hibernate_location->swap);
+        assert(hibernation_device);
 
         if (!is_efi_boot())
                 return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
                                       "Not an EFI boot, passing HibernateLocation via EFI variable is not possible.");
 
-        r = sd_device_new_from_devnum(&device, 'b', hibernate_location->devno);
+        r = sd_device_new_from_devnum(&device, 'b', hibernation_device->devno);
         if (r < 0)
                 return log_full_errno(log_level, r, "Failed to create sd-device object for '%s': %m",
-                                      hibernate_location->swap->path);
+                                      hibernation_device->path);
 
         r = sd_device_get_property_value(device, "ID_FS_UUID", &uuid_str);
         if (r < 0)
                 return log_full_errno(log_level, r, "Failed to get filesystem UUID for device '%s': %m",
-                                      hibernate_location->swap->path);
+                                      hibernation_device->path);
 
         r = sd_id128_from_string(uuid_str, &uuid);
         if (r < 0)
                 return log_full_errno(log_level, r, "Failed to parse ID_FS_UUID '%s' for device '%s': %m",
-                                      uuid_str, hibernate_location->swap->path);
+                                      uuid_str, hibernation_device->path);
 
         if (uname(&uts) < 0)
                 log_full_errno(log_level_ignore, errno, "Failed to get kernel info, ignoring: %m");
@@ -102,7 +101,7 @@ static int write_efi_hibernate_location(const HibernateLocation *hibernate_locat
 
         r = json_build(&v, JSON_BUILD_OBJECT(
                                JSON_BUILD_PAIR_UUID("uuid", uuid),
-                               JSON_BUILD_PAIR_UNSIGNED("offset", hibernate_location->offset),
+                               JSON_BUILD_PAIR_UNSIGNED("offset", hibernation_device->offset),
                                JSON_BUILD_PAIR_CONDITION(!isempty(uts.release), "kernelVersion", JSON_BUILD_STRING(uts.release)),
                                JSON_BUILD_PAIR_CONDITION(id, "osReleaseId", JSON_BUILD_STRING(id)),
                                JSON_BUILD_PAIR_CONDITION(image_id, "osReleaseImageId", JSON_BUILD_STRING(image_id)),
@@ -127,14 +126,6 @@ static int write_efi_hibernate_location(const HibernateLocation *hibernate_locat
 #endif
 }
 
-static int write_kernel_hibernate_location(const HibernateLocation *hibernate_location) {
-        assert(hibernate_location);
-        assert(hibernate_location->swap);
-        assert(IN_SET(hibernate_location->swap->type, SWAP_BLOCK, SWAP_FILE));
-
-        return write_resume_config(hibernate_location->devno, hibernate_location->offset, hibernate_location->swap->path);
-}
-
 static int write_mode(char **modes) {
         int r = 0;
 
@@ -266,7 +257,7 @@ static int execute(
                 NULL
         };
 
-        _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
+        _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {};
         _cleanup_fclose_ FILE *f = NULL;
         char **modes, **states;
         int r;
@@ -296,19 +287,19 @@ static int execute(
         if (!strv_isempty(modes)) {
                 bool resume_set;
 
-                r = find_hibernate_location(&hibernate_location);
+                r = find_suitable_hibernation_device(&hibernation_device);
                 if (r < 0)
                         return log_error_errno(r, "Failed to find location to hibernate to: %m");
                 resume_set = r > 0;
 
-                r = write_efi_hibernate_location(hibernate_location, !resume_set);
+                r = write_efi_hibernate_location(&hibernation_device, !resume_set);
                 if (!resume_set) {
                         if (r == -EOPNOTSUPP)
                                 return log_error_errno(r, "No valid 'resume=' option found, refusing to hibernate.");
                         if (r < 0)
                                 return r;
 
-                        r = write_kernel_hibernate_location(hibernate_location);
+                        r = write_resume_config(hibernation_device.devno, hibernation_device.offset, hibernation_device.path);
                         if (r < 0) {
                                 if (is_efi_boot())
                                         (void) efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0);