]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/sleep-config.c
sleep: Add support for setting a disk offset when hibernating
[thirdparty/systemd.git] / src / shared / sleep-config.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
6 Copyright 2018 Dell Inc.
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
22 #include <errno.h>
23 #include <linux/fs.h>
24 #include <stdbool.h>
25 #include <stddef.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <syslog.h>
29 #include <unistd.h>
30
31 #include "alloc-util.h"
32 #include "conf-parser.h"
33 #include "def.h"
34 #include "env-util.h"
35 #include "fd-util.h"
36 #include "fileio.h"
37 #include "log.h"
38 #include "macro.h"
39 #include "parse-util.h"
40 #include "sleep-config.h"
41 #include "string-util.h"
42 #include "strv.h"
43
44 int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
45
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;
50 char **modes, **states;
51 usec_t delay = 180 * USEC_PER_MINUTE;
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 },
60 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
61 {}
62 };
63
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);
68
69 if (streq(verb, "suspend")) {
70 /* empty by default */
71 modes = TAKE_PTR(suspend_mode);
72
73 if (suspend_state)
74 states = TAKE_PTR(suspend_state);
75 else
76 states = strv_new("mem", "standby", "freeze", NULL);
77
78 } else if (streq(verb, "hibernate")) {
79 if (hibernate_mode)
80 modes = TAKE_PTR(hibernate_mode);
81 else
82 modes = strv_new("platform", "shutdown", NULL);
83
84 if (hibernate_state)
85 states = TAKE_PTR(hibernate_state);
86 else
87 states = strv_new("disk", NULL);
88
89 } else if (streq(verb, "hybrid-sleep")) {
90 if (hybrid_mode)
91 modes = TAKE_PTR(hybrid_mode);
92 else
93 modes = strv_new("suspend", "platform", "shutdown", NULL);
94
95 if (hybrid_state)
96 states = TAKE_PTR(hybrid_state);
97 else
98 states = strv_new("disk", NULL);
99
100 } else if (streq(verb, "suspend-then-hibernate"))
101 modes = states = NULL;
102 else
103 assert_not_reached("what verb");
104
105 if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
106 (!states && !streq(verb, "suspend-then-hibernate"))) {
107 strv_free(modes);
108 strv_free(states);
109 return log_oom();
110 }
111
112 if (_modes)
113 *_modes = modes;
114 if (_states)
115 *_states = states;
116 if (_delay)
117 *_delay = delay;
118
119 return 0;
120 }
121
122 int can_sleep_state(char **types) {
123 char **type;
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) {
139 const char *word, *state;
140 size_t l, k;
141
142 k = strlen(*type);
143 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
144 if (l == k && memcmp(word, *type, l) == 0)
145 return true;
146 }
147
148 return false;
149 }
150
151 int can_sleep_disk(char **types) {
152 char **type;
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) {
168 const char *word, *state;
169 size_t l, k;
170
171 k = strlen(*type);
172 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
173 if (l == k && memcmp(word, *type, l) == 0)
174 return true;
175
176 if (l == k + 2 &&
177 word[0] == '[' &&
178 memcmp(word + 1, *type, l - 2) == 0 &&
179 word[l-1] == ']')
180 return true;
181 }
182 }
183
184 return false;
185 }
186
187 #define HIBERNATION_SWAP_THRESHOLD 0.98
188
189 int find_hibernate_location(char **device, char **type, size_t *size, size_t *used) {
190 _cleanup_fclose_ FILE *f;
191 unsigned i;
192
193 f = fopen("/proc/swaps", "re");
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 }
200
201 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
202
203 for (i = 1;; i++) {
204 _cleanup_free_ char *dev_field = NULL, *type_field = NULL;
205 size_t size_field, used_field;
206 int k;
207
208 k = fscanf(f,
209 "%ms " /* device/file */
210 "%ms " /* type of swap */
211 "%zu " /* swap size */
212 "%zu " /* used */
213 "%*i\n", /* priority */
214 &dev_field, &type_field, &size_field, &used_field);
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
223 if (streq(type_field, "partition") && endswith(dev_field, "\\040(deleted)")) {
224 log_warning("Ignoring deleted swapfile '%s'.", dev_field);
225 continue;
226 }
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;
235 return 0;
236 }
237
238 log_debug("No swap partitions were found.");
239 return -ENOSYS;
240 }
241
242 static bool enough_memory_for_hibernation(void) {
243 _cleanup_free_ char *active = NULL;
244 unsigned long long act = 0;
245 size_t size = 0, used = 0;
246 int r;
247
248 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
249 return true;
250
251 r = find_hibernate_location(NULL, NULL, &size, &used);
252 if (r < 0)
253 return false;
254
255 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
256 if (r < 0) {
257 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
258 return false;
259 }
260
261 r = safe_atollu(active, &act);
262 if (r < 0) {
263 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
264 active);
265 return false;
266 }
267
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);
271
272 return r;
273 }
274
275 int read_fiemap(int fd, struct fiemap **ret) {
276 _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
277 int extents_size;
278 struct stat statinfo;
279 uint32_t result_extents = 0;
280 uint64_t fiemap_start = 0, fiemap_length;
281 size_t fiemap_size = 1, result_fiemap_size = 1;
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
289 /* zero this out in case we run on a file with no extents */
290 fiemap = new0(struct fiemap, 1);
291 if (!fiemap)
292 return -ENOMEM;
293
294 result_fiemap = new(struct fiemap, 1);
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
318 /* Result fiemap has to hold all the extents for the whole file */
319 extents_size = DIV_ROUND_UP(sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents,
320 sizeof(struct fiemap));
321
322 /* Resize fiemap to allow us to read in the extents */
323 if (!GREEDY_REALLOC0(fiemap, fiemap_size, extents_size))
324 return -ENOMEM;
325
326 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
327 fiemap->fm_mapped_extents = 0;
328
329 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
330 return log_debug_errno(errno, "Failed to read extents: %m");
331
332 extents_size = DIV_ROUND_UP(sizeof(struct fiemap_extent) * (result_extents + fiemap->fm_mapped_extents),
333 sizeof(struct fiemap));
334
335 /* Resize result_fiemap to allow us to read in the extents */
336 if (!GREEDY_REALLOC(result_fiemap, result_fiemap_size,
337 extents_size))
338 return -ENOMEM;
339
340 memcpy(result_fiemap->fm_extents + result_extents,
341 fiemap->fm_extents,
342 sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
343
344 result_extents += fiemap->fm_mapped_extents;
345
346 /* Highly unlikely that it is zero */
347 if (fiemap->fm_mapped_extents > 0) {
348 uint32_t i = fiemap->fm_mapped_extents - 1;
349
350 fiemap_start = fiemap->fm_extents[i].fe_logical +
351 fiemap->fm_extents[i].fe_length;
352
353 if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
354 break;
355 }
356 }
357
358 memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
359 result_fiemap->fm_mapped_extents = result_extents;
360 *ret = TAKE_PTR(result_fiemap);
361 return 0;
362 }
363
364 static bool can_s2h(void) {
365 int r;
366
367 r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
368 if (r < 0) {
369 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
370 "/sys/class/rct/rct0/wakealarm is not writable %m");
371 return false;
372 }
373
374 r = can_sleep("suspend");
375 if (r < 0) {
376 log_debug_errno(r, "Unable to suspend system.");
377 return false;
378 }
379
380 r = can_sleep("hibernate");
381 if (r < 0) {
382 log_debug_errno(r, "Unable to hibernate system.");
383 return false;
384 }
385
386 return true;
387 }
388
389 int can_sleep(const char *verb) {
390 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
391 int r;
392
393 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
394
395 if (streq(verb, "suspend-then-hibernate"))
396 return can_s2h();
397
398 r = parse_sleep_config(verb, &modes, &states, NULL);
399 if (r < 0)
400 return false;
401
402 if (!can_sleep_state(states) || !can_sleep_disk(modes))
403 return false;
404
405 return streq(verb, "suspend") || enough_memory_for_hibernation();
406 }