chiark / gitweb /
a57ebacd2a124bf2a74dccf68dffd95f32c2710a
[elogind.git] / src / shared / sleep-config.c
1 /***
2   This file is part of systemd.
3
4   Copyright 2013 Zbigniew JÄ™drzejewski-Szmek
5
6   systemd is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as published by
8   the Free Software Foundation; either version 2.1 of the License, or
9   (at your option) any later version.
10
11   systemd is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   Lesser General Public License for more details.
15
16   You should have received a copy of the GNU Lesser General Public License
17   along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 //#include <errno.h>
21 //#include <stdbool.h>
22 //#include <stddef.h>
23 //#include <stdio.h>
24 //#include <string.h>
25 //#include <syslog.h>
26 //#include <unistd.h>
27
28 #include "alloc-util.h"
29 //#include "conf-parser.h"
30 //#include "def.h"
31 //#include "env-util.h"
32 #include "fd-util.h"
33 #include "fileio.h"
34 //#include "log.h"
35 //#include "macro.h"
36 #include "parse-util.h"
37 #include "sleep-config.h"
38 #include "string-util.h"
39 #include "strv.h"
40
41 #if 0 /// UNNEEDED by elogind
42 #define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
43
44 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
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
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 },
59                 {}
60         };
61
62         config_parse_many_nulstr(PKGSYSCONFDIR "/sleep.conf",
63                           CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
64                           "Sleep\0", config_item_table_lookup, items,
65                           false, NULL);
66
67         if (streq(verb, "suspend")) {
68                 /* empty by default */
69                 USE(modes, suspend_mode);
70
71                 if (suspend_state)
72                         USE(states, suspend_state);
73                 else
74                         states = strv_new("mem", "standby", "freeze", NULL);
75
76         } else if (streq(verb, "hibernate")) {
77                 if (hibernate_mode)
78                         USE(modes, hibernate_mode);
79                 else
80                         modes = strv_new("platform", "shutdown", NULL);
81
82                 if (hibernate_state)
83                         USE(states, hibernate_state);
84                 else
85                         states = strv_new("disk", NULL);
86
87         } else if (streq(verb, "hybrid-sleep")) {
88                 if (hybrid_mode)
89                         USE(modes, hybrid_mode);
90                 else
91                         modes = strv_new("suspend", "platform", "shutdown", NULL);
92
93                 if (hybrid_state)
94                         USE(states, hybrid_state);
95                 else
96                         states = strv_new("disk", NULL);
97
98         } else
99                 assert_not_reached("what verb");
100
101         if ((!modes && !streq(verb, "suspend")) || !states) {
102                 strv_free(modes);
103                 strv_free(states);
104                 return log_oom();
105         }
106
107         *_modes = modes;
108         *_states = states;
109         return 0;
110 }
111 #endif // 0
112
113 #if 1 /// Only available in this file for elogind
114 static
115 #endif // 0
116 int can_sleep_state(char **types) {
117         char **type;
118         int r;
119         _cleanup_free_ char *p = NULL;
120
121         if (strv_isempty(types))
122                 return true;
123
124         /* If /sys is read-only we cannot sleep */
125         if (access("/sys/power/state", W_OK) < 0)
126                 return false;
127
128         r = read_one_line_file("/sys/power/state", &p);
129         if (r < 0)
130                 return false;
131
132         STRV_FOREACH(type, types) {
133                 const char *word, *state;
134                 size_t l, k;
135
136                 k = strlen(*type);
137                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
138                         if (l == k && memcmp(word, *type, l) == 0)
139                                 return true;
140         }
141
142         return false;
143 }
144
145 #if 1 /// Only available in this file for elogind
146 static
147 #endif // 0
148 int can_sleep_disk(char **types) {
149         char **type;
150         int r;
151         _cleanup_free_ char *p = NULL;
152
153         if (strv_isempty(types))
154                 return true;
155
156         /* If /sys is read-only we cannot sleep */
157         if (access("/sys/power/disk", W_OK) < 0)
158                 return false;
159
160         r = read_one_line_file("/sys/power/disk", &p);
161         if (r < 0)
162                 return false;
163
164         STRV_FOREACH(type, types) {
165                 const char *word, *state;
166                 size_t l, k;
167
168                 k = strlen(*type);
169                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
170                         if (l == k && memcmp(word, *type, l) == 0)
171                                 return true;
172
173                         if (l == k + 2 &&
174                             word[0] == '[' &&
175                             memcmp(word + 1, *type, l - 2) == 0 &&
176                             word[l-1] == ']')
177                                 return true;
178                 }
179         }
180
181         return false;
182 }
183
184 #define HIBERNATION_SWAP_THRESHOLD 0.98
185
186 static int hibernation_partition_size(size_t *size, size_t *used) {
187         _cleanup_fclose_ FILE *f;
188         unsigned i;
189
190         assert(size);
191         assert(used);
192
193         f = fopen("/proc/swaps", "re");
194         if (!f) {
195                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
196                          "Failed to retrieve open /proc/swaps: %m");
197                 assert(errno > 0);
198                 return -errno;
199         }
200
201         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
202
203         for (i = 1;; i++) {
204                 _cleanup_free_ char *dev = NULL, *type = NULL;
205                 size_t size_field, used_field;
206                 int k;
207
208                 k = fscanf(f,
209                            "%ms "   /* device/file */
210                            "%ms "   /* type of swap */
211                            "%zu "   /* swap size */
212                            "%zu "   /* used */
213                            "%*i\n", /* priority */
214                            &dev, &type, &size_field, &used_field);
215                 if (k != 4) {
216                         if (k == EOF)
217                                 break;
218
219                         log_warning("Failed to parse /proc/swaps:%u", i);
220                         continue;
221                 }
222
223                 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
224                         log_warning("Ignoring deleted swapfile '%s'.", dev);
225                         continue;
226                 }
227
228                 *size = size_field;
229                 *used = used_field;
230                 return 0;
231         }
232
233         log_debug("No swap partitions were found.");
234         return -ENOSYS;
235 }
236
237 static bool enough_memory_for_hibernation(void) {
238         _cleanup_free_ char *active = NULL;
239         unsigned long long act = 0;
240         size_t size = 0, used = 0;
241         int r;
242
243 #if 0 /// elogind does not allow any bypass, we are never init!
244         if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
245                 return true;
246 #endif // 0
247
248         r = hibernation_partition_size(&size, &used);
249         if (r < 0)
250                 return false;
251
252         r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
253         if (r < 0) {
254                 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
255                 return false;
256         }
257
258         r = safe_atollu(active, &act);
259         if (r < 0) {
260                 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
261                                 active);
262                 return false;
263         }
264
265         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
266         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
267                   r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
268
269         return r;
270 }
271
272 #if 0 /// elogind has to do, or better, *can* do it differently
273 int can_sleep(const char *verb) {
274         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
275         int r;
276
277         assert(streq(verb, "suspend") ||
278                streq(verb, "hibernate") ||
279                streq(verb, "hybrid-sleep"));
280
281         r = parse_sleep_config(verb, &modes, &states);
282         if (r < 0)
283                 return false;
284
285         if (!can_sleep_state(states) || !can_sleep_disk(modes))
286                 return false;
287
288         return streq(verb, "suspend") || enough_memory_for_hibernation();
289 }
290 #else
291 int can_sleep(Manager *m, const char *verb) {
292
293         assert(streq(verb, "suspend") ||
294                streq(verb, "hibernate") ||
295                streq(verb, "hybrid-sleep"));
296
297         if ( streq(verb, "suspend")
298           && ( !can_sleep_state(m->suspend_state)
299             || !can_sleep_disk(m->suspend_mode) ) )
300                 return false;
301
302         if ( streq(verb, "hibernate")
303           && ( !can_sleep_state(m->hibernate_state)
304             || !can_sleep_disk(m->hibernate_mode) ) )
305                 return false;
306
307         if ( streq(verb, "hybrid-sleep")
308           && ( !can_sleep_state(m->hybrid_sleep_state)
309             || !can_sleep_disk(m->hybrid_sleep_mode) ) )
310                 return false;
311
312
313         return streq(verb, "suspend") || enough_memory_for_hibernation();
314 }
315 #endif // 0