]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
shared/sleep-config: return a custom message when not enough swap for hibernation
[thirdparty/systemd.git] / src / shared / sleep-config.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
19adb8a3
ZJS
2/***
3 This file is part of systemd.
4
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
c58493c0 6 Copyright 2018 Dell Inc.
19adb8a3
ZJS
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
a8fbdf54 22#include <errno.h>
17c40b3a 23#include <linux/fs.h>
a8fbdf54
TA
24#include <stdbool.h>
25#include <stddef.h>
19adb8a3 26#include <stdio.h>
a8fbdf54
TA
27#include <string.h>
28#include <syslog.h>
29#include <unistd.h>
19adb8a3 30
b5efdb8a 31#include "alloc-util.h"
19adb8a3 32#include "conf-parser.h"
a0f29c76 33#include "def.h"
490d20e6 34#include "env-util.h"
3ffd4af2 35#include "fd-util.h"
19adb8a3
ZJS
36#include "fileio.h"
37#include "log.h"
a8fbdf54 38#include "macro.h"
a0f29c76 39#include "parse-util.h"
3ffd4af2 40#include "sleep-config.h"
07630cea 41#include "string-util.h"
19adb8a3 42#include "strv.h"
19adb8a3 43
c58493c0 44int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
34c10968 45
19adb8a3
ZJS
46 _cleanup_strv_free_ char
47 **suspend_mode = NULL, **suspend_state = NULL,
48 **hibernate_mode = NULL, **hibernate_state = NULL,
49 **hybrid_mode = NULL, **hybrid_state = NULL;
dabeaa46 50 char **modes, **states;
9aa2e409 51 usec_t delay = 180 * USEC_PER_MINUTE;
19adb8a3
ZJS
52
53 const ConfigTableItem items[] = {
54 { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
55 { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
56 { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
57 { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
58 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
59 { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
c58493c0 60 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
34c10968
LP
61 {}
62 };
19adb8a3 63
bcde742e
LP
64 (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
65 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
66 "Sleep\0", config_item_table_lookup, items,
67 CONFIG_PARSE_WARN, NULL);
19adb8a3 68
19adb8a3
ZJS
69 if (streq(verb, "suspend")) {
70 /* empty by default */
2f4cefe6 71 modes = TAKE_PTR(suspend_mode);
19adb8a3
ZJS
72
73 if (suspend_state)
2f4cefe6 74 states = TAKE_PTR(suspend_state);
19adb8a3 75 else
dabeaa46 76 states = strv_new("mem", "standby", "freeze", NULL);
19adb8a3 77
19adb8a3
ZJS
78 } else if (streq(verb, "hibernate")) {
79 if (hibernate_mode)
2f4cefe6 80 modes = TAKE_PTR(hibernate_mode);
19adb8a3 81 else
dabeaa46 82 modes = strv_new("platform", "shutdown", NULL);
19adb8a3
ZJS
83
84 if (hibernate_state)
2f4cefe6 85 states = TAKE_PTR(hibernate_state);
19adb8a3 86 else
dabeaa46 87 states = strv_new("disk", NULL);
19adb8a3 88
19adb8a3
ZJS
89 } else if (streq(verb, "hybrid-sleep")) {
90 if (hybrid_mode)
2f4cefe6 91 modes = TAKE_PTR(hybrid_mode);
19adb8a3 92 else
dabeaa46 93 modes = strv_new("suspend", "platform", "shutdown", NULL);
19adb8a3
ZJS
94
95 if (hybrid_state)
2f4cefe6 96 states = TAKE_PTR(hybrid_state);
19adb8a3 97 else
dabeaa46 98 states = strv_new("disk", NULL);
9aa2e409 99
e68c79db 100 } else if (streq(verb, "suspend-then-hibernate"))
9aa2e409
ZJS
101 modes = states = NULL;
102 else
19adb8a3
ZJS
103 assert_not_reached("what verb");
104
9aa2e409 105 if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
e68c79db 106 (!states && !streq(verb, "suspend-then-hibernate"))) {
dabeaa46
ZJS
107 strv_free(modes);
108 strv_free(states);
19adb8a3
ZJS
109 return log_oom();
110 }
111
c58493c0
ML
112 if (_modes)
113 *_modes = modes;
114 if (_states)
115 *_states = states;
116 if (_delay)
117 *_delay = delay;
118
19adb8a3
ZJS
119 return 0;
120}
121
122int can_sleep_state(char **types) {
a2a5291b 123 char **type;
19adb8a3
ZJS
124 int r;
125 _cleanup_free_ char *p = NULL;
126
127 if (strv_isempty(types))
128 return true;
129
130 /* If /sys is read-only we cannot sleep */
131 if (access("/sys/power/state", W_OK) < 0)
132 return false;
133
134 r = read_one_line_file("/sys/power/state", &p);
135 if (r < 0)
136 return false;
137
138 STRV_FOREACH(type, types) {
a2a5291b 139 const char *word, *state;
19adb8a3
ZJS
140 size_t l, k;
141
142 k = strlen(*type);
a2a5291b
ZJS
143 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
144 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
145 return true;
146 }
147
148 return false;
149}
150
151int can_sleep_disk(char **types) {
a2a5291b 152 char **type;
19adb8a3
ZJS
153 int r;
154 _cleanup_free_ char *p = NULL;
155
156 if (strv_isempty(types))
157 return true;
158
159 /* If /sys is read-only we cannot sleep */
160 if (access("/sys/power/disk", W_OK) < 0)
161 return false;
162
163 r = read_one_line_file("/sys/power/disk", &p);
164 if (r < 0)
165 return false;
166
167 STRV_FOREACH(type, types) {
a2a5291b 168 const char *word, *state;
19adb8a3
ZJS
169 size_t l, k;
170
171 k = strlen(*type);
a2a5291b
ZJS
172 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
173 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
174 return true;
175
a2a5291b
ZJS
176 if (l == k + 2 &&
177 word[0] == '[' &&
178 memcmp(word + 1, *type, l - 2) == 0 &&
179 word[l-1] == ']')
19adb8a3
ZJS
180 return true;
181 }
182 }
183
184 return false;
185}
186
69ab8088
ZJS
187#define HIBERNATION_SWAP_THRESHOLD 0.98
188
17c40b3a 189int find_hibernate_location(char **device, char **type, size_t *size, size_t *used) {
9fb3675e 190 _cleanup_fclose_ FILE *f;
1fa2f38f 191 unsigned i;
9fb3675e 192
c8a202b7 193 f = fopen("/proc/swaps", "re");
9fb3675e
ZJS
194 if (!f) {
195 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
196 "Failed to retrieve open /proc/swaps: %m");
197 assert(errno > 0);
198 return -errno;
199 }
69ab8088 200
9fb3675e
ZJS
201 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
202
203 for (i = 1;; i++) {
17c40b3a 204 _cleanup_free_ char *dev_field = NULL, *type_field = NULL;
9fb3675e
ZJS
205 size_t size_field, used_field;
206 int k;
207
208 k = fscanf(f,
209 "%ms " /* device/file */
210 "%ms " /* type of swap */
1fa2f38f
ZJS
211 "%zu " /* swap size */
212 "%zu " /* used */
9fb3675e 213 "%*i\n", /* priority */
17c40b3a 214 &dev_field, &type_field, &size_field, &used_field);
9fb3675e
ZJS
215 if (k != 4) {
216 if (k == EOF)
217 break;
218
219 log_warning("Failed to parse /proc/swaps:%u", i);
220 continue;
221 }
222
17c40b3a
ML
223 if (streq(type_field, "partition") && endswith(dev_field, "\\040(deleted)")) {
224 log_warning("Ignoring deleted swapfile '%s'.", dev_field);
9fb3675e
ZJS
225 continue;
226 }
17c40b3a
ML
227 if (device)
228 *device = TAKE_PTR(dev_field);
229 if (type)
230 *type = TAKE_PTR(type_field);
231 if (size)
232 *size = size_field;
233 if (used)
234 *used = used_field;
9fb3675e 235 return 0;
69ab8088
ZJS
236 }
237
9fb3675e
ZJS
238 log_debug("No swap partitions were found.");
239 return -ENOSYS;
240}
241
242static bool enough_memory_for_hibernation(void) {
243 _cleanup_free_ char *active = NULL;
39883f62
LP
244 unsigned long long act = 0;
245 size_t size = 0, used = 0;
9fb3675e
ZJS
246 int r;
247
490d20e6
VV
248 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
249 return true;
250
17c40b3a 251 r = find_hibernate_location(NULL, NULL, &size, &used);
9fb3675e 252 if (r < 0)
69ab8088 253 return false;
69ab8088 254
c4cd1d4d 255 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
69ab8088 256 if (r < 0) {
da927ba9 257 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
69ab8088
ZJS
258 return false;
259 }
260
261 r = safe_atollu(active, &act);
262 if (r < 0) {
c33b3297
MS
263 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
264 active);
69ab8088
ZJS
265 return false;
266 }
267
9fb3675e
ZJS
268 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
269 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
270 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
69ab8088
ZJS
271
272 return r;
273}
274
17c40b3a
ML
275int read_fiemap(int fd, struct fiemap **ret) {
276 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
17c40b3a
ML
277 struct stat statinfo;
278 uint32_t result_extents = 0;
279 uint64_t fiemap_start = 0, fiemap_length;
6524f1a8
ZJS
280 const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
281 size_t fiemap_allocated = n_extra, result_fiemap_allocated = n_extra;
17c40b3a
ML
282
283 if (fstat(fd, &statinfo) < 0)
284 return log_debug_errno(errno, "Cannot determine file size: %m");
285 if (!S_ISREG(statinfo.st_mode))
286 return -ENOTTY;
287 fiemap_length = statinfo.st_size;
288
6524f1a8
ZJS
289 /* Zero this out in case we run on a file with no extents */
290 fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
291 if (!fiemap)
292 return -ENOMEM;
293
6524f1a8 294 result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
17c40b3a
ML
295 if (!result_fiemap)
296 return -ENOMEM;
297
298 /* XFS filesystem has incorrect implementation of fiemap ioctl and
299 * returns extents for only one block-group at a time, so we need
300 * to handle it manually, starting the next fiemap call from the end
301 * of the last extent
302 */
303 while (fiemap_start < fiemap_length) {
304 *fiemap = (struct fiemap) {
305 .fm_start = fiemap_start,
306 .fm_length = fiemap_length,
307 .fm_flags = FIEMAP_FLAG_SYNC,
308 };
309
310 /* Find out how many extents there are */
311 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
312 return log_debug_errno(errno, "Failed to read extents: %m");
313
314 /* Nothing to process */
315 if (fiemap->fm_mapped_extents == 0)
316 break;
317
6524f1a8
ZJS
318 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
319 * the extents for the whole file. Add space for the initial struct fiemap. */
320 if (!greedy_realloc0((void**) &fiemap, &fiemap_allocated,
321 n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
322 return -ENOMEM;
323
324 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
325 fiemap->fm_mapped_extents = 0;
326
327 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
328 return log_debug_errno(errno, "Failed to read extents: %m");
329
6524f1a8
ZJS
330 /* Resize result_fiemap to allow us to copy in the extents */
331 if (!greedy_realloc((void**) &result_fiemap, &result_fiemap_allocated,
332 n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
17c40b3a
ML
333 return -ENOMEM;
334
335 memcpy(result_fiemap->fm_extents + result_extents,
336 fiemap->fm_extents,
337 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
338
339 result_extents += fiemap->fm_mapped_extents;
340
341 /* Highly unlikely that it is zero */
6524f1a8 342 if (_likely_(fiemap->fm_mapped_extents > 0)) {
17c40b3a
ML
343 uint32_t i = fiemap->fm_mapped_extents - 1;
344
345 fiemap_start = fiemap->fm_extents[i].fe_logical +
346 fiemap->fm_extents[i].fe_length;
347
348 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
349 break;
350 }
351 }
352
353 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
354 result_fiemap->fm_mapped_extents = result_extents;
355 *ret = TAKE_PTR(result_fiemap);
356 return 0;
357}
358
c58493c0 359static bool can_s2h(void) {
c863dc05 360 const char *p;
c58493c0
ML
361 int r;
362
363 r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
364 if (r < 0) {
365 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
366 "/sys/class/rct/rct0/wakealarm is not writable %m");
367 return false;
368 }
369
c863dc05
ZJS
370 FOREACH_STRING(p, "suspend", "hibernate") {
371 r = can_sleep(p);
b71c9758 372 if (IN_SET(r, 0, -ENOSPC)) {
c863dc05
ZJS
373 log_debug("Unable to %s system.", p);
374 return false;
375 }
b71c9758
ZJS
376 if (r < 0)
377 return log_debug_errno(r, "Failed to check if %s is possible: %m", p);
c58493c0
ML
378 }
379
380 return true;
381}
382
19adb8a3
ZJS
383int can_sleep(const char *verb) {
384 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
385 int r;
386
e68c79db 387 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
c58493c0 388
e68c79db 389 if (streq(verb, "suspend-then-hibernate"))
c58493c0 390 return can_s2h();
19adb8a3 391
c58493c0 392 r = parse_sleep_config(verb, &modes, &states, NULL);
19adb8a3
ZJS
393 if (r < 0)
394 return false;
395
69ab8088
ZJS
396 if (!can_sleep_state(states) || !can_sleep_disk(modes))
397 return false;
398
b71c9758
ZJS
399 if (streq(verb, "suspend"))
400 return true;
401
402 if (!enough_memory_for_hibernation())
403 return -ENOSPC;
404
405 return true;
19adb8a3 406}