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