Coverage Report

Created: 2026-07-30 06:11

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