]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
sleep: store battery discharge rate/hour with hash
[thirdparty/systemd.git] / src / shared / sleep-config.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
19adb8a3 2/***
96b2fb93 3 Copyright © 2018 Dell Inc.
19adb8a3
ZJS
4***/
5
a8fbdf54 6#include <errno.h>
ca78ad1d 7#include <fcntl.h>
17c40b3a 8#include <linux/fs.h>
65ddc2c5 9#include <linux/magic.h>
a8fbdf54
TA
10#include <stdbool.h>
11#include <stddef.h>
36dd5ffd 12#include <sys/ioctl.h>
ca78ad1d
ZJS
13#include <sys/stat.h>
14#include <sys/types.h>
a8fbdf54
TA
15#include <syslog.h>
16#include <unistd.h>
19adb8a3 17
b5efdb8a 18#include "alloc-util.h"
52133271 19#include "blockdev-util.h"
7bdf56a2 20#include "btrfs-util.h"
19adb8a3 21#include "conf-parser.h"
a0f29c76 22#include "def.h"
7176f06c 23#include "devnum-util.h"
490d20e6 24#include "env-util.h"
427646ea 25#include "errno-util.h"
3ffd4af2 26#include "fd-util.h"
19adb8a3 27#include "fileio.h"
91ea7ebc
SS
28#include "hexdecoct.h"
29#include "id128-util.h"
19adb8a3 30#include "log.h"
a8fbdf54 31#include "macro.h"
411ae92b 32#include "path-util.h"
3ffd4af2 33#include "sleep-config.h"
91ea7ebc 34#include "siphash24.h"
65ddc2c5 35#include "stat-util.h"
7bdf56a2 36#include "stdio-util.h"
be2a4b0d 37#include "string-table.h"
07630cea 38#include "string-util.h"
19adb8a3 39#include "strv.h"
1bbbefe7 40#include "time-util.h"
19adb8a3 41
91ea7ebc
SS
42#define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour"
43#define BATTERY_DISCHARGE_RATE_HASH_KEY SD_ID128_MAKE(5f,9a,20,18,38,76,46,07,8d,36,58,0b,bb,c4,e0,63)
44
28ca9c24 45int parse_sleep_config(SleepConfig **ret_sleep_config) {
c2b2df60 46 _cleanup_(free_sleep_configp) SleepConfig *sc = NULL;
e8f1d00d
ZJS
47 int allow_suspend = -1, allow_hibernate = -1,
48 allow_s2h = -1, allow_hybrid_sleep = -1;
28ca9c24
ZS
49
50 sc = new0(SleepConfig, 1);
51 if (!sc)
52 return log_oom();
19adb8a3
ZJS
53
54 const ConfigTableItem items[] = {
c8cd8ca3
LP
55 { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
56 { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
57 { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
58 { "Sleep", "AllowHybridSleep", config_parse_tristate, 0, &allow_hybrid_sleep },
59
60 { "Sleep", "SuspendMode", config_parse_strv, 0, sc->modes + SLEEP_SUSPEND },
61 { "Sleep", "SuspendState", config_parse_strv, 0, sc->states + SLEEP_SUSPEND },
62 { "Sleep", "HibernateMode", config_parse_strv, 0, sc->modes + SLEEP_HIBERNATE },
63 { "Sleep", "HibernateState", config_parse_strv, 0, sc->states + SLEEP_HIBERNATE },
64 { "Sleep", "HybridSleepMode", config_parse_strv, 0, sc->modes + SLEEP_HYBRID_SLEEP },
65 { "Sleep", "HybridSleepState", config_parse_strv, 0, sc->states + SLEEP_HYBRID_SLEEP },
66
67 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_sec },
34c10968
LP
68 {}
69 };
19adb8a3 70
4f9ff96a
LP
71 (void) config_parse_many_nulstr(
72 PKGSYSCONFDIR "/sleep.conf",
73 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
74 "Sleep\0",
75 config_item_table_lookup, items,
76 CONFIG_PARSE_WARN,
77 NULL,
78 NULL);
19adb8a3 79
28ca9c24 80 /* use default values unless set */
c8cd8ca3
LP
81 sc->allow[SLEEP_SUSPEND] = allow_suspend != 0;
82 sc->allow[SLEEP_HIBERNATE] = allow_hibernate != 0;
83 sc->allow[SLEEP_HYBRID_SLEEP] = allow_hybrid_sleep >= 0 ? allow_hybrid_sleep
7874583d 84 : (allow_suspend != 0 && allow_hibernate != 0);
c8cd8ca3 85 sc->allow[SLEEP_SUSPEND_THEN_HIBERNATE] = allow_s2h >= 0 ? allow_s2h
7874583d 86 : (allow_suspend != 0 && allow_hibernate != 0);
28ca9c24 87
c8cd8ca3
LP
88 if (!sc->states[SLEEP_SUSPEND])
89 sc->states[SLEEP_SUSPEND] = strv_new("mem", "standby", "freeze");
90 if (!sc->modes[SLEEP_HIBERNATE])
91 sc->modes[SLEEP_HIBERNATE] = strv_new("platform", "shutdown");
92 if (!sc->states[SLEEP_HIBERNATE])
93 sc->states[SLEEP_HIBERNATE] = strv_new("disk");
94 if (!sc->modes[SLEEP_HYBRID_SLEEP])
95 sc->modes[SLEEP_HYBRID_SLEEP] = strv_new("suspend", "platform", "shutdown");
96 if (!sc->states[SLEEP_HYBRID_SLEEP])
97 sc->states[SLEEP_HYBRID_SLEEP] = strv_new("disk");
28ca9c24 98 if (sc->hibernate_delay_sec == 0)
a077755a 99 sc->hibernate_delay_sec = 2 * USEC_PER_HOUR;
28ca9c24
ZS
100
101 /* ensure values set for all required fields */
c8cd8ca3
LP
102 if (!sc->states[SLEEP_SUSPEND] || !sc->modes[SLEEP_HIBERNATE]
103 || !sc->states[SLEEP_HIBERNATE] || !sc->modes[SLEEP_HYBRID_SLEEP] || !sc->states[SLEEP_HYBRID_SLEEP])
19adb8a3 104 return log_oom();
19adb8a3 105
28ca9c24 106 *ret_sleep_config = TAKE_PTR(sc);
c58493c0 107
19adb8a3
ZJS
108 return 0;
109}
110
96d662fa
SS
111/* If battery percentage capacity is less than equal to 5% return success */
112int battery_is_low(void) {
113 int r;
114
115 /* We have not used battery capacity_level since value is set to full
116 * or Normal in case acpi is not working properly. In case of no battery
117 * 0 will be returned and system will be suspended for 1st cycle then hibernated */
118
119 r = read_battery_capacity_percentage();
120 if (r == -ENOENT)
121 return false;
122 if (r < 0)
123 return r;
124
125 return r <= 5;
126}
127
128/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */
129int read_battery_capacity_percentage(void) {
130 _cleanup_free_ char *bat_cap = NULL;
131 int battery_capacity, r;
132
133 r = read_one_line_file("/sys/class/power_supply/BAT0/capacity", &bat_cap);
134 if (r == -ENOENT)
135 return log_debug_errno(r, "/sys/class/power_supply/BAT0/capacity is unavailable. Assuming no battery exists: %m");
136 if (r < 0)
137 return log_debug_errno(r, "Failed to read /sys/class/power_supply/BAT0/capacity: %m");
138
139 r = safe_atoi(bat_cap, &battery_capacity);
140 if (r < 0)
141 return log_debug_errno(r, "Failed to parse battery capacity: %m");
142
143 if (battery_capacity < 0 || battery_capacity > 100)
144 return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity");
145
146 return battery_capacity;
147}
148
91ea7ebc
SS
149/* Read file path and return hash of value in that file */
150static int get_battery_identifier(const char *filepath, struct siphash *ret) {
151 _cleanup_free_ char *value = NULL;
152 int r;
153
154 assert(filepath);
155 assert(ret);
156
157 r = read_one_line_file(filepath, &value);
158 if (r == -ENOENT)
159 log_debug_errno(r, "%s is unavailable: %m", filepath);
160 else if (r < 0)
161 return log_debug_errno(r, "Failed to read %s: %m", filepath);
162 else if (isempty(value))
163 log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "%s is empty: %m", filepath);
164 else
165 siphash24_compress_string(value, ret);
166
167 return 0;
168}
169
170/* Read system and battery identifier from specific location and generate hash of it */
171static int get_system_battery_identifier_hash(uint64_t *ret_hash) {
172 struct siphash state;
173 sd_id128_t machine_id, product_id;
174 int r;
175
176 assert(ret_hash);
177
178 siphash24_init(&state, BATTERY_DISCHARGE_RATE_HASH_KEY.bytes);
179 get_battery_identifier("/sys/class/power_supply/BAT0/manufacturer", &state);
180 get_battery_identifier("/sys/class/power_supply/BAT0/model_name", &state);
181 get_battery_identifier("/sys/class/power_supply/BAT0/serial_number", &state);
182
183 r = sd_id128_get_machine(&machine_id);
184 if (r == -ENOENT)
185 log_debug_errno(r, "machine ID is unavailable: %m");
186 else if (r < 0)
187 return log_debug_errno(r, "Failed to get machine ID: %m");
188 else
189 siphash24_compress(&machine_id, sizeof(sd_id128_t), &state);
190
191 r = id128_get_product(&product_id);
192 if (r == -ENOENT)
193 log_debug_errno(r, "product_id does not exist: %m");
194 else if (r < 0)
195 return log_debug_errno(r, "Failed to get product ID: %m");
196 else
197 siphash24_compress(&product_id, sizeof(sd_id128_t), &state);
198
199 *ret_hash = siphash24_finalize(&state);
200
201 return 0;
202}
203
204/* battery percentage discharge rate per hour is in range 1-199 then return success */
205static bool battery_discharge_rate_is_valid(int battery_discharge_rate) {
206 return battery_discharge_rate > 0 && battery_discharge_rate < 200;
207}
208
209/* Battery percentage discharge rate per hour is read from specific file. It is stored along with system
210 * and battery identifier hash to maintain the integrity of discharge rate value */
211int get_battery_discharge_rate(void) {
212 _cleanup_free_ char *hash_id_discharge_rate = NULL, *stored_hash_id = NULL, *stored_discharge_rate = NULL;
213 const char *p;
214 uint64_t current_hash_id, hash_id;
215 int discharge_rate, r;
216
217 r = read_one_line_file(DISCHARGE_RATE_FILEPATH, &hash_id_discharge_rate);
218 if (r < 0)
219 return log_debug_errno(r, "Failed to read discharge rate from %s: %m", DISCHARGE_RATE_FILEPATH);
220
221 p = hash_id_discharge_rate;
222 r = extract_many_words(&p, " ", 0, &stored_hash_id, &stored_discharge_rate, NULL);
223 if (r < 0)
224 return log_debug_errno(r, "Failed to parse hash_id and discharge_rate read from %s location: %m", DISCHARGE_RATE_FILEPATH);
225 if (r != 2)
226 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid number of items fetched from %s", DISCHARGE_RATE_FILEPATH);
227
228 r = safe_atou64(stored_hash_id, &hash_id);
229 if (r < 0)
230 return log_debug_errno(r, "Failed to parse discharge rate read from %s location: %m", DISCHARGE_RATE_FILEPATH);
231
232 r = get_system_battery_identifier_hash(&current_hash_id);
233 if (r < 0)
234 return log_debug_errno(r, "Failed to generate system battery identifier hash: %m");
235
236 if(current_hash_id != hash_id)
237 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Current identifier does not match stored identifier: %m");
238
239 r = safe_atoi(stored_discharge_rate, &discharge_rate);
240 if (r < 0)
241 return log_debug_errno(r, "Failed to parse discharge rate read from %s location: %m", DISCHARGE_RATE_FILEPATH);
242
243 if (!battery_discharge_rate_is_valid(discharge_rate))
244 return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m");
245
246 return discharge_rate;
247}
248
249/* Write battery percentage discharge rate per hour along with system and battery identifier hash to file */
250int put_battery_discharge_rate(int estimated_battery_discharge_rate) {
251 uint64_t system_hash_id;
252 int r;
253
254 if (!battery_discharge_rate_is_valid(estimated_battery_discharge_rate))
255 return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m");
256
257 r = get_system_battery_identifier_hash(&system_hash_id);
258 if (r < 0)
259 return log_debug_errno(r, "Failed to generate system battery identifier hash: %m");
260
261 r = write_string_filef(
262 DISCHARGE_RATE_FILEPATH,
263 WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755,
264 "%"PRIu64" %d",
265 system_hash_id,
266 estimated_battery_discharge_rate);
267 if (r < 0)
268 return log_debug_errno(r, "Failed to create %s: %m", DISCHARGE_RATE_FILEPATH);
269
270 log_debug("Estimated discharge rate %d successfully updated to %s", estimated_battery_discharge_rate, DISCHARGE_RATE_FILEPATH);
271 return 0;
272}
273
19adb8a3 274int can_sleep_state(char **types) {
434fef6d 275 _cleanup_free_ char *text = NULL;
19adb8a3 276 int r;
19adb8a3
ZJS
277
278 if (strv_isempty(types))
279 return true;
280
281 /* If /sys is read-only we cannot sleep */
c0d8fbfa
LP
282 if (access("/sys/power/state", W_OK) < 0) {
283 log_debug_errno(errno, "/sys/power/state is not writable, cannot sleep: %m");
19adb8a3 284 return false;
c0d8fbfa 285 }
19adb8a3 286
434fef6d 287 r = read_one_line_file("/sys/power/state", &text);
c0d8fbfa
LP
288 if (r < 0) {
289 log_debug_errno(r, "Failed to read /sys/power/state, cannot sleep: %m");
19adb8a3 290 return false;
c0d8fbfa 291 }
19adb8a3 292
434fef6d
ZJS
293 const char *found;
294 r = string_contains_word_strv(text, NULL, types, &found);
295 if (r < 0)
296 return log_debug_errno(r, "Failed to parse /sys/power/state: %m");
297 if (r > 0)
298 log_debug("Sleep mode \"%s\" is supported by the kernel.", found);
299 else if (DEBUG_LOGGING) {
c550cb7f
ZJS
300 _cleanup_free_ char *t = strv_join(types, "/");
301 log_debug("Sleep mode %s not supported by the kernel, sorry.", strnull(t));
302 }
434fef6d 303 return r;
19adb8a3
ZJS
304}
305
306int can_sleep_disk(char **types) {
434fef6d 307 _cleanup_free_ char *text = NULL;
19adb8a3 308 int r;
19adb8a3
ZJS
309
310 if (strv_isempty(types))
311 return true;
312
313 /* If /sys is read-only we cannot sleep */
7474f15b
LP
314 if (access("/sys/power/disk", W_OK) < 0) {
315 log_debug_errno(errno, "/sys/power/disk is not writable: %m");
19adb8a3 316 return false;
7474f15b 317 }
19adb8a3 318
434fef6d 319 r = read_one_line_file("/sys/power/disk", &text);
7474f15b
LP
320 if (r < 0) {
321 log_debug_errno(r, "Couldn't read /sys/power/disk: %m");
19adb8a3 322 return false;
7474f15b 323 }
19adb8a3 324
434fef6d
ZJS
325 for (const char *p = text;;) {
326 _cleanup_free_ char *word = NULL;
19adb8a3 327
434fef6d
ZJS
328 r = extract_first_word(&p, &word, NULL, 0);
329 if (r < 0)
330 return log_debug_errno(r, "Failed to parse /sys/power/disk: %m");
331 if (r == 0)
332 break;
19adb8a3 333
434fef6d
ZJS
334 char *s = word;
335 size_t l = strlen(s);
336 if (s[0] == '[' && s[l-1] == ']') {
337 s[l-1] = '\0';
338 s++;
339 }
340
341 if (strv_contains(types, s)) {
342 log_debug("Disk sleep mode \"%s\" is supported by the kernel.", s);
343 return true;
19adb8a3
ZJS
344 }
345 }
346
434fef6d
ZJS
347 if (DEBUG_LOGGING) {
348 _cleanup_free_ char *t = strv_join(types, "/");
349 log_debug("Disk sleep mode %s not supported by the kernel, sorry.", strnull(t));
350 }
19adb8a3
ZJS
351 return false;
352}
353
69ab8088
ZJS
354#define HIBERNATION_SWAP_THRESHOLD 0.98
355
7bdf56a2 356SwapEntry* swap_entry_free(SwapEntry *se) {
88bc86fc
ZS
357 if (!se)
358 return NULL;
359
360 free(se->device);
361 free(se->type);
362
363 return mfree(se);
364}
365
7bdf56a2
ZS
366HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
367 if (!hl)
368 return NULL;
369
370 swap_entry_free(hl->swap);
7bdf56a2
ZS
371
372 return mfree(hl);
373}
374
52133271 375static int swap_device_to_device_id(const SwapEntry *swap, dev_t *ret_dev) {
7bdf56a2 376 struct stat sb;
7bdf56a2
ZS
377 int r;
378
379 assert(swap);
380 assert(swap->device);
381 assert(swap->type);
382
e9f0c5d0 383 r = stat(swap->device, &sb);
7bdf56a2 384 if (r < 0)
6f9120ad 385 return -errno;
7bdf56a2 386
52133271 387 if (streq(swap->type, "partition")) {
e9f0c5d0 388 if (!S_ISBLK(sb.st_mode))
52133271 389 return -ENOTBLK;
6f9120ad 390
e9f0c5d0
ZJS
391 *ret_dev = sb.st_rdev;
392 return 0;
6f9120ad 393 }
7bdf56a2 394
6f9120ad 395 return get_block_device(swap->device, ret_dev);
7bdf56a2
ZS
396}
397
52133271 398/*
162392b7 399 * Attempt to calculate the swap file offset on supported filesystems. On unsupported
8efc2c16 400 * filesystems, a debug message is logged and ret_offset is set to UINT64_MAX.
52133271 401 */
7bdf56a2
ZS
402static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
403 _cleanup_close_ int fd = -1;
404 _cleanup_free_ struct fiemap *fiemap = NULL;
405 struct stat sb;
65ddc2c5 406 int r;
7bdf56a2
ZS
407
408 assert(swap);
409 assert(swap->device);
410 assert(streq(swap->type, "file"));
411
412 fd = open(swap->device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
e97c3691 413 if (fd < 0)
c02540dc 414 return log_debug_errno(errno, "Failed to open swap file %s to determine on-disk offset: %m", swap->device);
7bdf56a2 415
e97c3691 416 if (fstat(fd, &sb) < 0)
c02540dc 417 return log_debug_errno(errno, "Failed to stat %s: %m", swap->device);
7bdf56a2 418
65ddc2c5
ZJS
419 r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC);
420 if (r < 0)
421 return log_debug_errno(r, "Error checking %s for Btrfs filesystem: %m", swap->device);
422 if (r > 0) {
8efc2c16
ZJS
423 log_debug("%s: detection of swap file offset on Btrfs is not supported", swap->device);
424 *ret_offset = UINT64_MAX;
7bdf56a2
ZS
425 return 0;
426 }
427
428 r = read_fiemap(fd, &fiemap);
429 if (r < 0)
430 return log_debug_errno(r, "Unable to read extent map for '%s': %m", swap->device);
431
432 *ret_offset = fiemap->fm_extents[0].fe_physical / page_size();
7bdf56a2
ZS
433 return 0;
434}
88bc86fc 435
52133271
ZS
436static int read_resume_files(dev_t *ret_resume, uint64_t *ret_resume_offset) {
437 _cleanup_free_ char *resume_str = NULL, *resume_offset_str = NULL;
7bdf56a2 438 uint64_t resume_offset = 0;
c02540dc 439 dev_t resume;
7bdf56a2
ZS
440 int r;
441
52133271 442 r = read_one_line_file("/sys/power/resume", &resume_str);
7bdf56a2 443 if (r < 0)
5021735f 444 return log_debug_errno(r, "Error reading /sys/power/resume: %m");
7bdf56a2 445
7176f06c 446 r = parse_devnum(resume_str, &resume);
52133271
ZS
447 if (r < 0)
448 return log_debug_errno(r, "Error parsing /sys/power/resume device: %s: %m", resume_str);
449
7bdf56a2 450 r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
b72571e0 451 if (r == -ENOENT)
c02540dc 452 log_debug_errno(r, "Kernel does not support resume_offset; swap file offset detection will be skipped.");
b72571e0 453 else if (r < 0)
5021735f 454 return log_debug_errno(r, "Error reading /sys/power/resume_offset: %m");
b72571e0 455 else {
7bdf56a2
ZS
456 r = safe_atou64(resume_offset_str, &resume_offset);
457 if (r < 0)
c02540dc 458 return log_debug_errno(r, "Failed to parse value in /sys/power/resume_offset \"%s\": %m", resume_offset_str);
7bdf56a2
ZS
459 }
460
8f817cb8
ZJS
461 if (resume_offset > 0 && resume == 0)
462 log_debug("Warning: found /sys/power/resume_offset==%" PRIu64 ", but /sys/power/resume unset. Misconfiguration?",
5021735f 463 resume_offset);
7bdf56a2 464
52133271 465 *ret_resume = resume;
7bdf56a2
ZS
466 *ret_resume_offset = resume_offset;
467
468 return 0;
469}
470
52133271
ZS
471/*
472 * Determine if the HibernateLocation matches the resume= (device) and resume_offset= (file).
473 */
474static bool location_is_resume_device(const HibernateLocation *location, dev_t sys_resume, uint64_t sys_offset) {
475 if (!location)
476 return false;
477
8efc2c16
ZJS
478 return sys_resume > 0 &&
479 sys_resume == location->devno &&
480 (sys_offset == location->offset || (sys_offset > 0 && location->offset == UINT64_MAX));
7bdf56a2
ZS
481}
482
483/*
484 * Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
485 * /sys/power/resume_offset.
486 *
487 * Returns:
52133271
ZS
488 * 1 - Values are set in /sys/power/resume and /sys/power/resume_offset.
489 * ret_hibernate_location will represent matching /proc/swap entry if identified or NULL if not.
490 *
491 * 0 - No values are set in /sys/power/resume and /sys/power/resume_offset.
492 ret_hibernate_location will represent the highest priority swap with most remaining space discovered in /proc/swaps.
493 *
494 * Negative value in the case of error.
7bdf56a2
ZS
495 */
496int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
b72571e0 497 _cleanup_fclose_ FILE *f = NULL;
7bdf56a2 498 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
aff81b18 499 dev_t sys_resume = 0; /* Unnecessary initialization to appease gcc */
7bdf56a2 500 uint64_t sys_offset = 0;
8efc2c16 501 bool resume_match = false;
7bdf56a2
ZS
502 int r;
503
504 /* read the /sys/power/resume & /sys/power/resume_offset values */
505 r = read_resume_files(&sys_resume, &sys_offset);
506 if (r < 0)
507 return r;
9fb3675e 508
c8a202b7 509 f = fopen("/proc/swaps", "re");
9fb3675e 510 if (!f) {
c02540dc
LP
511 log_debug_errno(errno, "Failed to open /proc/swaps: %m");
512 return errno == ENOENT ? -EOPNOTSUPP : -errno; /* Convert swap not supported to a recognizable error */
9fb3675e 513 }
69ab8088 514
9fb3675e 515 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
8efc2c16 516 for (unsigned i = 1;; i++) {
88bc86fc 517 _cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
7bdf56a2 518 uint64_t swap_offset = 0;
9fb3675e
ZJS
519 int k;
520
88bc86fc
ZS
521 swap = new0(SwapEntry, 1);
522 if (!swap)
c02540dc 523 return -ENOMEM;
88bc86fc 524
9fb3675e 525 k = fscanf(f,
88bc86fc
ZS
526 "%ms " /* device/file */
527 "%ms " /* type of swap */
528 "%" PRIu64 /* swap size */
529 "%" PRIu64 /* used */
530 "%i\n", /* priority */
531 &swap->device, &swap->type, &swap->size, &swap->used, &swap->priority);
3dea6886
LP
532 if (k == EOF)
533 break;
88bc86fc 534 if (k != 5) {
c02540dc 535 log_debug("Failed to parse /proc/swaps:%u, ignoring", i);
9fb3675e
ZJS
536 continue;
537 }
538
88bc86fc 539 if (streq(swap->type, "file")) {
88bc86fc 540 if (endswith(swap->device, "\\040(deleted)")) {
c02540dc 541 log_debug("Ignoring deleted swap file '%s'.", swap->device);
411ae92b
AJ
542 continue;
543 }
544
7bdf56a2
ZS
545 r = calculate_swap_file_offset(swap, &swap_offset);
546 if (r < 0)
547 return r;
52133271 548
88bc86fc 549 } else if (streq(swap->type, "partition")) {
411ae92b 550 const char *fn;
3dea6886 551
88bc86fc 552 fn = path_startswith(swap->device, "/dev/");
411ae92b 553 if (fn && startswith(fn, "zram")) {
8efc2c16 554 log_debug("%s: ignoring zram swap", swap->device);
411ae92b
AJ
555 continue;
556 }
8efc2c16 557
7bdf56a2 558 } else {
8efc2c16 559 log_debug("%s: swap type %s is unsupported for hibernation, ignoring", swap->device, swap->type);
7bdf56a2 560 continue;
9fb3675e 561 }
3dea6886 562
7bdf56a2 563 /* prefer resume device or highest priority swap with most remaining space */
936a7cb6
E
564 if (sys_resume == 0) {
565 if (hibernate_location && swap->priority < hibernate_location->swap->priority) {
566 log_debug("%s: ignoring device with lower priority", swap->device);
567 continue;
568 }
569 if (hibernate_location &&
570 (swap->priority == hibernate_location->swap->priority
571 && swap->size - swap->used < hibernate_location->swap->size - hibernate_location->swap->used)) {
572 log_debug("%s: ignoring device with lower usable space", swap->device);
573 continue;
574 }
8efc2c16 575 }
7bdf56a2 576
8efc2c16
ZJS
577 dev_t swap_device;
578 r = swap_device_to_device_id(swap, &swap_device);
579 if (r < 0)
c02540dc 580 return log_debug_errno(r, "%s: failed to query device number: %m", swap->device);
d161680e
LP
581 if (swap_device == 0)
582 return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "%s: not backed by block device.", swap->device);
7bdf56a2 583
8efc2c16
ZJS
584 hibernate_location = hibernate_location_free(hibernate_location);
585 hibernate_location = new(HibernateLocation, 1);
586 if (!hibernate_location)
c02540dc 587 return -ENOMEM;
7bdf56a2 588
8efc2c16
ZJS
589 *hibernate_location = (HibernateLocation) {
590 .devno = swap_device,
591 .offset = swap_offset,
592 .swap = TAKE_PTR(swap),
593 };
7bdf56a2 594
8efc2c16
ZJS
595 /* if the swap is the resume device, stop the loop */
596 if (location_is_resume_device(hibernate_location, sys_resume, sys_offset)) {
597 log_debug("%s: device matches configured resume settings.", hibernate_location->swap->device);
598 resume_match = true;
599 break;
88bc86fc 600 }
8efc2c16
ZJS
601
602 log_debug("%s: is a candidate device.", hibernate_location->swap->device);
69ab8088
ZJS
603 }
604
8efc2c16
ZJS
605 /* We found nothing at all */
606 if (!hibernate_location)
607 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
608 "No possible swap partitions or files suitable for hibernation were found in /proc/swaps.");
52133271 609
8efc2c16
ZJS
610 /* resume= is set but a matching /proc/swaps entry was not found */
611 if (sys_resume != 0 && !resume_match)
612 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
613 "No swap partitions or files matching resume config were found in /proc/swaps.");
52133271 614
8efc2c16
ZJS
615 if (hibernate_location->offset == UINT64_MAX) {
616 if (sys_offset == 0)
617 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "Offset detection failed and /sys/power/resume_offset is not set.");
7bdf56a2 618
8efc2c16
ZJS
619 hibernate_location->offset = sys_offset;
620 }
7bdf56a2 621
52133271
ZS
622 if (resume_match)
623 log_debug("Hibernation will attempt to use swap entry with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
624 hibernate_location->swap->device, major(hibernate_location->devno), minor(hibernate_location->devno),
625 hibernate_location->offset, hibernate_location->swap->priority);
626 else
8efc2c16 627 log_debug("/sys/power/resume is not configured; attempting to hibernate with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
52133271
ZS
628 hibernate_location->swap->device, major(hibernate_location->devno), minor(hibernate_location->devno),
629 hibernate_location->offset, hibernate_location->swap->priority);
88bc86fc 630
7bdf56a2 631 *ret_hibernate_location = TAKE_PTR(hibernate_location);
88bc86fc 632
52133271 633 if (resume_match)
7bdf56a2 634 return 1;
88bc86fc
ZS
635
636 return 0;
9fb3675e
ZJS
637}
638
4638cd39 639static bool enough_swap_for_hibernation(void) {
9fb3675e 640 _cleanup_free_ char *active = NULL;
7bdf56a2 641 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
39883f62 642 unsigned long long act = 0;
9fb3675e
ZJS
643 int r;
644
490d20e6
VV
645 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
646 return true;
647
7bdf56a2 648 r = find_hibernate_location(&hibernate_location);
9fb3675e 649 if (r < 0)
69ab8088 650 return false;
69ab8088 651
52133271
ZS
652 /* If /sys/power/{resume,resume_offset} is configured but a matching entry
653 * could not be identified in /proc/swaps, user is likely using Btrfs with a swapfile;
654 * return true and let the system attempt hibernation.
655 */
656 if (r > 0 && !hibernate_location) {
657 log_debug("Unable to determine remaining swap space; hibernation may fail");
658 return true;
659 }
660
661 if (!hibernate_location)
662 return false;
663
c4cd1d4d 664 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
69ab8088 665 if (r < 0) {
904865b8 666 log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
69ab8088
ZJS
667 return false;
668 }
669
670 r = safe_atollu(active, &act);
671 if (r < 0) {
904865b8 672 log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active);
69ab8088
ZJS
673 return false;
674 }
675
7bdf56a2 676 r = act <= (hibernate_location->swap->size - hibernate_location->swap->used) * HIBERNATION_SWAP_THRESHOLD;
88bc86fc 677 log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
7bdf56a2 678 r ? "Enough" : "Not enough", act, hibernate_location->swap->size, hibernate_location->swap->used, 100*HIBERNATION_SWAP_THRESHOLD);
69ab8088
ZJS
679
680 return r;
681}
682
17c40b3a
ML
683int read_fiemap(int fd, struct fiemap **ret) {
684 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
17c40b3a
ML
685 struct stat statinfo;
686 uint32_t result_extents = 0;
687 uint64_t fiemap_start = 0, fiemap_length;
6524f1a8 688 const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
17c40b3a
ML
689
690 if (fstat(fd, &statinfo) < 0)
691 return log_debug_errno(errno, "Cannot determine file size: %m");
692 if (!S_ISREG(statinfo.st_mode))
693 return -ENOTTY;
694 fiemap_length = statinfo.st_size;
695
6524f1a8
ZJS
696 /* Zero this out in case we run on a file with no extents */
697 fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
698 if (!fiemap)
699 return -ENOMEM;
700
6524f1a8 701 result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
702 if (!result_fiemap)
703 return -ENOMEM;
704
705 /* XFS filesystem has incorrect implementation of fiemap ioctl and
706 * returns extents for only one block-group at a time, so we need
707 * to handle it manually, starting the next fiemap call from the end
708 * of the last extent
709 */
710 while (fiemap_start < fiemap_length) {
711 *fiemap = (struct fiemap) {
712 .fm_start = fiemap_start,
713 .fm_length = fiemap_length,
714 .fm_flags = FIEMAP_FLAG_SYNC,
715 };
716
717 /* Find out how many extents there are */
718 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
719 return log_debug_errno(errno, "Failed to read extents: %m");
720
721 /* Nothing to process */
722 if (fiemap->fm_mapped_extents == 0)
723 break;
724
6524f1a8
ZJS
725 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
726 * the extents for the whole file. Add space for the initial struct fiemap. */
319a4f4b 727 if (!greedy_realloc0((void**) &fiemap, n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
728 return -ENOMEM;
729
730 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
731 fiemap->fm_mapped_extents = 0;
732
733 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
734 return log_debug_errno(errno, "Failed to read extents: %m");
735
6524f1a8 736 /* Resize result_fiemap to allow us to copy in the extents */
319a4f4b 737 if (!greedy_realloc((void**) &result_fiemap,
6524f1a8 738 n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
739 return -ENOMEM;
740
741 memcpy(result_fiemap->fm_extents + result_extents,
742 fiemap->fm_extents,
743 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
744
745 result_extents += fiemap->fm_mapped_extents;
746
747 /* Highly unlikely that it is zero */
6524f1a8 748 if (_likely_(fiemap->fm_mapped_extents > 0)) {
17c40b3a
ML
749 uint32_t i = fiemap->fm_mapped_extents - 1;
750
751 fiemap_start = fiemap->fm_extents[i].fe_logical +
752 fiemap->fm_extents[i].fe_length;
753
754 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
755 break;
756 }
757 }
758
759 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
760 result_fiemap->fm_mapped_extents = result_extents;
761 *ret = TAKE_PTR(result_fiemap);
762 return 0;
763}
764
c8cd8ca3 765static int can_sleep_internal(const SleepConfig *sleep_config, SleepOperation operation, bool check_allowed);
e8f1d00d 766
28ca9c24 767static bool can_s2h(const SleepConfig *sleep_config) {
c8cd8ca3
LP
768
769 static const SleepOperation operations[] = {
770 SLEEP_SUSPEND,
771 SLEEP_HIBERNATE,
772 };
773
c58493c0
ML
774 int r;
775
1bbbefe7 776 if (!clock_supported(CLOCK_BOOTTIME_ALARM)) {
c732e879 777 log_debug("CLOCK_BOOTTIME_ALARM is not supported.");
c58493c0
ML
778 return false;
779 }
780
c8cd8ca3
LP
781 for (size_t i = 0; i < ELEMENTSOF(operations); i++) {
782 r = can_sleep_internal(sleep_config, operations[i], false);
887b2019 783 if (IN_SET(r, 0, -ENOSPC)) {
c8cd8ca3 784 log_debug("Unable to %s system.", sleep_operation_to_string(operations[i]));
c863dc05
ZJS
785 return false;
786 }
b71c9758 787 if (r < 0)
c8cd8ca3 788 return log_debug_errno(r, "Failed to check if %s is possible: %m", sleep_operation_to_string(operations[i]));
c58493c0
ML
789 }
790
791 return true;
792}
793
c8cd8ca3
LP
794static int can_sleep_internal(
795 const SleepConfig *sleep_config,
796 SleepOperation operation,
797 bool check_allowed) {
c58493c0 798
c8cd8ca3
LP
799 assert(operation >= 0);
800 assert(operation < _SLEEP_OPERATION_MAX);
19adb8a3 801
c8cd8ca3
LP
802 if (check_allowed && !sleep_config->allow[operation]) {
803 log_debug("Sleep mode \"%s\" is disabled by configuration.", sleep_operation_to_string(operation));
e8f1d00d
ZJS
804 return false;
805 }
806
c8cd8ca3 807 if (operation == SLEEP_SUSPEND_THEN_HIBERNATE)
28ca9c24 808 return can_s2h(sleep_config);
e8f1d00d 809
61dc8481
LP
810 if (can_sleep_state(sleep_config->states[operation]) <= 0 ||
811 can_sleep_disk(sleep_config->modes[operation]) <= 0)
69ab8088
ZJS
812 return false;
813
c8cd8ca3 814 if (operation == SLEEP_SUSPEND)
b71c9758
ZJS
815 return true;
816
4638cd39 817 if (!enough_swap_for_hibernation())
b71c9758
ZJS
818 return -ENOSPC;
819
820 return true;
19adb8a3 821}
e8f1d00d 822
c8cd8ca3 823int can_sleep(SleepOperation operation) {
28ca9c24
ZS
824 _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
825 int r;
826
827 r = parse_sleep_config(&sleep_config);
828 if (r < 0)
829 return r;
830
c8cd8ca3 831 return can_sleep_internal(sleep_config, operation, true);
28ca9c24
ZS
832}
833
1326de01 834SleepConfig* free_sleep_config(SleepConfig *sc) {
28ca9c24 835 if (!sc)
1326de01 836 return NULL;
28ca9c24 837
c8cd8ca3
LP
838 for (SleepOperation i = 0; i < _SLEEP_OPERATION_MAX; i++) {
839 strv_free(sc->modes[i]);
840 strv_free(sc->states[i]);
841 }
28ca9c24 842
1326de01 843 return mfree(sc);
e8f1d00d 844}
be2a4b0d
LP
845
846static const char* const sleep_operation_table[_SLEEP_OPERATION_MAX] = {
847 [SLEEP_SUSPEND] = "suspend",
848 [SLEEP_HIBERNATE] = "hibernate",
849 [SLEEP_HYBRID_SLEEP] = "hybrid-sleep",
850 [SLEEP_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate",
851};
852
853DEFINE_STRING_TABLE_LOOKUP(sleep_operation, SleepOperation);