1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
6 Copyright 2018 Dell Inc.
18 #include "alloc-util.h"
19 #include "conf-parser.h"
26 #include "parse-util.h"
27 #include "sleep-config.h"
28 #include "string-util.h"
31 int parse_sleep_config(const char *verb
, char ***_modes
, char ***_states
, usec_t
*_delay
) {
33 _cleanup_strv_free_
char
34 **suspend_mode
= NULL
, **suspend_state
= NULL
,
35 **hibernate_mode
= NULL
, **hibernate_state
= NULL
,
36 **hybrid_mode
= NULL
, **hybrid_state
= NULL
;
37 _cleanup_strv_free_
char **modes
, **states
; /* always initialized below */
38 usec_t delay
= 180 * USEC_PER_MINUTE
;
40 const ConfigTableItem items
[] = {
41 { "Sleep", "SuspendMode", config_parse_strv
, 0, &suspend_mode
},
42 { "Sleep", "SuspendState", config_parse_strv
, 0, &suspend_state
},
43 { "Sleep", "HibernateMode", config_parse_strv
, 0, &hibernate_mode
},
44 { "Sleep", "HibernateState", config_parse_strv
, 0, &hibernate_state
},
45 { "Sleep", "HybridSleepMode", config_parse_strv
, 0, &hybrid_mode
},
46 { "Sleep", "HybridSleepState", config_parse_strv
, 0, &hybrid_state
},
47 { "Sleep", "HibernateDelaySec", config_parse_sec
, 0, &delay
},
51 (void) config_parse_many_nulstr(PKGSYSCONFDIR
"/sleep.conf",
52 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
53 "Sleep\0", config_item_table_lookup
, items
,
54 CONFIG_PARSE_WARN
, NULL
);
56 if (streq(verb
, "suspend")) {
57 /* empty by default */
58 modes
= TAKE_PTR(suspend_mode
);
61 states
= TAKE_PTR(suspend_state
);
63 states
= strv_new("mem", "standby", "freeze", NULL
);
65 } else if (streq(verb
, "hibernate")) {
67 modes
= TAKE_PTR(hibernate_mode
);
69 modes
= strv_new("platform", "shutdown", NULL
);
72 states
= TAKE_PTR(hibernate_state
);
74 states
= strv_new("disk", NULL
);
76 } else if (streq(verb
, "hybrid-sleep")) {
78 modes
= TAKE_PTR(hybrid_mode
);
80 modes
= strv_new("suspend", "platform", "shutdown", NULL
);
83 states
= TAKE_PTR(hybrid_state
);
85 states
= strv_new("disk", NULL
);
87 } else if (streq(verb
, "suspend-then-hibernate"))
88 modes
= states
= NULL
;
90 assert_not_reached("what verb");
92 if ((!modes
&& STR_IN_SET(verb
, "hibernate", "hybrid-sleep")) ||
93 (!states
&& !streq(verb
, "suspend-then-hibernate")))
97 *_modes
= TAKE_PTR(modes
);
99 *_states
= TAKE_PTR(states
);
106 int can_sleep_state(char **types
) {
109 _cleanup_free_
char *p
= NULL
;
111 if (strv_isempty(types
))
114 /* If /sys is read-only we cannot sleep */
115 if (access("/sys/power/state", W_OK
) < 0)
118 r
= read_one_line_file("/sys/power/state", &p
);
122 STRV_FOREACH(type
, types
) {
123 const char *word
, *state
;
127 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
)
128 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
135 int can_sleep_disk(char **types
) {
138 _cleanup_free_
char *p
= NULL
;
140 if (strv_isempty(types
))
143 /* If /sys is read-only we cannot sleep */
144 if (access("/sys/power/disk", W_OK
) < 0)
147 r
= read_one_line_file("/sys/power/disk", &p
);
151 STRV_FOREACH(type
, types
) {
152 const char *word
, *state
;
156 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
) {
157 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
162 memcmp(word
+ 1, *type
, l
- 2) == 0 &&
171 #define HIBERNATION_SWAP_THRESHOLD 0.98
173 int find_hibernate_location(char **device
, char **type
, size_t *size
, size_t *used
) {
174 _cleanup_fclose_
FILE *f
;
177 f
= fopen("/proc/swaps", "re");
179 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
180 "Failed to retrieve open /proc/swaps: %m");
185 (void) fscanf(f
, "%*s %*s %*s %*s %*s\n");
188 _cleanup_free_
char *dev_field
= NULL
, *type_field
= NULL
;
189 size_t size_field
, used_field
;
193 "%ms " /* device/file */
194 "%ms " /* type of swap */
195 "%zu " /* swap size */
197 "%*i\n", /* priority */
198 &dev_field
, &type_field
, &size_field
, &used_field
);
203 log_warning("Failed to parse /proc/swaps:%u", i
);
207 if (streq(type_field
, "partition") && endswith(dev_field
, "\\040(deleted)")) {
208 log_warning("Ignoring deleted swapfile '%s'.", dev_field
);
212 *device
= TAKE_PTR(dev_field
);
214 *type
= TAKE_PTR(type_field
);
222 log_debug("No swap partitions were found.");
226 static bool enough_swap_for_hibernation(void) {
227 _cleanup_free_
char *active
= NULL
;
228 unsigned long long act
= 0;
229 size_t size
= 0, used
= 0;
232 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
235 r
= find_hibernate_location(NULL
, NULL
, &size
, &used
);
239 r
= get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE
, &active
);
241 log_error_errno(r
, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
245 r
= safe_atollu(active
, &act
);
247 log_error_errno(r
, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
252 r
= act
<= (size
- used
) * HIBERNATION_SWAP_THRESHOLD
;
253 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
254 r
? "" : "im", act
, size
, used
, 100*HIBERNATION_SWAP_THRESHOLD
);
259 int read_fiemap(int fd
, struct fiemap
**ret
) {
260 _cleanup_free_
struct fiemap
*fiemap
= NULL
, *result_fiemap
= NULL
;
261 struct stat statinfo
;
262 uint32_t result_extents
= 0;
263 uint64_t fiemap_start
= 0, fiemap_length
;
264 const size_t n_extra
= DIV_ROUND_UP(sizeof(struct fiemap
), sizeof(struct fiemap_extent
));
265 size_t fiemap_allocated
= n_extra
, result_fiemap_allocated
= n_extra
;
267 if (fstat(fd
, &statinfo
) < 0)
268 return log_debug_errno(errno
, "Cannot determine file size: %m");
269 if (!S_ISREG(statinfo
.st_mode
))
271 fiemap_length
= statinfo
.st_size
;
273 /* Zero this out in case we run on a file with no extents */
274 fiemap
= calloc(n_extra
, sizeof(struct fiemap_extent
));
278 result_fiemap
= malloc_multiply(n_extra
, sizeof(struct fiemap_extent
));
282 /* XFS filesystem has incorrect implementation of fiemap ioctl and
283 * returns extents for only one block-group at a time, so we need
284 * to handle it manually, starting the next fiemap call from the end
287 while (fiemap_start
< fiemap_length
) {
288 *fiemap
= (struct fiemap
) {
289 .fm_start
= fiemap_start
,
290 .fm_length
= fiemap_length
,
291 .fm_flags
= FIEMAP_FLAG_SYNC
,
294 /* Find out how many extents there are */
295 if (ioctl(fd
, FS_IOC_FIEMAP
, fiemap
) < 0)
296 return log_debug_errno(errno
, "Failed to read extents: %m");
298 /* Nothing to process */
299 if (fiemap
->fm_mapped_extents
== 0)
302 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
303 * the extents for the whole file. Add space for the initial struct fiemap. */
304 if (!greedy_realloc0((void**) &fiemap
, &fiemap_allocated
,
305 n_extra
+ fiemap
->fm_mapped_extents
, sizeof(struct fiemap_extent
)))
308 fiemap
->fm_extent_count
= fiemap
->fm_mapped_extents
;
309 fiemap
->fm_mapped_extents
= 0;
311 if (ioctl(fd
, FS_IOC_FIEMAP
, fiemap
) < 0)
312 return log_debug_errno(errno
, "Failed to read extents: %m");
314 /* Resize result_fiemap to allow us to copy in the extents */
315 if (!greedy_realloc((void**) &result_fiemap
, &result_fiemap_allocated
,
316 n_extra
+ result_extents
+ fiemap
->fm_mapped_extents
, sizeof(struct fiemap_extent
)))
319 memcpy(result_fiemap
->fm_extents
+ result_extents
,
321 sizeof(struct fiemap_extent
) * fiemap
->fm_mapped_extents
);
323 result_extents
+= fiemap
->fm_mapped_extents
;
325 /* Highly unlikely that it is zero */
326 if (_likely_(fiemap
->fm_mapped_extents
> 0)) {
327 uint32_t i
= fiemap
->fm_mapped_extents
- 1;
329 fiemap_start
= fiemap
->fm_extents
[i
].fe_logical
+
330 fiemap
->fm_extents
[i
].fe_length
;
332 if (fiemap
->fm_extents
[i
].fe_flags
& FIEMAP_EXTENT_LAST
)
337 memcpy(result_fiemap
, fiemap
, sizeof(struct fiemap
));
338 result_fiemap
->fm_mapped_extents
= result_extents
;
339 *ret
= TAKE_PTR(result_fiemap
);
343 static bool can_s2h(void) {
347 r
= access("/sys/class/rtc/rtc0/wakealarm", W_OK
);
349 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
350 "/sys/class/rct/rct0/wakealarm is not writable %m");
354 FOREACH_STRING(p
, "suspend", "hibernate") {
356 if (IN_SET(r
, 0, -ENOSPC
)) {
357 log_debug("Unable to %s system.", p
);
361 return log_debug_errno(r
, "Failed to check if %s is possible: %m", p
);
367 int can_sleep(const char *verb
) {
368 _cleanup_strv_free_
char **modes
= NULL
, **states
= NULL
;
371 assert(STR_IN_SET(verb
, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
373 if (streq(verb
, "suspend-then-hibernate"))
376 r
= parse_sleep_config(verb
, &modes
, &states
, NULL
);
380 if (!can_sleep_state(states
) || !can_sleep_disk(modes
))
383 if (streq(verb
, "suspend"))
386 if (!enough_swap_for_hibernation())