1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
6 Copyright 2018 Dell Inc.
8 elogind is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 elogind is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with elogind; If not, see <http://www.gnu.org/licenses/>.
23 //#include <linux/fs.h>
24 //#include <stdbool.h>
31 #include "alloc-util.h"
32 //#include "conf-parser.h"
34 //#include "env-util.h"
39 #include "parse-util.h"
40 #include "sleep-config.h"
41 #include "string-util.h"
44 #if 0 /// UNNEEDED by elogind
45 int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
47 _cleanup_strv_free_ char
48 **suspend_mode = NULL, **suspend_state = NULL,
49 **hibernate_mode = NULL, **hibernate_state = NULL,
50 **hybrid_mode = NULL, **hybrid_state = NULL;
51 char **modes, **states;
52 usec_t delay = 180 * USEC_PER_MINUTE;
54 const ConfigTableItem items[] = {
55 { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
56 { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
57 { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
58 { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
59 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
60 { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
61 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
65 (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
66 CONF_PATHS_NULSTR("elogind/sleep.conf.d"),
67 "Sleep\0", config_item_table_lookup, items,
68 CONFIG_PARSE_WARN, NULL);
70 if (streq(verb, "suspend")) {
71 /* empty by default */
72 modes = TAKE_PTR(suspend_mode);
75 states = TAKE_PTR(suspend_state);
77 states = strv_new("mem", "standby", "freeze", NULL);
79 } else if (streq(verb, "hibernate")) {
81 modes = TAKE_PTR(hibernate_mode);
83 modes = strv_new("platform", "shutdown", NULL);
86 states = TAKE_PTR(hibernate_state);
88 states = strv_new("disk", NULL);
90 } else if (streq(verb, "hybrid-sleep")) {
92 modes = TAKE_PTR(hybrid_mode);
94 modes = strv_new("suspend", "platform", "shutdown", NULL);
97 states = TAKE_PTR(hybrid_state);
99 states = strv_new("disk", NULL);
101 } else if (streq(verb, "suspend-then-hibernate"))
102 modes = states = NULL;
104 assert_not_reached("what verb");
106 if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
107 (!states && !streq(verb, "suspend-then-hibernate"))) {
124 #if 1 /// Only available in this file for elogind
127 int can_sleep_state(char **types) {
130 _cleanup_free_ char *p = NULL;
132 if (strv_isempty(types))
135 /* If /sys is read-only we cannot sleep */
136 if (access("/sys/power/state", W_OK) < 0)
139 r = read_one_line_file("/sys/power/state", &p);
143 STRV_FOREACH(type, types) {
144 const char *word, *state;
148 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
149 if (l == k && memcmp(word, *type, l) == 0)
156 #if 1 /// Only available in this file for elogind
159 int can_sleep_disk(char **types) {
162 _cleanup_free_ char *p = NULL;
164 if (strv_isempty(types))
167 /* If /sys is read-only we cannot sleep */
168 if (access("/sys/power/disk", W_OK) < 0)
171 r = read_one_line_file("/sys/power/disk", &p);
175 STRV_FOREACH(type, types) {
176 const char *word, *state;
180 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
181 if (l == k && memcmp(word, *type, l) == 0)
186 memcmp(word + 1, *type, l - 2) == 0 &&
195 #define HIBERNATION_SWAP_THRESHOLD 0.98
197 int find_hibernate_location(char **device, char **type, size_t *size, size_t *used) {
198 _cleanup_fclose_ FILE *f;
201 f = fopen("/proc/swaps", "re");
203 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
204 "Failed to retrieve open /proc/swaps: %m");
209 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
212 _cleanup_free_ char *dev_field = NULL, *type_field = NULL;
213 size_t size_field, used_field;
217 "%ms " /* device/file */
218 "%ms " /* type of swap */
219 "%zu " /* swap size */
221 "%*i\n", /* priority */
222 &dev_field, &type_field, &size_field, &used_field);
227 log_warning("Failed to parse /proc/swaps:%u", i);
231 if (streq(type_field, "partition") && endswith(dev_field, "\\040(deleted)")) {
232 log_warning("Ignoring deleted swapfile '%s'.", dev_field);
236 *device = TAKE_PTR(dev_field);
238 *type = TAKE_PTR(type_field);
246 log_debug("No swap partitions were found.");
250 static bool enough_memory_for_hibernation(void) {
251 _cleanup_free_ char *active = NULL;
252 unsigned long long act = 0;
253 size_t size = 0, used = 0;
256 #if 0 /// elogind does not allow any bypass, we are never init!
257 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
261 r = find_hibernate_location(NULL, NULL, &size, &used);
265 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
267 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
271 r = safe_atollu(active, &act);
273 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
278 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
279 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
280 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
285 int read_fiemap(int fd, struct fiemap **ret) {
286 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
287 struct stat statinfo;
288 uint32_t result_extents = 0;
289 uint64_t fiemap_start = 0, fiemap_length;
290 const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
291 size_t fiemap_allocated = n_extra, result_fiemap_allocated = n_extra;
293 if (fstat(fd, &statinfo) < 0)
294 return log_debug_errno(errno, "Cannot determine file size: %m");
295 if (!S_ISREG(statinfo.st_mode))
297 fiemap_length = statinfo.st_size;
299 /* Zero this out in case we run on a file with no extents */
300 fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
304 result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
308 /* XFS filesystem has incorrect implementation of fiemap ioctl and
309 * returns extents for only one block-group at a time, so we need
310 * to handle it manually, starting the next fiemap call from the end
313 while (fiemap_start < fiemap_length) {
314 *fiemap = (struct fiemap) {
315 .fm_start = fiemap_start,
316 .fm_length = fiemap_length,
317 .fm_flags = FIEMAP_FLAG_SYNC,
320 /* Find out how many extents there are */
321 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
322 return log_debug_errno(errno, "Failed to read extents: %m");
324 /* Nothing to process */
325 if (fiemap->fm_mapped_extents == 0)
328 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
329 * the extents for the whole file. Add space for the initial struct fiemap. */
330 if (!greedy_realloc0((void**) &fiemap, &fiemap_allocated,
331 n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
334 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
335 fiemap->fm_mapped_extents = 0;
337 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
338 return log_debug_errno(errno, "Failed to read extents: %m");
340 /* Resize result_fiemap to allow us to copy in the extents */
341 if (!greedy_realloc((void**) &result_fiemap, &result_fiemap_allocated,
342 n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
345 memcpy(result_fiemap->fm_extents + result_extents,
347 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
349 result_extents += fiemap->fm_mapped_extents;
351 /* Highly unlikely that it is zero */
352 if (_likely_(fiemap->fm_mapped_extents > 0)) {
353 uint32_t i = fiemap->fm_mapped_extents - 1;
355 fiemap_start = fiemap->fm_extents[i].fe_logical +
356 fiemap->fm_extents[i].fe_length;
358 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
363 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
364 result_fiemap->fm_mapped_extents = result_extents;
365 *ret = TAKE_PTR(result_fiemap);
369 #if 0 /// elogind has to do, or better, *can* do it differently
370 static bool can_s2h(void) {
374 r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
376 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
377 "/sys/class/rct/rct0/wakealarm is not writable %m");
381 FOREACH_STRING(p, "suspend", "hibernate") {
383 if (IN_SET(r, 0, -ENOSPC)) {
384 log_debug("Unable to %s system.", p);
388 return log_debug_errno(r, "Failed to check if %s is possible: %m", p);
394 int can_sleep(Manager *m, const char *verb) {
396 assert(streq(verb, "suspend") ||
397 streq(verb, "hibernate") ||
398 streq(verb, "hybrid-sleep"));
399 int can_sleep(const char *verb) {
400 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
403 if ( streq(verb, "suspend")
404 && ( !can_sleep_state(m->suspend_state)
405 || !can_sleep_disk(m->suspend_mode) ) )
407 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
408 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
410 if ( streq(verb, "hibernate")
411 && ( !can_sleep_state(m->hibernate_state)
412 || !can_sleep_disk(m->hibernate_mode) ) )
414 if (streq(verb, "suspend-to-hibernate"))
415 if (streq(verb, "suspend-then-hibernate"))
418 if ( streq(verb, "hybrid-sleep")
419 && ( !can_sleep_state(m->hybrid_sleep_state)
420 || !can_sleep_disk(m->hybrid_sleep_mode) ) )
421 r = parse_sleep_config(verb, &modes, &states, NULL);
425 if (!can_sleep_state(states) || !can_sleep_disk(modes))
428 return streq(verb, "suspend") || enough_memory_for_hibernation();
429 if (streq(verb, "suspend"))
432 if (!enough_memory_for_hibernation())