]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
macro: introduce TAKE_PTR() macro
[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
TA
22#include <errno.h>
23#include <stdbool.h>
24#include <stddef.h>
19adb8a3 25#include <stdio.h>
a8fbdf54
TA
26#include <string.h>
27#include <syslog.h>
28#include <unistd.h>
19adb8a3 29
b5efdb8a 30#include "alloc-util.h"
19adb8a3 31#include "conf-parser.h"
a0f29c76 32#include "def.h"
490d20e6 33#include "env-util.h"
3ffd4af2 34#include "fd-util.h"
19adb8a3
ZJS
35#include "fileio.h"
36#include "log.h"
a8fbdf54 37#include "macro.h"
a0f29c76 38#include "parse-util.h"
3ffd4af2 39#include "sleep-config.h"
07630cea 40#include "string-util.h"
19adb8a3 41#include "strv.h"
19adb8a3 42
9ed794a3 43#define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
dabeaa46 44
c58493c0 45int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
34c10968 46
19adb8a3
ZJS
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;
dabeaa46 51 char **modes, **states;
9aa2e409 52 usec_t delay = 180 * USEC_PER_MINUTE;
19adb8a3
ZJS
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 },
c58493c0 61 { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &delay},
34c10968
LP
62 {}
63 };
19adb8a3 64
bcde742e
LP
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);
19adb8a3 69
19adb8a3
ZJS
70 if (streq(verb, "suspend")) {
71 /* empty by default */
dabeaa46 72 USE(modes, suspend_mode);
19adb8a3
ZJS
73
74 if (suspend_state)
dabeaa46 75 USE(states, suspend_state);
19adb8a3 76 else
dabeaa46 77 states = strv_new("mem", "standby", "freeze", NULL);
19adb8a3 78
19adb8a3
ZJS
79 } else if (streq(verb, "hibernate")) {
80 if (hibernate_mode)
dabeaa46 81 USE(modes, hibernate_mode);
19adb8a3 82 else
dabeaa46 83 modes = strv_new("platform", "shutdown", NULL);
19adb8a3
ZJS
84
85 if (hibernate_state)
dabeaa46 86 USE(states, hibernate_state);
19adb8a3 87 else
dabeaa46 88 states = strv_new("disk", NULL);
19adb8a3 89
19adb8a3
ZJS
90 } else if (streq(verb, "hybrid-sleep")) {
91 if (hybrid_mode)
dabeaa46 92 USE(modes, hybrid_mode);
19adb8a3 93 else
dabeaa46 94 modes = strv_new("suspend", "platform", "shutdown", NULL);
19adb8a3
ZJS
95
96 if (hybrid_state)
dabeaa46 97 USE(states, hybrid_state);
19adb8a3 98 else
dabeaa46 99 states = strv_new("disk", NULL);
9aa2e409
ZJS
100
101 } else if (streq(verb, "suspend-to-hibernate"))
102 modes = states = NULL;
103 else
19adb8a3
ZJS
104 assert_not_reached("what verb");
105
9aa2e409 106 if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
c58493c0 107 (!states && !streq(verb, "suspend-to-hibernate"))) {
dabeaa46
ZJS
108 strv_free(modes);
109 strv_free(states);
19adb8a3
ZJS
110 return log_oom();
111 }
112
c58493c0
ML
113 if (_modes)
114 *_modes = modes;
115 if (_states)
116 *_states = states;
117 if (_delay)
118 *_delay = delay;
119
19adb8a3
ZJS
120 return 0;
121}
122
123int can_sleep_state(char **types) {
a2a5291b 124 char **type;
19adb8a3
ZJS
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) {
a2a5291b 140 const char *word, *state;
19adb8a3
ZJS
141 size_t l, k;
142
143 k = strlen(*type);
a2a5291b
ZJS
144 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
145 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
146 return true;
147 }
148
149 return false;
150}
151
152int can_sleep_disk(char **types) {
a2a5291b 153 char **type;
19adb8a3
ZJS
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) {
a2a5291b 169 const char *word, *state;
19adb8a3
ZJS
170 size_t l, k;
171
172 k = strlen(*type);
a2a5291b
ZJS
173 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
174 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
175 return true;
176
a2a5291b
ZJS
177 if (l == k + 2 &&
178 word[0] == '[' &&
179 memcmp(word + 1, *type, l - 2) == 0 &&
180 word[l-1] == ']')
19adb8a3
ZJS
181 return true;
182 }
183 }
184
185 return false;
186}
187
69ab8088
ZJS
188#define HIBERNATION_SWAP_THRESHOLD 0.98
189
9fb3675e
ZJS
190static int hibernation_partition_size(size_t *size, size_t *used) {
191 _cleanup_fclose_ FILE *f;
1fa2f38f 192 unsigned i;
9fb3675e
ZJS
193
194 assert(size);
195 assert(used);
196
c8a202b7 197 f = fopen("/proc/swaps", "re");
9fb3675e
ZJS
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 }
69ab8088 204
9fb3675e
ZJS
205 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
206
207 for (i = 1;; i++) {
db69869f 208 _cleanup_free_ char *dev = NULL, *type = NULL;
9fb3675e
ZJS
209 size_t size_field, used_field;
210 int k;
211
212 k = fscanf(f,
213 "%ms " /* device/file */
214 "%ms " /* type of swap */
1fa2f38f
ZJS
215 "%zu " /* swap size */
216 "%zu " /* used */
9fb3675e
ZJS
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
db69869f
DR
227 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
228 log_warning("Ignoring deleted swapfile '%s'.", dev);
9fb3675e
ZJS
229 continue;
230 }
231
232 *size = size_field;
233 *used = used_field;
234 return 0;
69ab8088
ZJS
235 }
236
9fb3675e
ZJS
237 log_debug("No swap partitions were found.");
238 return -ENOSYS;
239}
240
241static bool enough_memory_for_hibernation(void) {
242 _cleanup_free_ char *active = NULL;
39883f62
LP
243 unsigned long long act = 0;
244 size_t size = 0, used = 0;
9fb3675e
ZJS
245 int r;
246
490d20e6
VV
247 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
248 return true;
249
9fb3675e
ZJS
250 r = hibernation_partition_size(&size, &used);
251 if (r < 0)
69ab8088 252 return false;
69ab8088 253
c4cd1d4d 254 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
69ab8088 255 if (r < 0) {
da927ba9 256 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
69ab8088
ZJS
257 return false;
258 }
259
260 r = safe_atollu(active, &act);
261 if (r < 0) {
c33b3297
MS
262 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
263 active);
69ab8088
ZJS
264 return false;
265 }
266
9fb3675e
ZJS
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);
69ab8088
ZJS
270
271 return r;
272}
273
c58493c0
ML
274static 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
19adb8a3
ZJS
299int can_sleep(const char *verb) {
300 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
301 int r;
302
9aa2e409 303 assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
c58493c0
ML
304
305 if (streq(verb, "suspend-to-hibernate"))
306 return can_s2h();
19adb8a3 307
c58493c0 308 r = parse_sleep_config(verb, &modes, &states, NULL);
19adb8a3
ZJS
309 if (r < 0)
310 return false;
311
69ab8088
ZJS
312 if (!can_sleep_state(states) || !can_sleep_disk(modes))
313 return false;
314
315 return streq(verb, "suspend") || enough_memory_for_hibernation();
19adb8a3 316}