]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
Merge pull request #24263 from pothos/sysext-for-static-binaries
[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
746cf898
SS
18#include "sd-device.h"
19
b5efdb8a 20#include "alloc-util.h"
52133271 21#include "blockdev-util.h"
7bdf56a2 22#include "btrfs-util.h"
19adb8a3 23#include "conf-parser.h"
a0f29c76 24#include "def.h"
746cf898 25#include "device-util.h"
7176f06c 26#include "devnum-util.h"
490d20e6 27#include "env-util.h"
427646ea 28#include "errno-util.h"
3ffd4af2 29#include "fd-util.h"
19adb8a3 30#include "fileio.h"
91ea7ebc
SS
31#include "hexdecoct.h"
32#include "id128-util.h"
19adb8a3 33#include "log.h"
a8fbdf54 34#include "macro.h"
411ae92b 35#include "path-util.h"
3ffd4af2 36#include "sleep-config.h"
91ea7ebc 37#include "siphash24.h"
65ddc2c5 38#include "stat-util.h"
7bdf56a2 39#include "stdio-util.h"
be2a4b0d 40#include "string-table.h"
07630cea 41#include "string-util.h"
19adb8a3 42#include "strv.h"
1bbbefe7 43#include "time-util.h"
19adb8a3 44
746cf898 45#define BATTERY_LOW_CAPACITY_LEVEL 5
91ea7ebc
SS
46#define DISCHARGE_RATE_FILEPATH "/var/lib/systemd/sleep/battery_discharge_percentage_rate_per_hour"
47#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)
48
746cf898
SS
49static void *CAPACITY_TO_PTR(int capacity) {
50 assert(capacity >= 0);
51 assert(capacity <= 100);
52 return INT_TO_PTR(capacity + 1);
53}
54
55static int PTR_TO_CAPACITY(void *p) {
56 int capacity = PTR_TO_INT(p) - 1;
57 assert(capacity >= 0);
58 assert(capacity <= 100);
59 return capacity;
60}
61
28ca9c24 62int parse_sleep_config(SleepConfig **ret_sleep_config) {
c2b2df60 63 _cleanup_(free_sleep_configp) SleepConfig *sc = NULL;
e8f1d00d
ZJS
64 int allow_suspend = -1, allow_hibernate = -1,
65 allow_s2h = -1, allow_hybrid_sleep = -1;
28ca9c24
ZS
66
67 sc = new0(SleepConfig, 1);
68 if (!sc)
69 return log_oom();
19adb8a3
ZJS
70
71 const ConfigTableItem items[] = {
c8cd8ca3
LP
72 { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
73 { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
74 { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
75 { "Sleep", "AllowHybridSleep", config_parse_tristate, 0, &allow_hybrid_sleep },
76
77 { "Sleep", "SuspendMode", config_parse_strv, 0, sc->modes + SLEEP_SUSPEND },
78 { "Sleep", "SuspendState", config_parse_strv, 0, sc->states + SLEEP_SUSPEND },
79 { "Sleep", "HibernateMode", config_parse_strv, 0, sc->modes + SLEEP_HIBERNATE },
80 { "Sleep", "HibernateState", config_parse_strv, 0, sc->states + SLEEP_HIBERNATE },
81 { "Sleep", "HybridSleepMode", config_parse_strv, 0, sc->modes + SLEEP_HYBRID_SLEEP },
82 { "Sleep", "HybridSleepState", config_parse_strv, 0, sc->states + SLEEP_HYBRID_SLEEP },
83
84 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_sec },
34c10968
LP
85 {}
86 };
19adb8a3 87
4f9ff96a
LP
88 (void) config_parse_many_nulstr(
89 PKGSYSCONFDIR "/sleep.conf",
90 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
91 "Sleep\0",
92 config_item_table_lookup, items,
93 CONFIG_PARSE_WARN,
94 NULL,
95 NULL);
19adb8a3 96
28ca9c24 97 /* use default values unless set */
c8cd8ca3
LP
98 sc->allow[SLEEP_SUSPEND] = allow_suspend != 0;
99 sc->allow[SLEEP_HIBERNATE] = allow_hibernate != 0;
100 sc->allow[SLEEP_HYBRID_SLEEP] = allow_hybrid_sleep >= 0 ? allow_hybrid_sleep
7874583d 101 : (allow_suspend != 0 && allow_hibernate != 0);
c8cd8ca3 102 sc->allow[SLEEP_SUSPEND_THEN_HIBERNATE] = allow_s2h >= 0 ? allow_s2h
7874583d 103 : (allow_suspend != 0 && allow_hibernate != 0);
28ca9c24 104
c8cd8ca3
LP
105 if (!sc->states[SLEEP_SUSPEND])
106 sc->states[SLEEP_SUSPEND] = strv_new("mem", "standby", "freeze");
107 if (!sc->modes[SLEEP_HIBERNATE])
108 sc->modes[SLEEP_HIBERNATE] = strv_new("platform", "shutdown");
109 if (!sc->states[SLEEP_HIBERNATE])
110 sc->states[SLEEP_HIBERNATE] = strv_new("disk");
111 if (!sc->modes[SLEEP_HYBRID_SLEEP])
112 sc->modes[SLEEP_HYBRID_SLEEP] = strv_new("suspend", "platform", "shutdown");
113 if (!sc->states[SLEEP_HYBRID_SLEEP])
114 sc->states[SLEEP_HYBRID_SLEEP] = strv_new("disk");
28ca9c24 115 if (sc->hibernate_delay_sec == 0)
a077755a 116 sc->hibernate_delay_sec = 2 * USEC_PER_HOUR;
28ca9c24
ZS
117
118 /* ensure values set for all required fields */
c8cd8ca3
LP
119 if (!sc->states[SLEEP_SUSPEND] || !sc->modes[SLEEP_HIBERNATE]
120 || !sc->states[SLEEP_HIBERNATE] || !sc->modes[SLEEP_HYBRID_SLEEP] || !sc->states[SLEEP_HYBRID_SLEEP])
19adb8a3 121 return log_oom();
19adb8a3 122
28ca9c24 123 *ret_sleep_config = TAKE_PTR(sc);
c58493c0 124
19adb8a3
ZJS
125 return 0;
126}
127
746cf898
SS
128/* Get the list of batteries */
129static int battery_enumerator_new(sd_device_enumerator **ret) {
130 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
96d662fa
SS
131 int r;
132
746cf898 133 assert(ret);
96d662fa 134
746cf898
SS
135 r = sd_device_enumerator_new(&e);
136 if (r < 0)
137 return r;
138
139 r = sd_device_enumerator_add_match_subsystem(e, "power_supply", /* match= */ true);
140 if (r < 0)
141 return r;
142
143 r = sd_device_enumerator_add_match_property(e, "POWER_SUPPLY_TYPE", "Battery");
96d662fa 144 if (r < 0)
746cf898
SS
145 return r;
146
147 *ret = TAKE_PTR(e);
96d662fa 148
746cf898
SS
149 return 0;
150}
151
152static int get_capacity_by_name(Hashmap *capacities_by_name, const char *name) {
153 void *p;
154
155 assert(capacities_by_name);
156 assert(name);
157
158 p = hashmap_get(capacities_by_name, name);
159 if (!p)
160 return -ENOENT;
161
162 return PTR_TO_CAPACITY(p);
96d662fa
SS
163}
164
165/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */
746cf898
SS
166static int read_battery_capacity_percentage(sd_device *dev) {
167 const char *power_supply_capacity;
96d662fa
SS
168 int battery_capacity, r;
169
746cf898
SS
170 assert(dev);
171
172 r = sd_device_get_property_value(dev, "POWER_SUPPLY_CAPACITY", &power_supply_capacity);
96d662fa 173 if (r < 0)
746cf898 174 return log_device_debug_errno(dev, r, "Failed to read battery capacity: %m");
96d662fa 175
746cf898 176 r = safe_atoi(power_supply_capacity, &battery_capacity);
96d662fa 177 if (r < 0)
746cf898 178 return log_device_debug_errno(dev, r, "Failed to parse battery capacity: %m");
96d662fa
SS
179
180 if (battery_capacity < 0 || battery_capacity > 100)
746cf898 181 return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity");
96d662fa
SS
182
183 return battery_capacity;
184}
185
746cf898
SS
186/* If battery percentage capacity is less than equal to 5% return success */
187int battery_is_low(void) {
188 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
189 sd_device *dev;
190 int r;
191
192 /* We have not used battery capacity_level since value is set to full
193 * or Normal in case acpi is not working properly. In case of no battery
194 * 0 will be returned and system will be suspended for 1st cycle then hibernated */
195
196 r = battery_enumerator_new(&e);
197 if (r < 0)
198 return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
199
200 FOREACH_DEVICE(e, dev) {
201 r = read_battery_capacity_percentage(dev);
202 if (r < 0) {
203 log_device_debug_errno(dev, r, "Failed to get battery capacity, ignoring: %m");
204 continue;
205 }
206 if (r > BATTERY_LOW_CAPACITY_LEVEL)
207 return false;
208 }
209
210 return true;
211}
212
213/* Store current capacity of each battery before suspension and timestamp */
214int fetch_batteries_capacity_by_name(Hashmap **ret) {
215 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
216 _cleanup_(hashmap_freep) Hashmap *batteries_capacity_by_name = NULL;
217 sd_device *dev;
91ea7ebc
SS
218 int r;
219
91ea7ebc
SS
220 assert(ret);
221
746cf898
SS
222 batteries_capacity_by_name = hashmap_new(&string_hash_ops_free);
223 if (!batteries_capacity_by_name)
224 return log_oom_debug();
225
226 r = battery_enumerator_new(&e);
227 if (r < 0)
228 return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
229
230 FOREACH_DEVICE(e, dev) {
231 _cleanup_free_ char *battery_name_copy = NULL;
232 const char *battery_name;
233 int battery_capacity;
234
235 battery_capacity = r = read_battery_capacity_percentage(dev);
236 if (r < 0) {
237 log_device_debug_errno(dev, r, "Failed to get battery capacity, ignoring: %m");
238 continue;
239 }
240
241 r = sd_device_get_property_value(dev, "POWER_SUPPLY_NAME", &battery_name);
242 if (r < 0) {
243 log_device_debug_errno(dev, r, "Failed to read battery name, ignoring: %m");
244 continue;
245 }
246
247 battery_name_copy = strdup(battery_name);
248 if (!battery_name_copy)
249 return log_oom_debug();
250
251 r = hashmap_put(batteries_capacity_by_name, battery_name_copy, CAPACITY_TO_PTR(battery_capacity));
252 if (r < 0)
253 return log_device_debug_errno(dev, r, "Failed to store battery capacity: %m");
254
255 TAKE_PTR(battery_name_copy);
256 }
257
258 *ret = TAKE_PTR(batteries_capacity_by_name);
259
260 return 0;
261}
262
263/* Read file path and return hash of value in that file */
264static int get_battery_identifier(sd_device *dev, const char *property, struct siphash *state) {
265 const char *x;
266 int r;
267
268 assert(dev);
269 assert(property);
270 assert(state);
271
272 r = sd_device_get_property_value(dev, property, &x);
91ea7ebc 273 if (r == -ENOENT)
746cf898 274 log_device_debug_errno(dev, r, "battery device property %s is unavailable, ignoring: %m", property);
91ea7ebc 275 else if (r < 0)
746cf898
SS
276 return log_device_debug_errno(dev, r, "Failed to read battery device property %s: %m", property);
277 else if (isempty(x))
278 log_device_debug(dev, "battery device property '%s' is null.", property);
91ea7ebc 279 else
746cf898 280 siphash24_compress_string(x, state);
91ea7ebc
SS
281
282 return 0;
283}
284
285/* Read system and battery identifier from specific location and generate hash of it */
746cf898 286static int get_system_battery_identifier_hash(sd_device *dev, uint64_t *ret) {
91ea7ebc
SS
287 struct siphash state;
288 sd_id128_t machine_id, product_id;
289 int r;
290
746cf898
SS
291 assert(ret);
292 assert(dev);
91ea7ebc
SS
293
294 siphash24_init(&state, BATTERY_DISCHARGE_RATE_HASH_KEY.bytes);
746cf898
SS
295
296 get_battery_identifier(dev, "POWER_SUPPLY_MANUFACTURER", &state);
297 get_battery_identifier(dev, "POWER_SUPPLY_MODEL_NAME", &state);
298 get_battery_identifier(dev, "POWER_SUPPLY_SERIAL_NUMBER", &state);
91ea7ebc
SS
299
300 r = sd_id128_get_machine(&machine_id);
301 if (r == -ENOENT)
302 log_debug_errno(r, "machine ID is unavailable: %m");
303 else if (r < 0)
304 return log_debug_errno(r, "Failed to get machine ID: %m");
305 else
306 siphash24_compress(&machine_id, sizeof(sd_id128_t), &state);
307
308 r = id128_get_product(&product_id);
309 if (r == -ENOENT)
310 log_debug_errno(r, "product_id does not exist: %m");
311 else if (r < 0)
312 return log_debug_errno(r, "Failed to get product ID: %m");
313 else
314 siphash24_compress(&product_id, sizeof(sd_id128_t), &state);
315
746cf898 316 *ret = siphash24_finalize(&state);
91ea7ebc
SS
317
318 return 0;
319}
320
321/* battery percentage discharge rate per hour is in range 1-199 then return success */
322static bool battery_discharge_rate_is_valid(int battery_discharge_rate) {
323 return battery_discharge_rate > 0 && battery_discharge_rate < 200;
324}
325
326/* Battery percentage discharge rate per hour is read from specific file. It is stored along with system
327 * and battery identifier hash to maintain the integrity of discharge rate value */
746cf898
SS
328static int get_battery_discharge_rate(sd_device *dev, int *ret) {
329 _cleanup_fclose_ FILE *f = NULL;
330 uint64_t current_hash_id;
91ea7ebc 331 const char *p;
746cf898 332 int r;
91ea7ebc 333
746cf898
SS
334 assert(dev);
335 assert(ret);
91ea7ebc 336
746cf898
SS
337 f = fopen(DISCHARGE_RATE_FILEPATH, "re");
338 if (!f)
339 return log_debug_errno(errno, "Failed to read discharge rate from " DISCHARGE_RATE_FILEPATH ": %m");
91ea7ebc 340
746cf898 341 r = get_system_battery_identifier_hash(dev, &current_hash_id);
91ea7ebc 342 if (r < 0)
746cf898
SS
343 return log_device_debug_errno(dev, r, "Failed to generate system battery identifier hash: %m");
344
345 for (;;) {
346 _cleanup_free_ char *stored_hash_id = NULL, *stored_discharge_rate = NULL, *line = NULL;
347 uint64_t hash_id;
348 int discharge_rate;
349
350 r = read_line(f, LONG_LINE_MAX, &line);
351 if (r < 0)
352 return log_debug_errno(r, "Failed to read discharge rate from " DISCHARGE_RATE_FILEPATH ": %m");
353 if (r == 0)
354 break;
355
356 p = line;
357 r = extract_many_words(&p, NULL, 0, &stored_hash_id, &stored_discharge_rate, NULL);
358 if (r < 0)
359 return log_debug_errno(r, "Failed to parse hash_id and discharge_rate read from " DISCHARGE_RATE_FILEPATH ": %m");
360 if (r != 2)
361 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid number of items fetched from " DISCHARGE_RATE_FILEPATH);
362
363 r = safe_atou64(stored_hash_id, &hash_id);
364 if (r < 0)
365 return log_debug_errno(r, "Failed to parse hash ID read from " DISCHARGE_RATE_FILEPATH " location: %m");
366
367 if (current_hash_id != hash_id)
368 /* matching device not found, move to next line */
369 continue;
370
371 r = safe_atoi(stored_discharge_rate, &discharge_rate);
372 if (r < 0)
373 return log_device_debug_errno(dev, r, "Failed to parse discharge rate read from " DISCHARGE_RATE_FILEPATH ": %m");
374
375 if (!battery_discharge_rate_is_valid(discharge_rate))
376 return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m");
377
378 *ret = discharge_rate;
379 return 0; /* matching device found, exit iteration */
380 }
381
382 return -ENOENT;
383}
91ea7ebc 384
746cf898
SS
385/* Write battery percentage discharge rate per hour along with system and battery identifier hash to file */
386static int put_battery_discharge_rate(int estimated_battery_discharge_rate, uint64_t system_hash_id, bool trunc) {
387 int r;
91ea7ebc 388
746cf898
SS
389 if (!battery_discharge_rate_is_valid(estimated_battery_discharge_rate))
390 return log_debug_errno(SYNTHETIC_ERRNO(ERANGE),
391 "Invalid battery discharge rate %d%% per hour: %m",
392 estimated_battery_discharge_rate);
91ea7ebc 393
746cf898
SS
394 r = write_string_filef(
395 DISCHARGE_RATE_FILEPATH,
396 WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MKDIR_0755 | (trunc ? WRITE_STRING_FILE_TRUNCATE : 0),
397 "%"PRIu64" %d",
398 system_hash_id,
399 estimated_battery_discharge_rate);
91ea7ebc 400 if (r < 0)
746cf898 401 return log_debug_errno(r, "Failed to update %s: %m", DISCHARGE_RATE_FILEPATH);
91ea7ebc 402
746cf898 403 log_debug("Estimated discharge rate %d%% per hour successfully saved to %s", estimated_battery_discharge_rate, DISCHARGE_RATE_FILEPATH);
91ea7ebc 404
746cf898 405 return 0;
91ea7ebc
SS
406}
407
746cf898
SS
408/* Estimate battery discharge rate using stored previous and current capacity over timestamp difference */
409int estimate_battery_discharge_rate_per_hour(
410 Hashmap *last_capacity,
411 Hashmap *current_capacity,
412 usec_t before_timestamp,
413 usec_t after_timestamp) {
414
415 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
416 sd_device *dev;
417 bool trunc = true;
91ea7ebc
SS
418 int r;
419
746cf898
SS
420 assert(last_capacity);
421 assert(current_capacity);
422 assert(before_timestamp < after_timestamp);
91ea7ebc 423
746cf898 424 r = battery_enumerator_new(&e);
91ea7ebc 425 if (r < 0)
746cf898
SS
426 return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
427
428 FOREACH_DEVICE(e, dev) {
429 int battery_last_capacity, battery_current_capacity, battery_discharge_rate;
430 const char *battery_name;
431 uint64_t system_hash_id;
432
433 r = sd_device_get_property_value(dev, "POWER_SUPPLY_NAME", &battery_name);
434 if (r < 0) {
435 log_device_debug_errno(dev, r, "Failed to read battery name, ignoring: %m");
436 continue;
437 }
438
439 battery_last_capacity = get_capacity_by_name(last_capacity, battery_name);
440 if (battery_last_capacity < 0)
441 continue;
442
443 battery_current_capacity = get_capacity_by_name(current_capacity, battery_name);
444 if (battery_current_capacity < 0)
445 continue;
446
447 if (battery_current_capacity >= battery_last_capacity) {
448 log_device_debug(dev, "Battery was not discharged during suspension");
449 continue;
450 }
451
452 r = get_system_battery_identifier_hash(dev, &system_hash_id);
453 if (r < 0)
454 return log_device_debug_errno(dev, r, "Failed to generate system battery identifier hash: %m");
455
456 log_device_debug(dev,
457 "%d%% was discharged in %s. Estimating discharge rate...",
458 battery_last_capacity - battery_current_capacity,
459 FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_SEC));
460
461 battery_discharge_rate = (battery_last_capacity - battery_current_capacity) * USEC_PER_HOUR / (after_timestamp - before_timestamp);
462 r = put_battery_discharge_rate(battery_discharge_rate, system_hash_id, trunc);
463 if (r < 0)
464 log_device_warning_errno(dev, r, "Failed to update battery discharge rate, ignoring: %m");
465 else
466 trunc = false;
467 }
91ea7ebc 468
746cf898
SS
469 return 0;
470}
471
472/* calculate the suspend interval for each battery and then return the sum of it */
473int get_total_suspend_interval(Hashmap *last_capacity, usec_t *ret) {
474 _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
475 usec_t total_suspend_interval = 0;
476 sd_device *dev;
477 int r;
478
479 assert(last_capacity);
480 assert(ret);
481
482 r = battery_enumerator_new(&e);
91ea7ebc 483 if (r < 0)
746cf898
SS
484 return log_debug_errno(r, "Failed to initialize battery enumerator: %m");
485
486 FOREACH_DEVICE(e, dev) {
487 int battery_last_capacity, previous_discharge_rate = 0;
488 const char *battery_name;
489 usec_t suspend_interval;
490
491 r = sd_device_get_property_value(dev, "POWER_SUPPLY_NAME", &battery_name);
492 if (r < 0) {
493 log_device_debug_errno(dev, r, "Failed to read battery name, ignoring: %m");
494 continue;
495 }
496
497 battery_last_capacity = PTR_TO_CAPACITY(hashmap_get(last_capacity, battery_name));
498 if (battery_last_capacity <= 0)
499 continue;
500
501 r = get_battery_discharge_rate(dev, &previous_discharge_rate);
502 if (r < 0) {
503 log_device_debug_errno(dev, r, "Failed to get discharge rate, ignoring: %m");
504 continue;
505 }
506
507 if (previous_discharge_rate == 0)
508 continue;
509
510 if (battery_last_capacity * 2 <= previous_discharge_rate) {
511 log_device_debug(dev, "Current battery capacity percentage too low compared to discharge rate");
512 continue;
513 }
514 suspend_interval = battery_last_capacity * USEC_PER_HOUR / previous_discharge_rate;
515
516 total_suspend_interval = usec_add(total_suspend_interval, suspend_interval);
517 }
518 /* The previous discharge rate is stored in per hour basis so converted to minutes.
519 * Subtracted 30 minutes from the result to keep a buffer of 30 minutes before battery gets critical */
520 total_suspend_interval = usec_sub_unsigned(total_suspend_interval, 30 * USEC_PER_MINUTE);
521 if (total_suspend_interval == 0)
522 return -ENOENT;
523
524 *ret = total_suspend_interval;
91ea7ebc 525
91ea7ebc
SS
526 return 0;
527}
528
19adb8a3 529int can_sleep_state(char **types) {
434fef6d 530 _cleanup_free_ char *text = NULL;
19adb8a3 531 int r;
19adb8a3
ZJS
532
533 if (strv_isempty(types))
534 return true;
535
536 /* If /sys is read-only we cannot sleep */
c0d8fbfa
LP
537 if (access("/sys/power/state", W_OK) < 0) {
538 log_debug_errno(errno, "/sys/power/state is not writable, cannot sleep: %m");
19adb8a3 539 return false;
c0d8fbfa 540 }
19adb8a3 541
434fef6d 542 r = read_one_line_file("/sys/power/state", &text);
c0d8fbfa
LP
543 if (r < 0) {
544 log_debug_errno(r, "Failed to read /sys/power/state, cannot sleep: %m");
19adb8a3 545 return false;
c0d8fbfa 546 }
19adb8a3 547
434fef6d
ZJS
548 const char *found;
549 r = string_contains_word_strv(text, NULL, types, &found);
550 if (r < 0)
551 return log_debug_errno(r, "Failed to parse /sys/power/state: %m");
552 if (r > 0)
553 log_debug("Sleep mode \"%s\" is supported by the kernel.", found);
554 else if (DEBUG_LOGGING) {
c550cb7f
ZJS
555 _cleanup_free_ char *t = strv_join(types, "/");
556 log_debug("Sleep mode %s not supported by the kernel, sorry.", strnull(t));
557 }
434fef6d 558 return r;
19adb8a3
ZJS
559}
560
561int can_sleep_disk(char **types) {
434fef6d 562 _cleanup_free_ char *text = NULL;
19adb8a3 563 int r;
19adb8a3
ZJS
564
565 if (strv_isempty(types))
566 return true;
567
568 /* If /sys is read-only we cannot sleep */
7474f15b
LP
569 if (access("/sys/power/disk", W_OK) < 0) {
570 log_debug_errno(errno, "/sys/power/disk is not writable: %m");
19adb8a3 571 return false;
7474f15b 572 }
19adb8a3 573
434fef6d 574 r = read_one_line_file("/sys/power/disk", &text);
7474f15b
LP
575 if (r < 0) {
576 log_debug_errno(r, "Couldn't read /sys/power/disk: %m");
19adb8a3 577 return false;
7474f15b 578 }
19adb8a3 579
434fef6d
ZJS
580 for (const char *p = text;;) {
581 _cleanup_free_ char *word = NULL;
19adb8a3 582
434fef6d
ZJS
583 r = extract_first_word(&p, &word, NULL, 0);
584 if (r < 0)
585 return log_debug_errno(r, "Failed to parse /sys/power/disk: %m");
586 if (r == 0)
587 break;
19adb8a3 588
434fef6d
ZJS
589 char *s = word;
590 size_t l = strlen(s);
591 if (s[0] == '[' && s[l-1] == ']') {
592 s[l-1] = '\0';
593 s++;
594 }
595
596 if (strv_contains(types, s)) {
597 log_debug("Disk sleep mode \"%s\" is supported by the kernel.", s);
598 return true;
19adb8a3
ZJS
599 }
600 }
601
434fef6d
ZJS
602 if (DEBUG_LOGGING) {
603 _cleanup_free_ char *t = strv_join(types, "/");
604 log_debug("Disk sleep mode %s not supported by the kernel, sorry.", strnull(t));
605 }
19adb8a3
ZJS
606 return false;
607}
608
69ab8088
ZJS
609#define HIBERNATION_SWAP_THRESHOLD 0.98
610
7bdf56a2 611SwapEntry* swap_entry_free(SwapEntry *se) {
88bc86fc
ZS
612 if (!se)
613 return NULL;
614
615 free(se->device);
616 free(se->type);
617
618 return mfree(se);
619}
620
7bdf56a2
ZS
621HibernateLocation* hibernate_location_free(HibernateLocation *hl) {
622 if (!hl)
623 return NULL;
624
625 swap_entry_free(hl->swap);
7bdf56a2
ZS
626
627 return mfree(hl);
628}
629
52133271 630static int swap_device_to_device_id(const SwapEntry *swap, dev_t *ret_dev) {
7bdf56a2 631 struct stat sb;
7bdf56a2
ZS
632 int r;
633
634 assert(swap);
635 assert(swap->device);
636 assert(swap->type);
637
e9f0c5d0 638 r = stat(swap->device, &sb);
7bdf56a2 639 if (r < 0)
6f9120ad 640 return -errno;
7bdf56a2 641
52133271 642 if (streq(swap->type, "partition")) {
e9f0c5d0 643 if (!S_ISBLK(sb.st_mode))
52133271 644 return -ENOTBLK;
6f9120ad 645
e9f0c5d0
ZJS
646 *ret_dev = sb.st_rdev;
647 return 0;
6f9120ad 648 }
7bdf56a2 649
6f9120ad 650 return get_block_device(swap->device, ret_dev);
7bdf56a2
ZS
651}
652
52133271 653/*
162392b7 654 * Attempt to calculate the swap file offset on supported filesystems. On unsupported
8efc2c16 655 * filesystems, a debug message is logged and ret_offset is set to UINT64_MAX.
52133271 656 */
7bdf56a2
ZS
657static int calculate_swap_file_offset(const SwapEntry *swap, uint64_t *ret_offset) {
658 _cleanup_close_ int fd = -1;
659 _cleanup_free_ struct fiemap *fiemap = NULL;
660 struct stat sb;
65ddc2c5 661 int r;
7bdf56a2
ZS
662
663 assert(swap);
664 assert(swap->device);
665 assert(streq(swap->type, "file"));
666
667 fd = open(swap->device, O_RDONLY|O_CLOEXEC|O_NOCTTY);
e97c3691 668 if (fd < 0)
c02540dc 669 return log_debug_errno(errno, "Failed to open swap file %s to determine on-disk offset: %m", swap->device);
7bdf56a2 670
e97c3691 671 if (fstat(fd, &sb) < 0)
c02540dc 672 return log_debug_errno(errno, "Failed to stat %s: %m", swap->device);
7bdf56a2 673
65ddc2c5
ZJS
674 r = fd_is_fs_type(fd, BTRFS_SUPER_MAGIC);
675 if (r < 0)
676 return log_debug_errno(r, "Error checking %s for Btrfs filesystem: %m", swap->device);
677 if (r > 0) {
8efc2c16
ZJS
678 log_debug("%s: detection of swap file offset on Btrfs is not supported", swap->device);
679 *ret_offset = UINT64_MAX;
7bdf56a2
ZS
680 return 0;
681 }
682
683 r = read_fiemap(fd, &fiemap);
684 if (r < 0)
685 return log_debug_errno(r, "Unable to read extent map for '%s': %m", swap->device);
686
687 *ret_offset = fiemap->fm_extents[0].fe_physical / page_size();
7bdf56a2
ZS
688 return 0;
689}
88bc86fc 690
52133271
ZS
691static int read_resume_files(dev_t *ret_resume, uint64_t *ret_resume_offset) {
692 _cleanup_free_ char *resume_str = NULL, *resume_offset_str = NULL;
7bdf56a2 693 uint64_t resume_offset = 0;
c02540dc 694 dev_t resume;
7bdf56a2
ZS
695 int r;
696
52133271 697 r = read_one_line_file("/sys/power/resume", &resume_str);
7bdf56a2 698 if (r < 0)
5021735f 699 return log_debug_errno(r, "Error reading /sys/power/resume: %m");
7bdf56a2 700
7176f06c 701 r = parse_devnum(resume_str, &resume);
52133271
ZS
702 if (r < 0)
703 return log_debug_errno(r, "Error parsing /sys/power/resume device: %s: %m", resume_str);
704
7bdf56a2 705 r = read_one_line_file("/sys/power/resume_offset", &resume_offset_str);
b72571e0 706 if (r == -ENOENT)
c02540dc 707 log_debug_errno(r, "Kernel does not support resume_offset; swap file offset detection will be skipped.");
b72571e0 708 else if (r < 0)
5021735f 709 return log_debug_errno(r, "Error reading /sys/power/resume_offset: %m");
b72571e0 710 else {
7bdf56a2
ZS
711 r = safe_atou64(resume_offset_str, &resume_offset);
712 if (r < 0)
c02540dc 713 return log_debug_errno(r, "Failed to parse value in /sys/power/resume_offset \"%s\": %m", resume_offset_str);
7bdf56a2
ZS
714 }
715
8f817cb8
ZJS
716 if (resume_offset > 0 && resume == 0)
717 log_debug("Warning: found /sys/power/resume_offset==%" PRIu64 ", but /sys/power/resume unset. Misconfiguration?",
5021735f 718 resume_offset);
7bdf56a2 719
52133271 720 *ret_resume = resume;
7bdf56a2
ZS
721 *ret_resume_offset = resume_offset;
722
723 return 0;
724}
725
52133271
ZS
726/*
727 * Determine if the HibernateLocation matches the resume= (device) and resume_offset= (file).
728 */
729static bool location_is_resume_device(const HibernateLocation *location, dev_t sys_resume, uint64_t sys_offset) {
730 if (!location)
731 return false;
732
8efc2c16
ZJS
733 return sys_resume > 0 &&
734 sys_resume == location->devno &&
735 (sys_offset == location->offset || (sys_offset > 0 && location->offset == UINT64_MAX));
7bdf56a2
ZS
736}
737
738/*
739 * Attempt to find the hibernation location by parsing /proc/swaps, /sys/power/resume, and
740 * /sys/power/resume_offset.
741 *
742 * Returns:
52133271
ZS
743 * 1 - Values are set in /sys/power/resume and /sys/power/resume_offset.
744 * ret_hibernate_location will represent matching /proc/swap entry if identified or NULL if not.
745 *
746 * 0 - No values are set in /sys/power/resume and /sys/power/resume_offset.
747 ret_hibernate_location will represent the highest priority swap with most remaining space discovered in /proc/swaps.
748 *
749 * Negative value in the case of error.
7bdf56a2
ZS
750 */
751int find_hibernate_location(HibernateLocation **ret_hibernate_location) {
b72571e0 752 _cleanup_fclose_ FILE *f = NULL;
7bdf56a2 753 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
aff81b18 754 dev_t sys_resume = 0; /* Unnecessary initialization to appease gcc */
7bdf56a2 755 uint64_t sys_offset = 0;
8efc2c16 756 bool resume_match = false;
7bdf56a2
ZS
757 int r;
758
759 /* read the /sys/power/resume & /sys/power/resume_offset values */
760 r = read_resume_files(&sys_resume, &sys_offset);
761 if (r < 0)
762 return r;
9fb3675e 763
c8a202b7 764 f = fopen("/proc/swaps", "re");
9fb3675e 765 if (!f) {
c02540dc
LP
766 log_debug_errno(errno, "Failed to open /proc/swaps: %m");
767 return errno == ENOENT ? -EOPNOTSUPP : -errno; /* Convert swap not supported to a recognizable error */
9fb3675e 768 }
69ab8088 769
9fb3675e 770 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
8efc2c16 771 for (unsigned i = 1;; i++) {
88bc86fc 772 _cleanup_(swap_entry_freep) SwapEntry *swap = NULL;
7bdf56a2 773 uint64_t swap_offset = 0;
9fb3675e
ZJS
774 int k;
775
88bc86fc
ZS
776 swap = new0(SwapEntry, 1);
777 if (!swap)
c02540dc 778 return -ENOMEM;
88bc86fc 779
9fb3675e 780 k = fscanf(f,
88bc86fc
ZS
781 "%ms " /* device/file */
782 "%ms " /* type of swap */
783 "%" PRIu64 /* swap size */
784 "%" PRIu64 /* used */
785 "%i\n", /* priority */
786 &swap->device, &swap->type, &swap->size, &swap->used, &swap->priority);
3dea6886
LP
787 if (k == EOF)
788 break;
88bc86fc 789 if (k != 5) {
c02540dc 790 log_debug("Failed to parse /proc/swaps:%u, ignoring", i);
9fb3675e
ZJS
791 continue;
792 }
793
88bc86fc 794 if (streq(swap->type, "file")) {
88bc86fc 795 if (endswith(swap->device, "\\040(deleted)")) {
c02540dc 796 log_debug("Ignoring deleted swap file '%s'.", swap->device);
411ae92b
AJ
797 continue;
798 }
799
7bdf56a2
ZS
800 r = calculate_swap_file_offset(swap, &swap_offset);
801 if (r < 0)
802 return r;
52133271 803
88bc86fc 804 } else if (streq(swap->type, "partition")) {
411ae92b 805 const char *fn;
3dea6886 806
88bc86fc 807 fn = path_startswith(swap->device, "/dev/");
411ae92b 808 if (fn && startswith(fn, "zram")) {
8efc2c16 809 log_debug("%s: ignoring zram swap", swap->device);
411ae92b
AJ
810 continue;
811 }
8efc2c16 812
7bdf56a2 813 } else {
8efc2c16 814 log_debug("%s: swap type %s is unsupported for hibernation, ignoring", swap->device, swap->type);
7bdf56a2 815 continue;
9fb3675e 816 }
3dea6886 817
7bdf56a2 818 /* prefer resume device or highest priority swap with most remaining space */
936a7cb6
E
819 if (sys_resume == 0) {
820 if (hibernate_location && swap->priority < hibernate_location->swap->priority) {
821 log_debug("%s: ignoring device with lower priority", swap->device);
822 continue;
823 }
824 if (hibernate_location &&
825 (swap->priority == hibernate_location->swap->priority
826 && swap->size - swap->used < hibernate_location->swap->size - hibernate_location->swap->used)) {
827 log_debug("%s: ignoring device with lower usable space", swap->device);
828 continue;
829 }
8efc2c16 830 }
7bdf56a2 831
8efc2c16
ZJS
832 dev_t swap_device;
833 r = swap_device_to_device_id(swap, &swap_device);
834 if (r < 0)
c02540dc 835 return log_debug_errno(r, "%s: failed to query device number: %m", swap->device);
d161680e
LP
836 if (swap_device == 0)
837 return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "%s: not backed by block device.", swap->device);
7bdf56a2 838
8efc2c16
ZJS
839 hibernate_location = hibernate_location_free(hibernate_location);
840 hibernate_location = new(HibernateLocation, 1);
841 if (!hibernate_location)
c02540dc 842 return -ENOMEM;
7bdf56a2 843
8efc2c16
ZJS
844 *hibernate_location = (HibernateLocation) {
845 .devno = swap_device,
846 .offset = swap_offset,
847 .swap = TAKE_PTR(swap),
848 };
7bdf56a2 849
8efc2c16
ZJS
850 /* if the swap is the resume device, stop the loop */
851 if (location_is_resume_device(hibernate_location, sys_resume, sys_offset)) {
852 log_debug("%s: device matches configured resume settings.", hibernate_location->swap->device);
853 resume_match = true;
854 break;
88bc86fc 855 }
8efc2c16
ZJS
856
857 log_debug("%s: is a candidate device.", hibernate_location->swap->device);
69ab8088
ZJS
858 }
859
8efc2c16
ZJS
860 /* We found nothing at all */
861 if (!hibernate_location)
862 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
863 "No possible swap partitions or files suitable for hibernation were found in /proc/swaps.");
52133271 864
8efc2c16
ZJS
865 /* resume= is set but a matching /proc/swaps entry was not found */
866 if (sys_resume != 0 && !resume_match)
867 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS),
868 "No swap partitions or files matching resume config were found in /proc/swaps.");
52133271 869
8efc2c16
ZJS
870 if (hibernate_location->offset == UINT64_MAX) {
871 if (sys_offset == 0)
872 return log_debug_errno(SYNTHETIC_ERRNO(ENOSYS), "Offset detection failed and /sys/power/resume_offset is not set.");
7bdf56a2 873
8efc2c16
ZJS
874 hibernate_location->offset = sys_offset;
875 }
7bdf56a2 876
52133271
ZS
877 if (resume_match)
878 log_debug("Hibernation will attempt to use swap entry with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
879 hibernate_location->swap->device, major(hibernate_location->devno), minor(hibernate_location->devno),
880 hibernate_location->offset, hibernate_location->swap->priority);
881 else
8efc2c16 882 log_debug("/sys/power/resume is not configured; attempting to hibernate with path: %s, device: %u:%u, offset: %" PRIu64 ", priority: %i",
52133271
ZS
883 hibernate_location->swap->device, major(hibernate_location->devno), minor(hibernate_location->devno),
884 hibernate_location->offset, hibernate_location->swap->priority);
88bc86fc 885
7bdf56a2 886 *ret_hibernate_location = TAKE_PTR(hibernate_location);
88bc86fc 887
52133271 888 if (resume_match)
7bdf56a2 889 return 1;
88bc86fc
ZS
890
891 return 0;
9fb3675e
ZJS
892}
893
4638cd39 894static bool enough_swap_for_hibernation(void) {
9fb3675e 895 _cleanup_free_ char *active = NULL;
7bdf56a2 896 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
39883f62 897 unsigned long long act = 0;
9fb3675e
ZJS
898 int r;
899
490d20e6
VV
900 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
901 return true;
902
7bdf56a2 903 r = find_hibernate_location(&hibernate_location);
9fb3675e 904 if (r < 0)
69ab8088 905 return false;
69ab8088 906
52133271
ZS
907 /* If /sys/power/{resume,resume_offset} is configured but a matching entry
908 * could not be identified in /proc/swaps, user is likely using Btrfs with a swapfile;
909 * return true and let the system attempt hibernation.
910 */
911 if (r > 0 && !hibernate_location) {
912 log_debug("Unable to determine remaining swap space; hibernation may fail");
913 return true;
914 }
915
916 if (!hibernate_location)
917 return false;
918
c4cd1d4d 919 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
69ab8088 920 if (r < 0) {
904865b8 921 log_debug_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
69ab8088
ZJS
922 return false;
923 }
924
925 r = safe_atollu(active, &act);
926 if (r < 0) {
904865b8 927 log_debug_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", active);
69ab8088
ZJS
928 return false;
929 }
930
7bdf56a2 931 r = act <= (hibernate_location->swap->size - hibernate_location->swap->used) * HIBERNATION_SWAP_THRESHOLD;
88bc86fc 932 log_debug("%s swap for hibernation, Active(anon)=%llu kB, size=%" PRIu64 " kB, used=%" PRIu64 " kB, threshold=%.2g%%",
7bdf56a2 933 r ? "Enough" : "Not enough", act, hibernate_location->swap->size, hibernate_location->swap->used, 100*HIBERNATION_SWAP_THRESHOLD);
69ab8088
ZJS
934
935 return r;
936}
937
17c40b3a
ML
938int read_fiemap(int fd, struct fiemap **ret) {
939 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
17c40b3a
ML
940 struct stat statinfo;
941 uint32_t result_extents = 0;
942 uint64_t fiemap_start = 0, fiemap_length;
6524f1a8 943 const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
17c40b3a
ML
944
945 if (fstat(fd, &statinfo) < 0)
946 return log_debug_errno(errno, "Cannot determine file size: %m");
947 if (!S_ISREG(statinfo.st_mode))
948 return -ENOTTY;
949 fiemap_length = statinfo.st_size;
950
6524f1a8
ZJS
951 /* Zero this out in case we run on a file with no extents */
952 fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
953 if (!fiemap)
954 return -ENOMEM;
955
6524f1a8 956 result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
957 if (!result_fiemap)
958 return -ENOMEM;
959
960 /* XFS filesystem has incorrect implementation of fiemap ioctl and
961 * returns extents for only one block-group at a time, so we need
962 * to handle it manually, starting the next fiemap call from the end
963 * of the last extent
964 */
965 while (fiemap_start < fiemap_length) {
966 *fiemap = (struct fiemap) {
967 .fm_start = fiemap_start,
968 .fm_length = fiemap_length,
969 .fm_flags = FIEMAP_FLAG_SYNC,
970 };
971
972 /* Find out how many extents there are */
973 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
974 return log_debug_errno(errno, "Failed to read extents: %m");
975
976 /* Nothing to process */
977 if (fiemap->fm_mapped_extents == 0)
978 break;
979
6524f1a8
ZJS
980 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
981 * the extents for the whole file. Add space for the initial struct fiemap. */
319a4f4b 982 if (!greedy_realloc0((void**) &fiemap, n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
983 return -ENOMEM;
984
985 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
986 fiemap->fm_mapped_extents = 0;
987
988 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
989 return log_debug_errno(errno, "Failed to read extents: %m");
990
6524f1a8 991 /* Resize result_fiemap to allow us to copy in the extents */
319a4f4b 992 if (!greedy_realloc((void**) &result_fiemap,
6524f1a8 993 n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
994 return -ENOMEM;
995
996 memcpy(result_fiemap->fm_extents + result_extents,
997 fiemap->fm_extents,
998 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
999
1000 result_extents += fiemap->fm_mapped_extents;
1001
1002 /* Highly unlikely that it is zero */
6524f1a8 1003 if (_likely_(fiemap->fm_mapped_extents > 0)) {
17c40b3a
ML
1004 uint32_t i = fiemap->fm_mapped_extents - 1;
1005
1006 fiemap_start = fiemap->fm_extents[i].fe_logical +
1007 fiemap->fm_extents[i].fe_length;
1008
1009 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
1010 break;
1011 }
1012 }
1013
1014 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
1015 result_fiemap->fm_mapped_extents = result_extents;
1016 *ret = TAKE_PTR(result_fiemap);
1017 return 0;
1018}
1019
c8cd8ca3 1020static int can_sleep_internal(const SleepConfig *sleep_config, SleepOperation operation, bool check_allowed);
e8f1d00d 1021
28ca9c24 1022static bool can_s2h(const SleepConfig *sleep_config) {
c8cd8ca3
LP
1023
1024 static const SleepOperation operations[] = {
1025 SLEEP_SUSPEND,
1026 SLEEP_HIBERNATE,
1027 };
1028
c58493c0
ML
1029 int r;
1030
1bbbefe7 1031 if (!clock_supported(CLOCK_BOOTTIME_ALARM)) {
c732e879 1032 log_debug("CLOCK_BOOTTIME_ALARM is not supported.");
c58493c0
ML
1033 return false;
1034 }
1035
c8cd8ca3
LP
1036 for (size_t i = 0; i < ELEMENTSOF(operations); i++) {
1037 r = can_sleep_internal(sleep_config, operations[i], false);
887b2019 1038 if (IN_SET(r, 0, -ENOSPC)) {
c8cd8ca3 1039 log_debug("Unable to %s system.", sleep_operation_to_string(operations[i]));
c863dc05
ZJS
1040 return false;
1041 }
b71c9758 1042 if (r < 0)
c8cd8ca3 1043 return log_debug_errno(r, "Failed to check if %s is possible: %m", sleep_operation_to_string(operations[i]));
c58493c0
ML
1044 }
1045
1046 return true;
1047}
1048
c8cd8ca3
LP
1049static int can_sleep_internal(
1050 const SleepConfig *sleep_config,
1051 SleepOperation operation,
1052 bool check_allowed) {
c58493c0 1053
c8cd8ca3
LP
1054 assert(operation >= 0);
1055 assert(operation < _SLEEP_OPERATION_MAX);
19adb8a3 1056
c8cd8ca3
LP
1057 if (check_allowed && !sleep_config->allow[operation]) {
1058 log_debug("Sleep mode \"%s\" is disabled by configuration.", sleep_operation_to_string(operation));
e8f1d00d
ZJS
1059 return false;
1060 }
1061
c8cd8ca3 1062 if (operation == SLEEP_SUSPEND_THEN_HIBERNATE)
28ca9c24 1063 return can_s2h(sleep_config);
e8f1d00d 1064
61dc8481
LP
1065 if (can_sleep_state(sleep_config->states[operation]) <= 0 ||
1066 can_sleep_disk(sleep_config->modes[operation]) <= 0)
69ab8088
ZJS
1067 return false;
1068
c8cd8ca3 1069 if (operation == SLEEP_SUSPEND)
b71c9758
ZJS
1070 return true;
1071
4638cd39 1072 if (!enough_swap_for_hibernation())
b71c9758
ZJS
1073 return -ENOSPC;
1074
1075 return true;
19adb8a3 1076}
e8f1d00d 1077
c8cd8ca3 1078int can_sleep(SleepOperation operation) {
28ca9c24
ZS
1079 _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
1080 int r;
1081
1082 r = parse_sleep_config(&sleep_config);
1083 if (r < 0)
1084 return r;
1085
c8cd8ca3 1086 return can_sleep_internal(sleep_config, operation, true);
28ca9c24
ZS
1087}
1088
1326de01 1089SleepConfig* free_sleep_config(SleepConfig *sc) {
28ca9c24 1090 if (!sc)
1326de01 1091 return NULL;
28ca9c24 1092
c8cd8ca3
LP
1093 for (SleepOperation i = 0; i < _SLEEP_OPERATION_MAX; i++) {
1094 strv_free(sc->modes[i]);
1095 strv_free(sc->states[i]);
1096 }
28ca9c24 1097
1326de01 1098 return mfree(sc);
e8f1d00d 1099}
be2a4b0d
LP
1100
1101static const char* const sleep_operation_table[_SLEEP_OPERATION_MAX] = {
1102 [SLEEP_SUSPEND] = "suspend",
1103 [SLEEP_HIBERNATE] = "hibernate",
1104 [SLEEP_HYBRID_SLEEP] = "hybrid-sleep",
1105 [SLEEP_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate",
1106};
1107
1108DEFINE_STRING_TABLE_LOOKUP(sleep_operation, SleepOperation);