]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/sleep-config.c
Add SPDX license identifiers to source files under the LGPL
[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
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stdbool.h>
23 #include <stddef.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <syslog.h>
27 #include <unistd.h>
28
29 #include "alloc-util.h"
30 #include "conf-parser.h"
31 #include "def.h"
32 #include "env-util.h"
33 #include "fd-util.h"
34 #include "fileio.h"
35 #include "log.h"
36 #include "macro.h"
37 #include "parse-util.h"
38 #include "sleep-config.h"
39 #include "string-util.h"
40 #include "strv.h"
41
42 #define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
43
44 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
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
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 {}
60 };
61
62 (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
63 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
64 "Sleep\0", config_item_table_lookup, items,
65 CONFIG_PARSE_WARN, NULL);
66
67 if (streq(verb, "suspend")) {
68 /* empty by default */
69 USE(modes, suspend_mode);
70
71 if (suspend_state)
72 USE(states, suspend_state);
73 else
74 states = strv_new("mem", "standby", "freeze", NULL);
75
76 } else if (streq(verb, "hibernate")) {
77 if (hibernate_mode)
78 USE(modes, hibernate_mode);
79 else
80 modes = strv_new("platform", "shutdown", NULL);
81
82 if (hibernate_state)
83 USE(states, hibernate_state);
84 else
85 states = strv_new("disk", NULL);
86
87 } else if (streq(verb, "hybrid-sleep")) {
88 if (hybrid_mode)
89 USE(modes, hybrid_mode);
90 else
91 modes = strv_new("suspend", "platform", "shutdown", NULL);
92
93 if (hybrid_state)
94 USE(states, hybrid_state);
95 else
96 states = strv_new("disk", NULL);
97
98 } else
99 assert_not_reached("what verb");
100
101 if ((!modes && !streq(verb, "suspend")) || !states) {
102 strv_free(modes);
103 strv_free(states);
104 return log_oom();
105 }
106
107 *_modes = modes;
108 *_states = states;
109 return 0;
110 }
111
112 int can_sleep_state(char **types) {
113 char **type;
114 int r;
115 _cleanup_free_ char *p = NULL;
116
117 if (strv_isempty(types))
118 return true;
119
120 /* If /sys is read-only we cannot sleep */
121 if (access("/sys/power/state", W_OK) < 0)
122 return false;
123
124 r = read_one_line_file("/sys/power/state", &p);
125 if (r < 0)
126 return false;
127
128 STRV_FOREACH(type, types) {
129 const char *word, *state;
130 size_t l, k;
131
132 k = strlen(*type);
133 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
134 if (l == k && memcmp(word, *type, l) == 0)
135 return true;
136 }
137
138 return false;
139 }
140
141 int can_sleep_disk(char **types) {
142 char **type;
143 int r;
144 _cleanup_free_ char *p = NULL;
145
146 if (strv_isempty(types))
147 return true;
148
149 /* If /sys is read-only we cannot sleep */
150 if (access("/sys/power/disk", W_OK) < 0)
151 return false;
152
153 r = read_one_line_file("/sys/power/disk", &p);
154 if (r < 0)
155 return false;
156
157 STRV_FOREACH(type, types) {
158 const char *word, *state;
159 size_t l, k;
160
161 k = strlen(*type);
162 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
163 if (l == k && memcmp(word, *type, l) == 0)
164 return true;
165
166 if (l == k + 2 &&
167 word[0] == '[' &&
168 memcmp(word + 1, *type, l - 2) == 0 &&
169 word[l-1] == ']')
170 return true;
171 }
172 }
173
174 return false;
175 }
176
177 #define HIBERNATION_SWAP_THRESHOLD 0.98
178
179 static int hibernation_partition_size(size_t *size, size_t *used) {
180 _cleanup_fclose_ FILE *f;
181 unsigned i;
182
183 assert(size);
184 assert(used);
185
186 f = fopen("/proc/swaps", "re");
187 if (!f) {
188 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
189 "Failed to retrieve open /proc/swaps: %m");
190 assert(errno > 0);
191 return -errno;
192 }
193
194 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
195
196 for (i = 1;; i++) {
197 _cleanup_free_ char *dev = NULL, *type = NULL;
198 size_t size_field, used_field;
199 int k;
200
201 k = fscanf(f,
202 "%ms " /* device/file */
203 "%ms " /* type of swap */
204 "%zu " /* swap size */
205 "%zu " /* used */
206 "%*i\n", /* priority */
207 &dev, &type, &size_field, &used_field);
208 if (k != 4) {
209 if (k == EOF)
210 break;
211
212 log_warning("Failed to parse /proc/swaps:%u", i);
213 continue;
214 }
215
216 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
217 log_warning("Ignoring deleted swapfile '%s'.", dev);
218 continue;
219 }
220
221 *size = size_field;
222 *used = used_field;
223 return 0;
224 }
225
226 log_debug("No swap partitions were found.");
227 return -ENOSYS;
228 }
229
230 static bool enough_memory_for_hibernation(void) {
231 _cleanup_free_ char *active = NULL;
232 unsigned long long act = 0;
233 size_t size = 0, used = 0;
234 int r;
235
236 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
237 return true;
238
239 r = hibernation_partition_size(&size, &used);
240 if (r < 0)
241 return false;
242
243 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
244 if (r < 0) {
245 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
246 return false;
247 }
248
249 r = safe_atollu(active, &act);
250 if (r < 0) {
251 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
252 active);
253 return false;
254 }
255
256 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
257 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
258 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
259
260 return r;
261 }
262
263 int can_sleep(const char *verb) {
264 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
265 int r;
266
267 assert(streq(verb, "suspend") ||
268 streq(verb, "hibernate") ||
269 streq(verb, "hybrid-sleep"));
270
271 r = parse_sleep_config(verb, &modes, &states);
272 if (r < 0)
273 return false;
274
275 if (!can_sleep_state(states) || !can_sleep_disk(modes))
276 return false;
277
278 return streq(verb, "suspend") || enough_memory_for_hibernation();
279 }