chiark / gitweb /
shared/sleep-config: get rid of explicit allocation size calculation
[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_memory_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         int r;
372
373         r = access("/sys/class/rtc/rtc0/wakealarm", W_OK);
374         if (r < 0) {
375                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
376                          "/sys/class/rct/rct0/wakealarm is not writable %m");
377                 return false;
378         }
379
380         r = can_sleep("suspend");
381         if (r < 0) {
382                 log_debug_errno(r, "Unable to suspend system.");
383                 return false;
384         }
385
386         r = can_sleep("hibernate");
387         if (r < 0) {
388                 log_debug_errno(r, "Unable to hibernate system.");
389                 return false;
390         }
391
392         return true;
393 }
394 #else
395 int can_sleep(Manager *m, const char *verb) {
396
397         assert(streq(verb, "suspend") ||
398                streq(verb, "hibernate") ||
399                streq(verb, "hybrid-sleep"));
400 int can_sleep(const char *verb) {
401         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
402         int r;
403
404         if ( streq(verb, "suspend")
405           && ( !can_sleep_state(m->suspend_state)
406             || !can_sleep_disk(m->suspend_mode) ) )
407                 return false;
408         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
409         assert(STR_IN_SET(verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
410
411         if ( streq(verb, "hibernate")
412           && ( !can_sleep_state(m->hibernate_state)
413             || !can_sleep_disk(m->hibernate_mode) ) )
414                 return false;
415         if (streq(verb, "suspend-to-hibernate"))
416         if (streq(verb, "suspend-then-hibernate"))
417                 return can_s2h();
418
419         if ( streq(verb, "hybrid-sleep")
420           && ( !can_sleep_state(m->hybrid_sleep_state)
421             || !can_sleep_disk(m->hybrid_sleep_mode) ) )
422         r = parse_sleep_config(verb, &modes, &states, NULL);
423         if (r < 0)
424                 return false;
425
426         if (!can_sleep_state(states) || !can_sleep_disk(modes))
427                 return false;
428
429         return streq(verb, "suspend") || enough_memory_for_hibernation();
430 }
431 #endif // 0