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