1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.h"
6 #include "conf-parser.h"
8 #include "device-util.h"
9 #include "devnum-util.h"
10 #include "errno-util.h"
13 #include "hibernate-util.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"
23 #include "time-util.h"
25 #define DEFAULT_SUSPEND_ESTIMATION_USEC (1 * USEC_PER_HOUR)
27 static 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",
34 DEFINE_STRING_TABLE_LOOKUP(sleep_operation
, SleepOperation
);
36 static 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"),
42 static char* const* const sleep_default_mode_table
[_SLEEP_OPERATION_CONFIG_MAX
] = {
43 /* Not used by SLEEP_SUSPEND */
44 [SLEEP_HIBERNATE
] = STRV_MAKE("platform", "shutdown"),
45 [SLEEP_HYBRID_SLEEP
] = STRV_MAKE("suspend"),
48 SleepConfig
* sleep_config_free(SleepConfig
*sc
) {
52 for (SleepOperation i
= 0; i
< _SLEEP_OPERATION_CONFIG_MAX
; i
++) {
53 strv_free(sc
->states
[i
]);
54 strv_free(sc
->modes
[i
]);
57 strv_free(sc
->mem_modes
);
62 static int config_parse_sleep_mode(
67 unsigned section_line
,
74 char ***sv
= ASSERT_PTR(data
);
75 _cleanup_strv_free_
char **modes
= NULL
;
82 if (isempty(rvalue
)) {
83 modes
= strv_new(NULL
);
87 r
= strv_split_full(&modes
, rvalue
, NULL
, EXTRACT_UNQUOTE
|EXTRACT_RETAIN_ESCAPE
);
92 return strv_free_and_replace(*sv
, modes
);
95 static void sleep_config_validate_state_and_mode(SleepConfig
*sc
) {
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
));
107 assert(!sc
->modes
[SLEEP_SUSPEND
]);
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
));
116 int 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;
122 sc
= new(SleepConfig
, 1);
126 *sc
= (SleepConfig
) {
127 .hibernate_delay_usec
= USEC_INFINITY
,
130 const ConfigTableItem items
[] = {
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
},
136 { "Sleep", "SuspendState", config_parse_strv
, 0, sc
->states
+ SLEEP_SUSPEND
},
137 { "Sleep", "SuspendMode", config_parse_warn_compat
, DISABLED_LEGACY
, NULL
},
139 { "Sleep", "HibernateState", config_parse_warn_compat
, DISABLED_LEGACY
, NULL
},
140 { "Sleep", "HibernateMode", config_parse_sleep_mode
, 0, sc
->modes
+ SLEEP_HIBERNATE
},
142 { "Sleep", "HybridSleepState", config_parse_warn_compat
, DISABLED_LEGACY
, NULL
},
143 { "Sleep", "HybridSleepMode", config_parse_warn_compat
, DISABLED_LEGACY
, NULL
},
145 { "Sleep", "MemorySleepMode", config_parse_sleep_mode
, 0, &sc
->mem_modes
},
147 { "Sleep", "HibernateDelaySec", config_parse_sec
, 0, &sc
->hibernate_delay_usec
},
148 { "Sleep", "SuspendEstimationSec", config_parse_sec
, 0, &sc
->suspend_estimation_usec
},
152 (void) config_parse_standard_file_with_dropins(
153 "systemd/sleep.conf",
155 config_item_table_lookup
, items
,
157 /* userdata= */ NULL
);
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);
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
]);
174 if (!sc
->modes
[i
] && sleep_default_mode_table
[i
]) {
175 sc
->modes
[i
] = strv_copy(sleep_default_mode_table
[i
]);
181 if (sc
->suspend_estimation_usec
== 0)
182 sc
->suspend_estimation_usec
= DEFAULT_SUSPEND_ESTIMATION_USEC
;
184 sleep_config_validate_state_and_mode(sc
);
190 int sleep_state_supported(char * const *states
) {
191 _cleanup_free_
char *supported_sysfs
= NULL
;
195 if (strv_isempty(states
))
196 return log_debug_errno(SYNTHETIC_ERRNO(ENOMSG
), "No sleep state configured.");
198 if (access("/sys/power/state", W_OK
) < 0)
199 return log_debug_errno(errno
, "/sys/power/state is not writable: %m");
201 r
= read_one_line_file("/sys/power/state", &supported_sysfs
);
203 return log_debug_errno(r
, "Failed to read /sys/power/state: %m");
205 r
= string_contains_word_strv(supported_sysfs
, NULL
, states
, &found
);
207 return log_debug_errno(r
, "Failed to parse /sys/power/state: %m");
209 log_debug("Sleep state '%s' is supported by kernel.", found
);
214 _cleanup_free_
char *joined
= strv_join(states
, " ");
215 log_debug("None of the configured sleep states are supported by kernel: %s", strnull(joined
));
220 int sleep_mode_supported(const char *path
, char * const *modes
) {
221 _cleanup_free_
char *supported_sysfs
= NULL
;
226 /* Unlike state, kernel has its own default choice if not configured */
227 if (strv_isempty(modes
)) {
228 log_debug("No sleep mode configured, using kernel default for %s.", path
);
232 if (access(path
, W_OK
) < 0)
233 return log_debug_errno(errno
, "%s is not writable: %m", path
);
235 r
= read_one_line_file(path
, &supported_sysfs
);
237 return log_debug_errno(r
, "Failed to read %s: %m", path
);
239 for (const char *p
= supported_sysfs
;;) {
240 _cleanup_free_
char *word
= NULL
;
244 r
= extract_first_word(&p
, &word
, NULL
, 0);
246 return log_debug_errno(r
, "Failed to parse %s: %m", path
);
253 if (mode
[0] == '[' && mode
[l
- 1] == ']') {
258 if (strv_contains(modes
, mode
)) {
259 log_debug("Sleep mode '%s' is supported by kernel (%s).", mode
, path
);
265 _cleanup_free_
char *joined
= strv_join(modes
, " ");
266 log_debug("None of the configured modes are supported by kernel (%s): %s",
267 path
, strnull(joined
));
272 static int sleep_supported_internal(
273 const SleepConfig
*sleep_config
,
274 SleepOperation operation
,
276 SleepSupport
*ret_support
);
278 static int s2h_supported(const SleepConfig
*sleep_config
, SleepSupport
*ret_support
) {
280 static const SleepOperation operations
[] = {
285 SleepSupport support
;
288 assert(sleep_config
);
291 if (!clock_supported(CLOCK_BOOTTIME_ALARM
)) {
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
;
297 FOREACH_ELEMENT(i
, operations
) {
298 r
= sleep_supported_internal(sleep_config
, *i
, /* check_allowed = */ false, &support
);
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
;
309 assert(support
== SLEEP_SUPPORTED
);
310 *ret_support
= support
;
315 static int sleep_supported_internal(
316 const SleepConfig
*sleep_config
,
317 SleepOperation operation
,
319 SleepSupport
*ret_support
) {
323 assert(sleep_config
);
324 assert(operation
>= 0);
325 assert(operation
< _SLEEP_OPERATION_MAX
);
328 if (check_allowed
&& !sleep_config
->allow
[operation
]) {
329 log_debug("Sleep operation %s is disabled by configuration.", sleep_operation_to_string(operation
));
330 *ret_support
= SLEEP_DISABLED
;
334 if (operation
== SLEEP_SUSPEND_THEN_HIBERNATE
)
335 return s2h_supported(sleep_config
, ret_support
);
337 assert(operation
< _SLEEP_OPERATION_CONFIG_MAX
);
339 r
= sleep_state_supported(sleep_config
->states
[operation
]);
341 *ret_support
= SLEEP_NOT_CONFIGURED
;
347 *ret_support
= SLEEP_STATE_OR_MODE_NOT_SUPPORTED
;
351 if (SLEEP_NEEDS_MEM_SLEEP(sleep_config
, operation
)) {
352 r
= sleep_mode_supported("/sys/power/mem_sleep", sleep_config
->mem_modes
);
356 *ret_support
= SLEEP_STATE_OR_MODE_NOT_SUPPORTED
;
361 if (SLEEP_OPERATION_IS_HIBERNATION(operation
)) {
362 r
= sleep_mode_supported("/sys/power/disk", sleep_config
->modes
[operation
]);
366 *ret_support
= SLEEP_STATE_OR_MODE_NOT_SUPPORTED
;
370 r
= hibernation_is_safe();
373 case -ENOTRECOVERABLE
:
374 *ret_support
= SLEEP_RESUME_NOT_SUPPORTED
;
378 *ret_support
= SLEEP_RESUME_DEVICE_MISSING
;
382 *ret_support
= SLEEP_RESUME_MISCONFIGURED
;
386 *ret_support
= SLEEP_NOT_ENOUGH_SWAP_SPACE
;
394 assert(!sleep_config
->modes
[operation
]);
396 *ret_support
= SLEEP_SUPPORTED
;
400 int sleep_supported_full(SleepOperation operation
, SleepSupport
*ret_support
) {
401 _cleanup_(sleep_config_freep
) SleepConfig
*sleep_config
= NULL
;
402 SleepSupport support
;
405 assert(operation
>= 0);
406 assert(operation
< _SLEEP_OPERATION_MAX
);
408 r
= parse_sleep_config(&sleep_config
);
412 r
= sleep_supported_internal(sleep_config
, operation
, /* check_allowed = */ true, &support
);
416 assert((r
> 0) == (support
== SLEEP_SUPPORTED
));
419 *ret_support
= support
;