diff --git a/src/login/logind-action.c b/src/login/logind-action.c index a2b211933a..d4d53bddd7 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -201,20 +201,20 @@ int manager_handle_action( } if (handle == HANDLE_SUSPEND) - supported = can_sleep(SLEEP_SUSPEND) > 0; + supported = sleep_supported(SLEEP_SUSPEND) > 0; else if (handle == HANDLE_HIBERNATE) - supported = can_sleep(SLEEP_HIBERNATE) > 0; + supported = sleep_supported(SLEEP_HIBERNATE) > 0; else if (handle == HANDLE_HYBRID_SLEEP) - supported = can_sleep(SLEEP_HYBRID_SLEEP) > 0; + supported = sleep_supported(SLEEP_HYBRID_SLEEP) > 0; else if (handle == HANDLE_SUSPEND_THEN_HIBERNATE) - supported = can_sleep(SLEEP_SUSPEND_THEN_HIBERNATE) > 0; + supported = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE) > 0; else if (handle == HANDLE_KEXEC) supported = access(KEXEC, X_OK) >= 0; else supported = true; if (!supported && HANDLE_ACTION_IS_SLEEP(handle) && handle != HANDLE_SUSPEND) { - supported = can_sleep(SLEEP_SUSPEND) > 0; + supported = sleep_supported(SLEEP_SUSPEND) > 0; if (supported) { log_notice("Requested %s operation is not supported, using regular suspend instead.", handle_action_to_string(handle)); diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index d631a30716..f45a944f17 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -1924,16 +1924,38 @@ static int method_do_shutdown_or_sleep( "There's already a shutdown or sleep operation in progress"); if (a->sleep_operation >= 0) { - r = can_sleep(a->sleep_operation); - if (r == -ENOSPC) - return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Not enough suitable swap space for hibernation available on compatible block devices and file systems"); - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, - "Sleep verb \"%s\" not supported", - sleep_operation_to_string(a->sleep_operation)); + SleepSupport support; + + r = sleep_supported_full(a->sleep_operation, &support); if (r < 0) return r; + if (r == 0) + switch (support) { + + case SLEEP_DISABLED: + return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Sleep verb '%s' is disabled by config", + sleep_operation_to_string(a->sleep_operation)); + + case SLEEP_NOT_CONFIGURED: + case SLEEP_STATE_OR_MODE_NOT_SUPPORTED: + case SLEEP_ALARM_NOT_SUPPORTED: + return sd_bus_error_setf(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Sleep verb '%s' is not configured or configuration is not supported by kernel", + sleep_operation_to_string(a->sleep_operation)); + + case SLEEP_RESUME_NOT_SUPPORTED: + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Not running on EFI and resume= is not set. No available method to resume from hibernation"); + + case SLEEP_NOT_ENOUGH_SWAP_SPACE: + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Not enough suitable swap space for hibernation available on compatible block devices and file systems"); + + default: + assert_not_reached(); + + } } r = verify_shutdown_creds(m, message, a, flags, error); @@ -2395,11 +2417,11 @@ static int method_can_shutdown_or_sleep( assert(a); if (a->sleep_operation >= 0) { - r = can_sleep(a->sleep_operation); - if (IN_SET(r, 0, -ENOSPC)) - return sd_bus_reply_method_return(message, "s", "na"); + r = sleep_supported(a->sleep_operation); if (r < 0) return r; + if (r == 0) + return sd_bus_reply_method_return(message, "s", "na"); } r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index c6c58f8e5d..4a05320a40 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -14,6 +14,7 @@ #include "btrfs-util.h" #include "device-util.h" #include "devnum-util.h" +#include "efivars.h" #include "env-util.h" #include "errno-util.h" #include "fd-util.h" @@ -28,441 +29,426 @@ #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; - return sys_resume > 0 && - sys_resume == location->devno && - (sys_offset == location->offset || (sys_offset > 0 && location->offset == UINT64_MAX)); +typedef struct SwapEntries { + SwapEntry *swaps; + size_t n_swaps; +} SwapEntries; + +static void swap_entry_done(SwapEntry *entry) { + assert(entry); + + 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; - } - } - - 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); - - hibernate_location = hibernate_location_free(hibernate_location); - hibernate_location = new(HibernateLocation, 1); - if (!hibernate_location) - return -ENOMEM; - - *hibernate_location = (HibernateLocation) { - .devno = swap_devno, - .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->path); - resume_match = true; - break; - } - - log_debug("%s: is a candidate device.", hibernate_location->swap->path); - } - - /* 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."); + if (!GREEDY_REALLOC(entries.swaps, entries.n_swaps + 1)) + return log_oom_debug(); - /* 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."); - - 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; + entries.swaps[entries.n_swaps++] = TAKE_STRUCT(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); - - *ret_hibernate_location = TAKE_PTR(hibernate_location); - - if (resume_match) - return 1; - + *ret = TAKE_STRUCT(entries); return 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; +/* 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 (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0) - return true; + assert(!ret_size == !ret_used); - r = find_hibernate_location(&hibernate_location); + r = read_resume_config(&resume_config_devno, &resume_config_offset); 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"); - return false; - } + return r; - r = safe_atollu(active, &act); - if (r < 0) { - log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active); - return false; - } + 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."); - 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); + 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); - return r; -} + log_debug("Swap file '%s' is not backed by block device, ignoring: %m", swap->path); + continue; + } -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)); + 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 (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; + /* If resume= is set, don't try to use other swap spaces. */ + continue; + } - /* 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; + if (!entry || + swap->priority > entry->priority || + swap->size - swap->used > entry->size - entry->used) + entry = swap; + } - result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent)); - if (!result_fiemap) - return -ENOMEM; + 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."); + } - /* 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 (ret_device) + *ret_device = (HibernationDevice) { + .devno = entry->devno, + .offset = entry->offset, + .path = TAKE_PTR(entry->path), }; - /* 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 (ret_size) { + *ret_size = entry->size; + *ret_used = entry->used; + } - /* Nothing to process */ - if (fiemap->fm_mapped_extents == 0) - break; + return resume_config_devno > 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; +static int get_proc_meminfo_active(unsigned long long *ret) { + _cleanup_free_ char *active_str = NULL; + unsigned long long active; + int r; - fiemap->fm_extent_count = fiemap->fm_mapped_extents; - fiemap->fm_mapped_extents = 0; + r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active_str); + if (r < 0) + return log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); - if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) - return log_debug_errno(errno, "Failed to read extents: %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); - /* 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; + *ret = active; + return 0; +} - memcpy(result_fiemap->fm_extents + result_extents, - fiemap->fm_extents, - sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents); +int hibernation_is_safe(void) { + unsigned long long active; + uint64_t size, used; + bool resume_set; + int r; - result_extents += fiemap->fm_mapped_extents; + r = find_suitable_hibernation_device_full(NULL, &size, &used); + if (r < 0) + return r; + resume_set = r > 0; - /* Highly unlikely that it is zero */ - if (_likely_(fiemap->fm_mapped_extents > 0)) { - uint32_t i = fiemap->fm_mapped_extents - 1; + if (!resume_set && !is_efi_boot()) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Not running on EFI and resume= is not set. Hibernation is not safe."); - fiemap_start = fiemap->fm_extents[i].fe_logical + - fiemap->fm_extents[i].fe_length; + if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0) + return true; - if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST) - break; - } - } + r = get_proc_meminfo_active(&active); + if (r < 0) + return r; - memcpy(result_fiemap, fiemap, sizeof(struct fiemap)); - result_fiemap->fm_mapped_extents = result_extents; - *ret = TAKE_PTR(result_fiemap); - return 0; + 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); + if (!r) + return -ENOSPC; + + return resume_set; } int write_resume_config(dev_t devno, uint64_t offset, const char *device) { diff --git a/src/shared/hibernate-util.h b/src/shared/hibernate-util.h index 209383c38b..2ae10fb100 100644 --- a/src/shared/hibernate-util.h +++ b/src/shared/hibernate-util.h @@ -4,39 +4,23 @@ #include #include -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); +int hibernation_is_safe(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); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 764c29d2a9..deba2b1c58 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -22,6 +22,8 @@ #include "strv.h" #include "time-util.h" +#define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR) + static const char* const sleep_operation_table[_SLEEP_OPERATION_MAX] = { [SLEEP_SUSPEND] = "suspend", [SLEEP_HIBERNATE] = "hibernate", @@ -110,59 +112,57 @@ int parse_sleep_config(SleepConfig **ret) { return 0; } -int can_sleep_state(char **requested_types) { - _cleanup_free_ char *text = NULL; +int sleep_state_supported(char **states) { + _cleanup_free_ char *supported_sysfs = NULL; + const char *found; int r; - if (strv_isempty(requested_types)) - return true; + if (strv_isempty(states)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOMSG), "No sleep state configured."); - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0) { - log_debug_errno(errno, "/sys/power/state is not writable, cannot sleep: %m"); - return false; - } + if (access("/sys/power/state", W_OK) < 0) + return log_debug_errno(errno, "/sys/power/state is not writable: %m"); - r = read_one_line_file("/sys/power/state", &text); - if (r < 0) { - log_debug_errno(r, "Failed to read /sys/power/state, cannot sleep: %m"); - return false; - } + r = read_one_line_file("/sys/power/state", &supported_sysfs); + if (r < 0) + return log_debug_errno(r, "Failed to read /sys/power/state: %m"); - const char *found; - r = string_contains_word_strv(text, NULL, requested_types, &found); + r = string_contains_word_strv(supported_sysfs, NULL, states, &found); if (r < 0) return log_debug_errno(r, "Failed to parse /sys/power/state: %m"); - if (r > 0) - log_debug("Sleep mode \"%s\" is supported by the kernel.", found); - else if (DEBUG_LOGGING) { - _cleanup_free_ char *t = strv_join(requested_types, "/"); - log_debug("Sleep mode %s not supported by the kernel, sorry.", strnull(t)); + if (r > 0) { + log_debug("Sleep state '%s' is supported by kernel.", found); + return true; } - return r; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = strv_join(states, " "); + log_debug("None of the configured sleep states are supported by kernel: %s", strnull(joined)); + } + return false; } -int can_sleep_disk(char **types) { - _cleanup_free_ char *text = NULL; +int sleep_mode_supported(char **modes) { + _cleanup_free_ char *supported_sysfs = NULL; int r; - if (strv_isempty(types)) + /* Unlike state, kernel has its own default choice if not configured */ + if (strv_isempty(modes)) { + log_debug("No sleep mode configured, using kernel default."); return true; - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/disk", W_OK) < 0) { - log_debug_errno(errno, "/sys/power/disk is not writable: %m"); - return false; } - r = read_one_line_file("/sys/power/disk", &text); - if (r < 0) { - log_debug_errno(r, "Couldn't read /sys/power/disk: %m"); - return false; - } + if (access("/sys/power/disk", W_OK) < 0) + return log_debug_errno(errno, "/sys/power/disk is not writable: %m"); + + r = read_one_line_file("/sys/power/disk", &supported_sysfs); + if (r < 0) + return log_debug_errno(r, "Failed to read /sys/power/disk: %m"); - for (const char *p = text;;) { + for (const char *p = supported_sysfs;;) { _cleanup_free_ char *word = NULL; + char *mode; + size_t l; r = extract_first_word(&p, &word, NULL, 0); if (r < 0) @@ -170,91 +170,152 @@ int can_sleep_disk(char **types) { if (r == 0) break; - char *s = word; - size_t l = strlen(s); - if (s[0] == '[' && s[l-1] == ']') { - s[l-1] = '\0'; - s++; + mode = word; + l = strlen(word); + + if (mode[0] == '[' && mode[l - 1] == ']') { + mode[l - 1] = '\0'; + mode++; } - if (strv_contains(types, s)) { - log_debug("Disk sleep mode \"%s\" is supported by the kernel.", s); + if (strv_contains(modes, mode)) { + log_debug("Disk sleep mode '%s' is supported by kernel.", mode); return true; } } if (DEBUG_LOGGING) { - _cleanup_free_ char *t = strv_join(types, "/"); - log_debug("Disk sleep mode %s not supported by the kernel, sorry.", strnull(t)); + _cleanup_free_ char *joined = strv_join(modes, " "); + log_debug("None of the configured hibernation power modes are supported by kernel: %s", strnull(joined)); } return false; } -static int can_sleep_internal(const SleepConfig *sleep_config, SleepOperation operation, bool check_allowed); +static int sleep_supported_internal( + const SleepConfig *sleep_config, + SleepOperation operation, + bool check_allowed, + SleepSupport *ret_support); -static bool can_s2h(const SleepConfig *sleep_config) { +static int s2h_supported(const SleepConfig *sleep_config, SleepSupport *ret_support) { static const SleepOperation operations[] = { SLEEP_SUSPEND, SLEEP_HIBERNATE, }; + SleepSupport support; int r; + assert(sleep_config); + assert(ret_support); + if (!clock_supported(CLOCK_BOOTTIME_ALARM)) { - log_debug("CLOCK_BOOTTIME_ALARM is not supported."); + log_debug("CLOCK_BOOTTIME_ALARM is not supported, can't perform %s.", sleep_operation_to_string(SLEEP_SUSPEND_THEN_HIBERNATE)); + *ret_support = SLEEP_ALARM_NOT_SUPPORTED; return false; } - for (size_t i = 0; i < ELEMENTSOF(operations); i++) { - r = can_sleep_internal(sleep_config, operations[i], false); - if (IN_SET(r, 0, -ENOSPC)) { - log_debug("Unable to %s system.", sleep_operation_to_string(operations[i])); + FOREACH_ARRAY(i, operations, ELEMENTSOF(operations)) { + r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support); + if (r < 0) + return r; + if (r == 0) { + log_debug("Sleep operation %s is not supported, can't perform %s.", + sleep_operation_to_string(*i), sleep_operation_to_string(SLEEP_SUSPEND_THEN_HIBERNATE)); + *ret_support = support; return false; } - if (r < 0) - return log_debug_errno(r, "Failed to check if %s is possible: %m", sleep_operation_to_string(operations[i])); } + assert(support == SLEEP_SUPPORTED); + *ret_support = support; + return true; } -static int can_sleep_internal( +static int sleep_supported_internal( const SleepConfig *sleep_config, SleepOperation operation, - bool check_allowed) { + bool check_allowed, + SleepSupport *ret_support) { + int r; + + assert(sleep_config); assert(operation >= 0); assert(operation < _SLEEP_OPERATION_MAX); + assert(ret_support); if (check_allowed && !sleep_config->allow[operation]) { - log_debug("Sleep mode \"%s\" is disabled by configuration.", sleep_operation_to_string(operation)); + log_debug("Sleep operation %s is disabled by configuration.", sleep_operation_to_string(operation)); + *ret_support = SLEEP_DISABLED; return false; } if (operation == SLEEP_SUSPEND_THEN_HIBERNATE) - return can_s2h(sleep_config); + return s2h_supported(sleep_config, ret_support); - if (can_sleep_state(sleep_config->states[operation]) <= 0 || - can_sleep_disk(sleep_config->modes[operation]) <= 0) + assert(operation < _SLEEP_OPERATION_CONFIG_MAX); + + r = sleep_state_supported(sleep_config->states[operation]); + if (r == -ENOMSG) { + *ret_support = SLEEP_NOT_CONFIGURED; + return false; + } + if (r < 0) + return r; + if (r == 0) { + *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED; return false; + } - if (operation == SLEEP_SUSPEND) - return true; + r = sleep_mode_supported(sleep_config->modes[operation]); + if (r < 0) + return r; + if (r == 0) { + *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED; + return false; + } - if (!enough_swap_for_hibernation()) - return -ENOSPC; + if (IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP)) { + r = hibernation_is_safe(); + if (r == -ENOTRECOVERABLE) { + *ret_support = SLEEP_RESUME_NOT_SUPPORTED; + return false; + } + if (r == -ENOSPC) { + *ret_support = SLEEP_NOT_ENOUGH_SWAP_SPACE; + return false; + } + if (r < 0) + return r; + } + *ret_support = SLEEP_SUPPORTED; return true; } -int can_sleep(SleepOperation operation) { +int sleep_supported_full(SleepOperation operation, SleepSupport *ret_support) { _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; + SleepSupport support; int r; + assert(operation >= 0); + assert(operation < _SLEEP_OPERATION_MAX); + r = parse_sleep_config(&sleep_config); if (r < 0) return r; - return can_sleep_internal(sleep_config, operation, true); + r = sleep_supported_internal(sleep_config, operation, /* check_allowed = */ true, &support); + if (r < 0) + return r; + + assert((r > 0) == (support == SLEEP_SUPPORTED)); + + if (ret_support) + *ret_support = support; + + return r; } diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index 467502a7da..bc10a5b39b 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -3,8 +3,6 @@ #include "time-util.h" -#define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR) - typedef enum SleepOperation { SLEEP_SUSPEND, SLEEP_HIBERNATE, @@ -37,6 +35,21 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, sleep_config_free); int parse_sleep_config(SleepConfig **sleep_config); -int can_sleep(SleepOperation operation); -int can_sleep_disk(char **types); -int can_sleep_state(char **types); +typedef enum SleepSupport { + SLEEP_SUPPORTED, + SLEEP_DISABLED, /* Disabled in SleepConfig.allow */ + SLEEP_NOT_CONFIGURED, /* SleepConfig.states is not configured */ + SLEEP_STATE_OR_MODE_NOT_SUPPORTED, /* SleepConfig.states/modes are not supported by kernel */ + SLEEP_RESUME_NOT_SUPPORTED, + SLEEP_NOT_ENOUGH_SWAP_SPACE, + SLEEP_ALARM_NOT_SUPPORTED, /* CLOCK_BOOTTIME_ALARM is unsupported by kernel (only used by s2h) */ +} SleepSupport; + +int sleep_supported_full(SleepOperation operation, SleepSupport *ret_support); +static inline int sleep_supported(SleepOperation operation) { + return sleep_supported_full(operation, NULL); +} + +/* Only for test-sleep-config */ +int sleep_state_supported(char **states); +int sleep_mode_supported(char **modes); diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index a299568ccf..8bfc9ee6cc 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -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); diff --git a/src/test/test-btrfs-physical-offset.c b/src/test/test-btrfs-physical-offset.c index bcc6252381..379ab31100 100644 --- a/src/test/test-btrfs-physical-offset.c +++ b/src/test/test-btrfs-physical-offset.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include "btrfs-util.h" @@ -18,7 +19,7 @@ int main(int argc, char *argv[]) { fd = open(argv[1], O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) { - log_error_errno(fd, "Failed to open '%s': %m", argv[1]); + log_error_errno(errno, "Failed to open '%s': %m", argv[1]); return EXIT_FAILURE; } diff --git a/src/test/test-sleep-config.c b/src/test/test-sleep-config.c index 58d910d1d9..112fec6346 100644 --- a/src/test/test-sleep-config.c +++ b/src/test/test-sleep-config.c @@ -34,7 +34,7 @@ TEST(parse_sleep_config) { log_debug(" states: %s", hys); } -TEST(sleep) { +TEST(sleep_supported) { _cleanup_strv_free_ char **standby = strv_new("standby"), **mem = strv_new("mem"), @@ -49,23 +49,23 @@ TEST(sleep) { printf("Secure boot: %sd\n", enable_disable(is_efi_secure_boot())); log_info("/= individual sleep modes =/"); - log_info("Standby configured: %s", yes_no(can_sleep_state(standby) > 0)); - log_info("Suspend configured: %s", yes_no(can_sleep_state(mem) > 0)); - log_info("Hibernate configured: %s", yes_no(can_sleep_state(disk) > 0)); - log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(can_sleep_disk(suspend) > 0)); - log_info("Hibernate+Reboot configured: %s", yes_no(can_sleep_disk(reboot) > 0)); - log_info("Hibernate+Platform configured: %s", yes_no(can_sleep_disk(platform) > 0)); - log_info("Hibernate+Shutdown configured: %s", yes_no(can_sleep_disk(shutdown) > 0)); - log_info("Freeze configured: %s", yes_no(can_sleep_state(freeze) > 0)); + log_info("Standby configured: %s", yes_no(sleep_state_supported(standby) > 0)); + log_info("Suspend configured: %s", yes_no(sleep_state_supported(mem) > 0)); + log_info("Hibernate configured: %s", yes_no(sleep_state_supported(disk) > 0)); + log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported(suspend) > 0)); + log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported(reboot) > 0)); + log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported(platform) > 0)); + log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported(shutdown) > 0)); + log_info("Freeze configured: %s", yes_no(sleep_state_supported(freeze) > 0)); log_info("/= high-level sleep verbs =/"); - r = can_sleep(SLEEP_SUSPEND); + r = sleep_supported(SLEEP_SUSPEND); log_info("Suspend configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); - r = can_sleep(SLEEP_HIBERNATE); + r = sleep_supported(SLEEP_HIBERNATE); log_info("Hibernation configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); - r = can_sleep(SLEEP_HYBRID_SLEEP); + r = sleep_supported(SLEEP_HYBRID_SLEEP); log_info("Hybrid-sleep configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); - r = can_sleep(SLEEP_SUSPEND_THEN_HIBERNATE); + r = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE); log_info("Suspend-then-Hibernate configured and possible: %s", r >= 0 ? yes_no(r) : STRERROR(r)); }