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