]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/sleep-config.c
Add SPDX license identifiers to source files under the LGPL
[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
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
a8fbdf54
TA
21#include <errno.h>
22#include <stdbool.h>
23#include <stddef.h>
19adb8a3 24#include <stdio.h>
a8fbdf54
TA
25#include <string.h>
26#include <syslog.h>
27#include <unistd.h>
19adb8a3 28
b5efdb8a 29#include "alloc-util.h"
19adb8a3 30#include "conf-parser.h"
a0f29c76 31#include "def.h"
490d20e6 32#include "env-util.h"
3ffd4af2 33#include "fd-util.h"
19adb8a3
ZJS
34#include "fileio.h"
35#include "log.h"
a8fbdf54 36#include "macro.h"
a0f29c76 37#include "parse-util.h"
3ffd4af2 38#include "sleep-config.h"
07630cea 39#include "string-util.h"
19adb8a3 40#include "strv.h"
19adb8a3 41
9ed794a3 42#define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
dabeaa46
ZJS
43
44int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
34c10968 45
19adb8a3
ZJS
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;
dabeaa46 50 char **modes, **states;
19adb8a3
ZJS
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 },
34c10968
LP
59 {}
60 };
19adb8a3 61
bcde742e
LP
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);
19adb8a3 66
19adb8a3
ZJS
67 if (streq(verb, "suspend")) {
68 /* empty by default */
dabeaa46 69 USE(modes, suspend_mode);
19adb8a3
ZJS
70
71 if (suspend_state)
dabeaa46 72 USE(states, suspend_state);
19adb8a3 73 else
dabeaa46 74 states = strv_new("mem", "standby", "freeze", NULL);
19adb8a3 75
19adb8a3
ZJS
76 } else if (streq(verb, "hibernate")) {
77 if (hibernate_mode)
dabeaa46 78 USE(modes, hibernate_mode);
19adb8a3 79 else
dabeaa46 80 modes = strv_new("platform", "shutdown", NULL);
19adb8a3
ZJS
81
82 if (hibernate_state)
dabeaa46 83 USE(states, hibernate_state);
19adb8a3 84 else
dabeaa46 85 states = strv_new("disk", NULL);
19adb8a3 86
19adb8a3
ZJS
87 } else if (streq(verb, "hybrid-sleep")) {
88 if (hybrid_mode)
dabeaa46 89 USE(modes, hybrid_mode);
19adb8a3 90 else
dabeaa46 91 modes = strv_new("suspend", "platform", "shutdown", NULL);
19adb8a3
ZJS
92
93 if (hybrid_state)
dabeaa46 94 USE(states, hybrid_state);
19adb8a3 95 else
dabeaa46 96 states = strv_new("disk", NULL);
19adb8a3 97
19adb8a3
ZJS
98 } else
99 assert_not_reached("what verb");
100
dabeaa46
ZJS
101 if ((!modes && !streq(verb, "suspend")) || !states) {
102 strv_free(modes);
103 strv_free(states);
19adb8a3
ZJS
104 return log_oom();
105 }
106
dabeaa46
ZJS
107 *_modes = modes;
108 *_states = states;
19adb8a3
ZJS
109 return 0;
110}
111
112int can_sleep_state(char **types) {
a2a5291b 113 char **type;
19adb8a3
ZJS
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) {
a2a5291b 129 const char *word, *state;
19adb8a3
ZJS
130 size_t l, k;
131
132 k = strlen(*type);
a2a5291b
ZJS
133 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
134 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
135 return true;
136 }
137
138 return false;
139}
140
141int can_sleep_disk(char **types) {
a2a5291b 142 char **type;
19adb8a3
ZJS
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) {
a2a5291b 158 const char *word, *state;
19adb8a3
ZJS
159 size_t l, k;
160
161 k = strlen(*type);
a2a5291b
ZJS
162 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
163 if (l == k && memcmp(word, *type, l) == 0)
19adb8a3
ZJS
164 return true;
165
a2a5291b
ZJS
166 if (l == k + 2 &&
167 word[0] == '[' &&
168 memcmp(word + 1, *type, l - 2) == 0 &&
169 word[l-1] == ']')
19adb8a3
ZJS
170 return true;
171 }
172 }
173
174 return false;
175}
176
69ab8088
ZJS
177#define HIBERNATION_SWAP_THRESHOLD 0.98
178
9fb3675e
ZJS
179static int hibernation_partition_size(size_t *size, size_t *used) {
180 _cleanup_fclose_ FILE *f;
1fa2f38f 181 unsigned i;
9fb3675e
ZJS
182
183 assert(size);
184 assert(used);
185
c8a202b7 186 f = fopen("/proc/swaps", "re");
9fb3675e
ZJS
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 }
69ab8088 193
9fb3675e
ZJS
194 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
195
196 for (i = 1;; i++) {
db69869f 197 _cleanup_free_ char *dev = NULL, *type = NULL;
9fb3675e
ZJS
198 size_t size_field, used_field;
199 int k;
200
201 k = fscanf(f,
202 "%ms " /* device/file */
203 "%ms " /* type of swap */
1fa2f38f
ZJS
204 "%zu " /* swap size */
205 "%zu " /* used */
9fb3675e
ZJS
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
db69869f
DR
216 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
217 log_warning("Ignoring deleted swapfile '%s'.", dev);
9fb3675e
ZJS
218 continue;
219 }
220
221 *size = size_field;
222 *used = used_field;
223 return 0;
69ab8088
ZJS
224 }
225
9fb3675e
ZJS
226 log_debug("No swap partitions were found.");
227 return -ENOSYS;
228}
229
230static bool enough_memory_for_hibernation(void) {
231 _cleanup_free_ char *active = NULL;
39883f62
LP
232 unsigned long long act = 0;
233 size_t size = 0, used = 0;
9fb3675e
ZJS
234 int r;
235
490d20e6
VV
236 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
237 return true;
238
9fb3675e
ZJS
239 r = hibernation_partition_size(&size, &used);
240 if (r < 0)
69ab8088 241 return false;
69ab8088 242
c4cd1d4d 243 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
69ab8088 244 if (r < 0) {
da927ba9 245 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
69ab8088
ZJS
246 return false;
247 }
248
249 r = safe_atollu(active, &act);
250 if (r < 0) {
c33b3297
MS
251 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
252 active);
69ab8088
ZJS
253 return false;
254 }
255
9fb3675e
ZJS
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);
69ab8088
ZJS
259
260 return r;
261}
262
19adb8a3
ZJS
263int 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
69ab8088
ZJS
275 if (!can_sleep_state(states) || !can_sleep_disk(modes))
276 return false;
277
278 return streq(verb, "suspend") || enough_memory_for_hibernation();
19adb8a3 279}