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