chiark / gitweb /
shared/sleep-config: rename misnamed function
[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   elogind 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   elogind 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 elogind; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 //#include <errno.h>
23 //#include <linux/fs.h>
24 //#include <stdbool.h>
25 //#include <stddef.h>
26 //#include <stdio.h>
27 //#include <string.h>
28 //#include <syslog.h>
29 //#include <unistd.h>
30
31 #include "alloc-util.h"
32 //#include "conf-parser.h"
33 //#include "def.h"
34 //#include "env-util.h"
35 #include "fd-util.h"
36 #include "fileio.h"
37 //#include "log.h"
38 //#include "macro.h"
39 #include "parse-util.h"
40 #include "sleep-config.h"
41 #include "string-util.h"
42 #include "strv.h"
43
44 #if 0 /// UNNEEDED by elogind
45 int parse_sleep_config(const char *verb, char ***_modes, char ***_states, usec_t *_delay) {
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         usec_t delay = 180 * USEC_PER_MINUTE;
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 },
61                 { "Sleep",   "HibernateDelaySec", config_parse_sec,  0, &delay},
62                 {}
63         };
64
65         (void) config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
66                                         CONF_PATHS_NULSTR("elogind/sleep.conf.d"),
67                                         "Sleep\0", config_item_table_lookup, items,
68                                         CONFIG_PARSE_WARN, NULL);
69
70         if (streq(verb, "suspend")) {
71                 /* empty by default */
72                 modes = TAKE_PTR(suspend_mode);
73
74                 if (suspend_state)
75                         states = TAKE_PTR(suspend_state);
76                 else
77                         states = strv_new("mem", "standby", "freeze", NULL);
78
79         } else if (streq(verb, "hibernate")) {
80                 if (hibernate_mode)
81                         modes = TAKE_PTR(hibernate_mode);
82                 else
83                         modes = strv_new("platform", "shutdown", NULL);
84
85                 if (hibernate_state)
86                         states = TAKE_PTR(hibernate_state);
87                 else
88                         states = strv_new("disk", NULL);
89
90         } else if (streq(verb, "hybrid-sleep")) {
91                 if (hybrid_mode)
92                         modes = TAKE_PTR(hybrid_mode);
93                 else
94                         modes = strv_new("suspend", "platform", "shutdown", NULL);
95
96                 if (hybrid_state)
97                         states = TAKE_PTR(hybrid_state);
98                 else
99                         states = strv_new("disk", NULL);
100
101         } else if (streq(verb, "suspend-then-hibernate"))
102                 modes = states = NULL;
103         else
104                 assert_not_reached("what verb");
105
106         if ((!modes && STR_IN_SET(verb, "hibernate", "hybrid-sleep")) ||
107             (!states && !streq(verb, "suspend-then-hibernate"))) {
108                 strv_free(modes);
109                 strv_free(states);
110                 return log_oom();
111         }
112
113         if (_modes)
114                 *_modes = modes;
115         if (_states)
116                 *_states = states;
117         if (_delay)
118                 *_delay = delay;
119
120         return 0;
121 }
122 #endif // 0
123
124 #if 1 /// Only available in this file for elogind
125 static
126 #endif // 1
127 int can_sleep_state(char **types) {
128         char **type;
129         int r;
130         _cleanup_free_ char *p = NULL;
131
132         if (strv_isempty(types))
133                 return true;
134
135         /* If /sys is read-only we cannot sleep */
136         if (access("/sys/power/state", W_OK) < 0)
137                 return false;
138
139         r = read_one_line_file("/sys/power/state", &p);
140         if (r < 0)
141                 return false;
142
143         STRV_FOREACH(type, types) {
144                 const char *word, *state;
145                 size_t l, k;
146
147                 k = strlen(*type);
148                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
149                         if (l == k && memcmp(word, *type, l) == 0)
150                                 return true;
151         }
152
153         return false;
154 }
155
156 #if 1 /// Only available in this file for elogind
157 static
158 #endif // 1
159 int can_sleep_disk(char **types) {
160         char **type;
161         int r;
162         _cleanup_free_ char *p = NULL;
163
164         if (strv_isempty(types))
165                 return true;
166
167         /* If /sys is read-only we cannot sleep */
168         if (access("/sys/power/disk", W_OK) < 0)
169                 return false;
170
171         r = read_one_line_file("/sys/power/disk", &p);
172         if (r < 0)
173                 return false;
174
175         STRV_FOREACH(type, types) {
176                 const char *word, *state;
177                 size_t l, k;
178
179                 k = strlen(*type);
180                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
181                         if (l == k && memcmp(word, *type, l) == 0)
182                                 return true;
183
184                         if (l == k + 2 &&
185                             word[0] == '[' &&
186                             memcmp(word + 1, *type, l - 2) == 0 &&
187                             word[l-1] == ']')
188                                 return true;
189                 }
190         }
191
192         return false;
193 }
194
195 #define HIBERNATION_SWAP_THRESHOLD 0.98
196
197 int find_hibernate_location(char **device, char **type, size_t *size, size_t *used) {
198         _cleanup_fclose_ FILE *f;
199         unsigned i;
200
201         f = fopen("/proc/swaps", "re");
202         if (!f) {
203                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
204                          "Failed to retrieve open /proc/swaps: %m");
205                 assert(errno > 0);
206                 return -errno;
207         }
208
209         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
210
211         for (i = 1;; i++) {
212                 _cleanup_free_ char *dev_field = NULL, *type_field = NULL;
213                 size_t size_field, used_field;
214                 int k;
215
216                 k = fscanf(f,
217                            "%ms "   /* device/file */
218                            "%ms "   /* type of swap */
219                            "%zu "   /* swap size */
220                            "%zu "   /* used */
221                            "%*i\n", /* priority */
222                            &dev_field, &type_field, &size_field, &used_field);
223                 if (k != 4) {
224                         if (k == EOF)
225                                 break;
226
227                         log_warning("Failed to parse /proc/swaps:%u", i);
228                         continue;
229                 }
230
231                 if (streq(type_field, "partition") && endswith(dev_field, "\\040(deleted)")) {
232                         log_warning("Ignoring deleted swapfile '%s'.", dev_field);
233                         continue;
234                 }
235                 if (device)
236                         *device = TAKE_PTR(dev_field);
237                 if (type)
238                         *type = TAKE_PTR(type_field);
239                 if (size)
240                         *size = size_field;
241                 if (used)
242                         *used = used_field;
243                 return 0;
244         }
245
246         log_debug("No swap partitions were found.");
247         return -ENOSYS;
248 }
249
250 static bool enough_swap_for_hibernation(void) {
251         _cleanup_free_ char *active = NULL;
252         unsigned long long act = 0;
253         size_t size = 0, used = 0;
254         int r;
255
256 #if 0 /// elogind does not allow any bypass, we are never init!
257         if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
258                 return true;
259 #endif // 0
260
261         r = find_hibernate_location(NULL, NULL, &size, &used);
262         if (r < 0)
263                 return false;
264
265         r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
266         if (r < 0) {
267                 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
268                 return false;
269         }
270
271         r = safe_atollu(active, &act);
272         if (r < 0) {
273                 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
274                                 active);
275                 return false;
276         }
277
278         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
279         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
280                   r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
281
282         return r;
283 }
284
285 int read_fiemap(int fd, struct fiemap **ret) {
286         _cleanup_free_ struct fiemap *fiemap = NULL, *result_fiemap = NULL;
287         struct stat statinfo;
288         uint32_t result_extents = 0;
289         uint64_t fiemap_start = 0, fiemap_length;
290         const size_t n_extra = DIV_ROUND_UP(sizeof(struct fiemap), sizeof(struct fiemap_extent));
291         size_t fiemap_allocated = n_extra, result_fiemap_allocated = n_extra;
292
293         if (fstat(fd, &statinfo) < 0)
294                 return log_debug_errno(errno, "Cannot determine file size: %m");
295         if (!S_ISREG(statinfo.st_mode))
296                 return -ENOTTY;
297         fiemap_length = statinfo.st_size;
298
299         /* Zero this out in case we run on a file with no extents */
300         fiemap = calloc(n_extra, sizeof(struct fiemap_extent));
301         if (!fiemap)
302                 return -ENOMEM;
303
304         result_fiemap = malloc_multiply(n_extra, sizeof(struct fiemap_extent));
305         if (!result_fiemap)
306                 return -ENOMEM;
307
308         /*  XFS filesystem has incorrect implementation of fiemap ioctl and
309          *  returns extents for only one block-group at a time, so we need
310          *  to handle it manually, starting the next fiemap call from the end
311          *  of the last extent
312          */
313         while (fiemap_start < fiemap_length) {
314                 *fiemap = (struct fiemap) {
315                         .fm_start = fiemap_start,
316                         .fm_length = fiemap_length,
317                         .fm_flags = FIEMAP_FLAG_SYNC,
318                 };
319
320                 /* Find out how many extents there are */
321                 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
322                         return log_debug_errno(errno, "Failed to read extents: %m");
323
324                 /* Nothing to process */
325                 if (fiemap->fm_mapped_extents == 0)
326                         break;
327
328                 /* Resize fiemap to allow us to read in the extents, result fiemap has to hold all
329                  * the extents for the whole file. Add space for the initial struct fiemap. */
330                 if (!greedy_realloc0((void**) &fiemap, &fiemap_allocated,
331                                      n_extra + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
332                         return -ENOMEM;
333
334                 fiemap->fm_extent_count = fiemap->fm_mapped_extents;
335                 fiemap->fm_mapped_extents = 0;
336
337                 if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0)
338                         return log_debug_errno(errno, "Failed to read extents: %m");
339
340                 /* Resize result_fiemap to allow us to copy in the extents */
341                 if (!greedy_realloc((void**) &result_fiemap, &result_fiemap_allocated,
342                                     n_extra + result_extents + fiemap->fm_mapped_extents, sizeof(struct fiemap_extent)))
343                         return -ENOMEM;
344
345                 memcpy(result_fiemap->fm_extents + result_extents,
346                        fiemap->fm_extents,
347                        sizeof(struct fiemap_extent) * fiemap->fm_mapped_extents);
348
349                 result_extents += fiemap->fm_mapped_extents;
350
351                 /* Highly unlikely that it is zero */
352                 if (_likely_(fiemap->fm_mapped_extents > 0)) {
353                         uint32_t i = fiemap->fm_mapped_extents - 1;
354
355                         fiemap_start = fiemap->fm_extents[i].fe_logical +
356                                        fiemap->fm_extents[i].fe_length;
357
358                         if (fiemap->fm_extents[i].fe_flags & FIEMAP_EXTENT_LAST)
359                                 break;
360                 }
361         }
362
363         memcpy(result_fiemap, fiemap, sizeof(struct fiemap));
364         result_fiemap->fm_mapped_extents = result_extents;
365         *ret = TAKE_PTR(result_fiemap);
366         return 0;
367 }
368
369 #if 0 /// elogind has to do, or better, *can* do it differently
370 static bool can_s2h(void) {
371         const char *p;
372         int r;
373
374         r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
375         if (r < 0) {
376                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
377                          "/sys/class/rct/rct0/wakealarm is not writable %m");
378                 return false;
379         }
380
381         FOREACH_STRING(p, "suspend", "hibernate") {
382                 r = can_sleep(p);
383                 if (IN_SET(r, 0, -ENOSPC)) {
384                         log_debug("Unable to %s system.", p);
385                         return false;
386                 }
387                 if (r < 0)
388                         return log_debug_errno(r, "Failed to check if %s is possible: %m", p);
389         }
390
391         return true;
392 }
393 #else
394 int can_sleep(Manager *m, const char *verb) {
395
396         assert(streq(verb, "suspend") ||
397                streq(verb, "hibernate") ||
398                streq(verb, "hybrid-sleep"));
399 int can_sleep(const char *verb) {
400         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
401         int r;
402
403         if ( streq(verb, "suspend")
404           && ( !can_sleep_state(m->suspend_state)
405             || !can_sleep_disk(m->suspend_mode) ) )
406                 return false;
407         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
408         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
409
410         if ( streq(verb, "hibernate")
411           && ( !can_sleep_state(m->hibernate_state)
412             || !can_sleep_disk(m->hibernate_mode) ) )
413                 return false;
414         if (streq(verb, "suspend-to-hibernate"))
415         if (streq(verb, "suspend-then-hibernate"))
416                 return can_s2h();
417
418         if ( streq(verb, "hybrid-sleep")
419           && ( !can_sleep_state(m->hybrid_sleep_state)
420             || !can_sleep_disk(m->hybrid_sleep_mode) ) )
421         r = parse_sleep_config(verb, &modes, &states, NULL);
422         if (r < 0)
423                 return false;
424
425         if (!can_sleep_state(states) || !can_sleep_disk(modes))
426                 return false;
427
428         return streq(verb, "suspend") || enough_memory_for_hibernation();
429         if (streq(verb, "suspend"))
430                 return true;
431
432         if (!enough_memory_for_hibernation())
433         if (!enough_swap_for_hibernation())
434                 return -ENOSPC;
435
436         return true;
437 }
438 #endif // 0