]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
use FOREACH_ELEMENT
[thirdparty/systemd.git] / src / shared / sleep-config.c
CommitLineData
54d7fcc6
MY
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <unistd.h>
4
5#include "alloc-util.h"
6#include "conf-parser.h"
7#include "constants.h"
8#include "device-util.h"
9#include "devnum-util.h"
10#include "errno-util.h"
11#include "fd-util.h"
12#include "fileio.h"
13#include "hibernate-util.h"
14#include "log.h"
15#include "macro.h"
16#include "path-util.h"
17#include "sleep-config.h"
18#include "stat-util.h"
19#include "stdio-util.h"
20#include "string-table.h"
21#include "string-util.h"
22#include "strv.h"
23#include "time-util.h"
24
23577f44
MY
25#define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR)
26
087a25d2
MY
27static const char* const sleep_operation_table[_SLEEP_OPERATION_MAX] = {
28 [SLEEP_SUSPEND] = "suspend",
29 [SLEEP_HIBERNATE] = "hibernate",
30 [SLEEP_HYBRID_SLEEP] = "hybrid-sleep",
31 [SLEEP_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate",
32};
33
34DEFINE_STRING_TABLE_LOOKUP(sleep_operation, SleepOperation);
35
1f82c21d
MY
36static char* const* const sleep_default_state_table[_SLEEP_OPERATION_CONFIG_MAX] = {
37 [SLEEP_SUSPEND] = STRV_MAKE("mem", "standby", "freeze"),
38 [SLEEP_HIBERNATE] = STRV_MAKE("disk"),
39 [SLEEP_HYBRID_SLEEP] = STRV_MAKE("disk"),
40};
41
42static char* const* const sleep_default_mode_table[_SLEEP_OPERATION_CONFIG_MAX] = {
43 /* Not used by SLEEP_SUSPEND */
44 [SLEEP_HIBERNATE] = STRV_MAKE("platform", "shutdown"),
828ad304 45 [SLEEP_HYBRID_SLEEP] = STRV_MAKE("suspend"),
1f82c21d
MY
46};
47
087a25d2
MY
48SleepConfig* sleep_config_free(SleepConfig *sc) {
49 if (!sc)
50 return NULL;
51
52 for (SleepOperation i = 0; i < _SLEEP_OPERATION_CONFIG_MAX; i++) {
087a25d2 53 strv_free(sc->states[i]);
1f82c21d 54 strv_free(sc->modes[i]);
087a25d2
MY
55 }
56
a2124b35
MY
57 strv_free(sc->mem_modes);
58
087a25d2
MY
59 return mfree(sc);
60}
61
77bd3938
MY
62static int config_parse_sleep_mode(
63 const char *unit,
64 const char *filename,
65 unsigned line,
66 const char *section,
67 unsigned section_line,
68 const char *lvalue,
69 int ltype,
70 const char *rvalue,
71 void *data,
72 void *userdata) {
73
77bd3938 74 char ***sv = ASSERT_PTR(data);
a6fe020a 75 _cleanup_strv_free_ char **modes = NULL;
77bd3938
MY
76 int r;
77
78 assert(filename);
79 assert(lvalue);
80 assert(rvalue);
81
82 if (isempty(rvalue)) {
83 modes = strv_new(NULL);
84 if (!modes)
85 return log_oom();
86 } else {
87 r = strv_split_full(&modes, rvalue, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
88 if (r < 0)
89 return log_oom();
90 }
91
a6fe020a 92 return strv_free_and_replace(*sv, modes);
77bd3938
MY
93}
94
1f82c21d
MY
95static void sleep_config_validate_state_and_mode(SleepConfig *sc) {
96 assert(sc);
97
98 /* So we should really not allow setting SuspendState= to 'disk', which means hibernation. We have
99 * SLEEP_HIBERNATE for proper hibernation support, which includes checks for resume support (through
100 * EFI variable or resume= kernel command line option). It's simply not sensible to call the suspend
101 * operation but eventually do an unsafe hibernation. */
102 if (strv_contains(sc->states[SLEEP_SUSPEND], "disk")) {
103 strv_remove(sc->states[SLEEP_SUSPEND], "disk");
104 log_warning("Sleep state 'disk' is not supported by operation %s, ignoring.",
105 sleep_operation_to_string(SLEEP_SUSPEND));
106 }
107 assert(!sc->modes[SLEEP_SUSPEND]);
108
109 /* People should use hybrid-sleep instead of setting HibernateMode=suspend. Warn about it but don't
110 * drop it in this case. */
111 if (strv_contains(sc->modes[SLEEP_HIBERNATE], "suspend"))
112 log_warning("Sleep mode 'suspend' should not be used by operation %s. Please use %s instead.",
113 sleep_operation_to_string(SLEEP_HIBERNATE), sleep_operation_to_string(SLEEP_HYBRID_SLEEP));
1f82c21d
MY
114}
115
087a25d2
MY
116int parse_sleep_config(SleepConfig **ret) {
117 _cleanup_(sleep_config_freep) SleepConfig *sc = NULL;
118 int allow_suspend = -1, allow_hibernate = -1, allow_s2h = -1, allow_hybrid_sleep = -1;
119
120 assert(ret);
54d7fcc6
MY
121
122 sc = new(SleepConfig, 1);
123 if (!sc)
124 return log_oom();
125
126 *sc = (SleepConfig) {
127 .hibernate_delay_usec = USEC_INFINITY,
128 };
129
130 const ConfigTableItem items[] = {
828ad304
MY
131 { "Sleep", "AllowSuspend", config_parse_tristate, 0, &allow_suspend },
132 { "Sleep", "AllowHibernation", config_parse_tristate, 0, &allow_hibernate },
133 { "Sleep", "AllowSuspendThenHibernate", config_parse_tristate, 0, &allow_s2h },
134 { "Sleep", "AllowHybridSleep", config_parse_tristate, 0, &allow_hybrid_sleep },
1f82c21d 135
828ad304
MY
136 { "Sleep", "SuspendState", config_parse_strv, 0, sc->states + SLEEP_SUSPEND },
137 { "Sleep", "SuspendMode", config_parse_warn_compat, DISABLED_LEGACY, NULL },
1f82c21d 138
828ad304 139 { "Sleep", "HibernateState", config_parse_warn_compat, DISABLED_LEGACY, NULL },
77bd3938 140 { "Sleep", "HibernateMode", config_parse_sleep_mode, 0, sc->modes + SLEEP_HIBERNATE },
1f82c21d 141
828ad304
MY
142 { "Sleep", "HybridSleepState", config_parse_warn_compat, DISABLED_LEGACY, NULL },
143 { "Sleep", "HybridSleepMode", config_parse_warn_compat, DISABLED_LEGACY, NULL },
1f82c21d 144
36b21fac 145 { "Sleep", "MemorySleepMode", config_parse_sleep_mode, 0, &sc->mem_modes },
a2124b35 146
828ad304
MY
147 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec },
148 { "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec },
54d7fcc6
MY
149 {}
150 };
151
6378f257
ZJS
152 (void) config_parse_standard_file_with_dropins(
153 "systemd/sleep.conf",
154 "Sleep\0",
155 config_item_table_lookup, items,
156 CONFIG_PARSE_WARN,
157 /* userdata= */ NULL);
54d7fcc6
MY
158
159 /* use default values unless set */
160 sc->allow[SLEEP_SUSPEND] = allow_suspend != 0;
161 sc->allow[SLEEP_HIBERNATE] = allow_hibernate != 0;
162 sc->allow[SLEEP_HYBRID_SLEEP] = allow_hybrid_sleep >= 0 ? allow_hybrid_sleep
163 : (allow_suspend != 0 && allow_hibernate != 0);
164 sc->allow[SLEEP_SUSPEND_THEN_HIBERNATE] = allow_s2h >= 0 ? allow_s2h
165 : (allow_suspend != 0 && allow_hibernate != 0);
166
1f82c21d
MY
167 for (SleepOperation i = 0; i < _SLEEP_OPERATION_CONFIG_MAX; i++) {
168 if (!sc->states[i] && sleep_default_state_table[i]) {
169 sc->states[i] = strv_copy(sleep_default_state_table[i]);
170 if (!sc->states[i])
171 return log_oom();
172 }
173
174 if (!sc->modes[i] && sleep_default_mode_table[i]) {
175 sc->modes[i] = strv_copy(sleep_default_mode_table[i]);
176 if (!sc->modes[i])
177 return log_oom();
178 }
179 }
180
54d7fcc6
MY
181 if (sc->suspend_estimation_usec == 0)
182 sc->suspend_estimation_usec = DEFAULT_SUSPEND_ESTIMATION_USEC;
183
1f82c21d 184 sleep_config_validate_state_and_mode(sc);
54d7fcc6 185
087a25d2 186 *ret = TAKE_PTR(sc);
54d7fcc6
MY
187 return 0;
188}
189
b0e3b85a 190int sleep_state_supported(char * const *states) {
23577f44
MY
191 _cleanup_free_ char *supported_sysfs = NULL;
192 const char *found;
54d7fcc6
MY
193 int r;
194
23577f44
MY
195 if (strv_isempty(states))
196 return log_debug_errno(SYNTHETIC_ERRNO(ENOMSG), "No sleep state configured.");
54d7fcc6 197
23577f44
MY
198 if (access("/sys/power/state", W_OK) < 0)
199 return log_debug_errno(errno, "/sys/power/state is not writable: %m");
54d7fcc6 200
23577f44
MY
201 r = read_one_line_file("/sys/power/state", &supported_sysfs);
202 if (r < 0)
203 return log_debug_errno(r, "Failed to read /sys/power/state: %m");
54d7fcc6 204
23577f44 205 r = string_contains_word_strv(supported_sysfs, NULL, states, &found);
54d7fcc6
MY
206 if (r < 0)
207 return log_debug_errno(r, "Failed to parse /sys/power/state: %m");
23577f44
MY
208 if (r > 0) {
209 log_debug("Sleep state '%s' is supported by kernel.", found);
210 return true;
54d7fcc6 211 }
23577f44
MY
212
213 if (DEBUG_LOGGING) {
214 _cleanup_free_ char *joined = strv_join(states, " ");
215 log_debug("None of the configured sleep states are supported by kernel: %s", strnull(joined));
216 }
217 return false;
54d7fcc6
MY
218}
219
b0e3b85a 220int sleep_mode_supported(const char *path, char * const *modes) {
23577f44 221 _cleanup_free_ char *supported_sysfs = NULL;
54d7fcc6
MY
222 int r;
223
b0e3b85a
MY
224 assert(path);
225
23577f44
MY
226 /* Unlike state, kernel has its own default choice if not configured */
227 if (strv_isempty(modes)) {
b0e3b85a 228 log_debug("No sleep mode configured, using kernel default for %s.", path);
54d7fcc6 229 return true;
54d7fcc6
MY
230 }
231
b0e3b85a
MY
232 if (access(path, W_OK) < 0)
233 return log_debug_errno(errno, "%s is not writable: %m", path);
54d7fcc6 234
b0e3b85a 235 r = read_one_line_file(path, &supported_sysfs);
23577f44 236 if (r < 0)
b0e3b85a 237 return log_debug_errno(r, "Failed to read %s: %m", path);
23577f44
MY
238
239 for (const char *p = supported_sysfs;;) {
54d7fcc6 240 _cleanup_free_ char *word = NULL;
23577f44
MY
241 char *mode;
242 size_t l;
54d7fcc6
MY
243
244 r = extract_first_word(&p, &word, NULL, 0);
245 if (r < 0)
b0e3b85a 246 return log_debug_errno(r, "Failed to parse %s: %m", path);
54d7fcc6
MY
247 if (r == 0)
248 break;
249
23577f44
MY
250 mode = word;
251 l = strlen(word);
252
253 if (mode[0] == '[' && mode[l - 1] == ']') {
254 mode[l - 1] = '\0';
255 mode++;
54d7fcc6
MY
256 }
257
23577f44 258 if (strv_contains(modes, mode)) {
b0e3b85a 259 log_debug("Sleep mode '%s' is supported by kernel (%s).", mode, path);
54d7fcc6
MY
260 return true;
261 }
262 }
263
264 if (DEBUG_LOGGING) {
23577f44 265 _cleanup_free_ char *joined = strv_join(modes, " ");
b0e3b85a
MY
266 log_debug("None of the configured modes are supported by kernel (%s): %s",
267 path, strnull(joined));
54d7fcc6
MY
268 }
269 return false;
270}
271
a0f6d74e
MY
272static int sleep_supported_internal(
273 const SleepConfig *sleep_config,
274 SleepOperation operation,
275 bool check_allowed,
276 SleepSupport *ret_support);
54d7fcc6 277
a0f6d74e 278static int s2h_supported(const SleepConfig *sleep_config, SleepSupport *ret_support) {
54d7fcc6
MY
279
280 static const SleepOperation operations[] = {
281 SLEEP_SUSPEND,
282 SLEEP_HIBERNATE,
283 };
284
a0f6d74e 285 SleepSupport support;
54d7fcc6
MY
286 int r;
287
a0f6d74e
MY
288 assert(sleep_config);
289 assert(ret_support);
290
54d7fcc6 291 if (!clock_supported(CLOCK_BOOTTIME_ALARM)) {
a0f6d74e
MY
292 log_debug("CLOCK_BOOTTIME_ALARM is not supported, can't perform %s.", sleep_operation_to_string(SLEEP_SUSPEND_THEN_HIBERNATE));
293 *ret_support = SLEEP_ALARM_NOT_SUPPORTED;
54d7fcc6
MY
294 return false;
295 }
296
85471164 297 FOREACH_ELEMENT(i, operations) {
a0f6d74e
MY
298 r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support);
299 if (r < 0)
300 return r;
301 if (r == 0) {
302 log_debug("Sleep operation %s is not supported, can't perform %s.",
303 sleep_operation_to_string(*i), sleep_operation_to_string(SLEEP_SUSPEND_THEN_HIBERNATE));
304 *ret_support = support;
54d7fcc6
MY
305 return false;
306 }
54d7fcc6
MY
307 }
308
a0f6d74e
MY
309 assert(support == SLEEP_SUPPORTED);
310 *ret_support = support;
311
54d7fcc6
MY
312 return true;
313}
314
a0f6d74e 315static int sleep_supported_internal(
54d7fcc6
MY
316 const SleepConfig *sleep_config,
317 SleepOperation operation,
a0f6d74e
MY
318 bool check_allowed,
319 SleepSupport *ret_support) {
320
321 int r;
54d7fcc6 322
a0f6d74e 323 assert(sleep_config);
54d7fcc6
MY
324 assert(operation >= 0);
325 assert(operation < _SLEEP_OPERATION_MAX);
a0f6d74e 326 assert(ret_support);
54d7fcc6
MY
327
328 if (check_allowed && !sleep_config->allow[operation]) {
a0f6d74e
MY
329 log_debug("Sleep operation %s is disabled by configuration.", sleep_operation_to_string(operation));
330 *ret_support = SLEEP_DISABLED;
54d7fcc6
MY
331 return false;
332 }
333
334 if (operation == SLEEP_SUSPEND_THEN_HIBERNATE)
a0f6d74e 335 return s2h_supported(sleep_config, ret_support);
54d7fcc6 336
a0f6d74e
MY
337 assert(operation < _SLEEP_OPERATION_CONFIG_MAX);
338
339 r = sleep_state_supported(sleep_config->states[operation]);
340 if (r == -ENOMSG) {
341 *ret_support = SLEEP_NOT_CONFIGURED;
54d7fcc6 342 return false;
a0f6d74e
MY
343 }
344 if (r < 0)
345 return r;
346 if (r == 0) {
347 *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED;
348 return false;
349 }
54d7fcc6 350
a2124b35
MY
351 if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) {
352 r = sleep_mode_supported("/sys/power/mem_sleep", sleep_config->mem_modes);
353 if (r < 0)
354 return r;
355 if (r == 0) {
356 *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED;
357 return false;
358 }
359 }
360
792dd6f4 361 if (SLEEP_OPERATION_IS_HIBERNATION(operation)) {
b0e3b85a 362 r = sleep_mode_supported("/sys/power/disk", sleep_config->modes[operation]);
fefddffa
MY
363 if (r < 0)
364 return r;
365 if (r == 0) {
366 *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED;
367 return false;
368 }
369
805deec0
MY
370 r = hibernation_is_safe();
371 if (r == -ENOTRECOVERABLE) {
372 *ret_support = SLEEP_RESUME_NOT_SUPPORTED;
373 return false;
374 }
375 if (r == -ENOSPC) {
376 *ret_support = SLEEP_NOT_ENOUGH_SWAP_SPACE;
377 return false;
378 }
379 if (r < 0)
380 return r;
fefddffa
MY
381 } else
382 assert(!sleep_config->modes[operation]);
54d7fcc6 383
a0f6d74e 384 *ret_support = SLEEP_SUPPORTED;
54d7fcc6
MY
385 return true;
386}
387
a0f6d74e 388int sleep_supported_full(SleepOperation operation, SleepSupport *ret_support) {
087a25d2 389 _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL;
a0f6d74e 390 SleepSupport support;
54d7fcc6
MY
391 int r;
392
a0f6d74e
MY
393 assert(operation >= 0);
394 assert(operation < _SLEEP_OPERATION_MAX);
395
54d7fcc6
MY
396 r = parse_sleep_config(&sleep_config);
397 if (r < 0)
398 return r;
399
a0f6d74e
MY
400 r = sleep_supported_internal(sleep_config, operation, /* check_allowed = */ true, &support);
401 if (r < 0)
402 return r;
403
404 assert((r > 0) == (support == SLEEP_SUPPORTED));
405
406 if (ret_support)
407 *ret_support = support;
408
409 return r;
54d7fcc6 410}