]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/sleep-config.c
Merge pull request #8377 from sourcejedi/logind_restart_is_sorely_lacking_in_testing3
[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 <stdbool.h>
24 #include <stddef.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <syslog.h>
28 #include <unistd.h>
29
30 #include "alloc-util.h"
31 #include "conf-parser.h"
32 #include "def.h"
33 #include "env-util.h"
34 #include "fd-util.h"
35 #include "fileio.h"
36 #include "log.h"
37 #include "macro.h"
38 #include "parse-util.h"
39 #include "sleep-config.h"
40 #include "string-util.h"
41 #include "strv.h"
42
43 #define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
44
45 int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
46
47 _cleanup_strv_free_ char
48 **suspend_mode = NULL, **suspend_state = NULL,
49 **hibernate_mode = NULL, **hibernate_state = NULL,
50 **hybrid_mode = NULL, **hybrid_state = NULL;
51 char **modes, **states;
52 usec_t delay = 180 * USEC_PER_MINUTE;
53
54 const ConfigTableItem items[] = {
55 { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
56 { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
57 { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
58 { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
59 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
60 { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
61 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
62 {}
63 };
64
65 (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
66 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
67 "Sleep\0", config_item_table_lookup, items,
68 CONFIG_PARSE_WARN, NULL);
69
70 if (streq(verb, "suspend")) {
71 /* empty by default */
72 USE(modes, suspend_mode);
73
74 if (suspend_state)
75 USE(states, suspend_state);
76 else
77 states = strv_new("mem", "standby", "freeze", NULL);
78
79 } else if (streq(verb, "hibernate")) {
80 if (hibernate_mode)
81 USE(modes, hibernate_mode);
82 else
83 modes = strv_new("platform", "shutdown", NULL);
84
85 if (hibernate_state)
86 USE(states, hibernate_state);
87 else
88 states = strv_new("disk", NULL);
89
90 } else if (streq(verb, "hybrid-sleep")) {
91 if (hybrid_mode)
92 USE(modes, hybrid_mode);
93 else
94 modes = strv_new("suspend", "platform", "shutdown", NULL);
95
96 if (hybrid_state)
97 USE(states, hybrid_state);
98 else
99 states = strv_new("disk", NULL);
100
101 } else if (streq(verb, "suspend-to-hibernate"))
102 modes = states = NULL;
103 else
104 assert_not_reached("what verb");
105
106 if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
107 (!states && !streq(verb, "suspend-to-hibernate"))) {
108 strv_free(modes);
109 strv_free(states);
110 return log_oom();
111 }
112
113 if (_modes)
114 *_modes = modes;
115 if (_states)
116 *_states = states;
117 if (_delay)
118 *_delay = delay;
119
120 return 0;
121 }
122
123 int can_sleep_state(char **types) {
124 char **type;
125 int r;
126 _cleanup_free_ char *p = NULL;
127
128 if (strv_isempty(types))
129 return true;
130
131 /* If /sys is read-only we cannot sleep */
132 if (access("/sys/power/state", W_OK) < 0)
133 return false;
134
135 r = read_one_line_file("/sys/power/state", &p);
136 if (r < 0)
137 return false;
138
139 STRV_FOREACH(type, types) {
140 const char *word, *state;
141 size_t l, k;
142
143 k = strlen(*type);
144 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
145 if (l == k && memcmp(word, *type, l) == 0)
146 return true;
147 }
148
149 return false;
150 }
151
152 int can_sleep_disk(char **types) {
153 char **type;
154 int r;
155 _cleanup_free_ char *p = NULL;
156
157 if (strv_isempty(types))
158 return true;
159
160 /* If /sys is read-only we cannot sleep */
161 if (access("/sys/power/disk", W_OK) < 0)
162 return false;
163
164 r = read_one_line_file("/sys/power/disk", &p);
165 if (r < 0)
166 return false;
167
168 STRV_FOREACH(type, types) {
169 const char *word, *state;
170 size_t l, k;
171
172 k = strlen(*type);
173 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
174 if (l == k && memcmp(word, *type, l) == 0)
175 return true;
176
177 if (l == k + 2 &&
178 word[0] == '[' &&
179 memcmp(word + 1, *type, l - 2) == 0 &&
180 word[l-1] == ']')
181 return true;
182 }
183 }
184
185 return false;
186 }
187
188 #define HIBERNATION_SWAP_THRESHOLD 0.98
189
190 static int hibernation_partition_size(size_t *size, size_t *used) {
191 _cleanup_fclose_ FILE *f;
192 unsigned i;
193
194 assert(size);
195 assert(used);
196
197 f = fopen("/proc/swaps", "re");
198 if (!f) {
199 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
200 "Failed to retrieve open /proc/swaps: %m");
201 assert(errno > 0);
202 return -errno;
203 }
204
205 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
206
207 for (i = 1;; i++) {
208 _cleanup_free_ char *dev = NULL, *type = NULL;
209 size_t size_field, used_field;
210 int k;
211
212 k = fscanf(f,
213 "%ms " /* device/file */
214 "%ms " /* type of swap */
215 "%zu " /* swap size */
216 "%zu " /* used */
217 "%*i\n", /* priority */
218 &dev, &type, &size_field, &used_field);
219 if (k != 4) {
220 if (k == EOF)
221 break;
222
223 log_warning("Failed to parse /proc/swaps:%u", i);
224 continue;
225 }
226
227 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
228 log_warning("Ignoring deleted swapfile '%s'.", dev);
229 continue;
230 }
231
232 *size = size_field;
233 *used = used_field;
234 return 0;
235 }
236
237 log_debug("No swap partitions were found.");
238 return -ENOSYS;
239 }
240
241 static bool enough_memory_for_hibernation(void) {
242 _cleanup_free_ char *active = NULL;
243 unsigned long long act = 0;
244 size_t size = 0, used = 0;
245 int r;
246
247 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
248 return true;
249
250 r = hibernation_partition_size(&size, &used);
251 if (r < 0)
252 return false;
253
254 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
255 if (r < 0) {
256 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
257 return false;
258 }
259
260 r = safe_atollu(active, &act);
261 if (r < 0) {
262 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
263 active);
264 return false;
265 }
266
267 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
268 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
269 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
270
271 return r;
272 }
273
274 static bool can_s2h(void) {
275 int r;
276
277 r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
278 if (r < 0) {
279 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
280 "/sys/class/rct/rct0/wakealarm is not writable %m");
281 return false;
282 }
283
284 r = can_sleep("suspend");
285 if (r < 0) {
286 log_debug_errno(r, "Unable to suspend system.");
287 return false;
288 }
289
290 r = can_sleep("hibernate");
291 if (r < 0) {
292 log_debug_errno(r, "Unable to hibernate system.");
293 return false;
294 }
295
296 return true;
297 }
298
299 int can_sleep(const char *verb) {
300 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
301 int r;
302
303 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
304
305 if (streq(verb, "suspend-to-hibernate"))
306 return can_s2h();
307
308 r = parse_sleep_config(verb, &modes, &states, NULL);
309 if (r < 0)
310 return false;
311
312 if (!can_sleep_state(states) || !can_sleep_disk(modes))
313 return false;
314
315 return streq(verb, "suspend") || enough_memory_for_hibernation();
316 }