chiark / gitweb /
Prep v229: elogind should honor its Sleep configuration.
[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 "fd-util.h"
32 #include "fileio.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "parse-util.h"
36 #include "sleep-config.h"
37 #include "string-util.h"
38 #include "strv.h"
39
40 #define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
41
42 #if 0 /// UNNEEDED by elogind
43 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
44 #else
45 /// really only used in here.
46 static int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
47 #endif // 0
48         _cleanup_strv_free_ char
49                 **suspend_mode = NULL, **suspend_state = NULL,
50                 **hibernate_mode = NULL, **hibernate_state = NULL,
51                 **hybrid_mode = NULL, **hybrid_state = NULL;
52         char **modes, **states;
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                 {}
62         };
63
64 #if 0 /// elogind has its own config file
65         config_parse_many(PKGSYSCONFDIR "/sleep.conf",
66                           CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
67                           "Sleep\0", config_item_table_lookup, items,
68                           false, NULL);
69 #else
70         const char* logind_conf = getenv("ELOGIND_CONF_FILE");
71         if (!logind_conf)
72                 logind_conf = PKGSYSCONFDIR "/logind.conf";
73         config_parse(NULL, logind_conf, NULL, "Sleep\0",
74                      config_item_table_lookup, items, false,
75                      false, true, NULL);
76 #endif // 0
77
78         if (streq(verb, "suspend")) {
79                 /* empty by default */
80                 USE(modes, suspend_mode);
81
82                 if (suspend_state)
83                         USE(states, suspend_state);
84                 else
85                         states = strv_new("mem", "standby", "freeze", NULL);
86
87         } else if (streq(verb, "hibernate")) {
88                 if (hibernate_mode)
89                         USE(modes, hibernate_mode);
90                 else
91                         modes = strv_new("platform", "shutdown", NULL);
92
93                 if (hibernate_state)
94                         USE(states, hibernate_state);
95                 else
96                         states = strv_new("disk", NULL);
97
98         } else if (streq(verb, "hybrid-sleep")) {
99                 if (hybrid_mode)
100                         USE(modes, hybrid_mode);
101                 else
102                         modes = strv_new("suspend", "platform", "shutdown", NULL);
103
104                 if (hybrid_state)
105                         USE(states, hybrid_state);
106                 else
107                         states = strv_new("disk", NULL);
108
109         } else
110                 assert_not_reached("what verb");
111
112         if ((!modes && !streq(verb, "suspend")) || !states) {
113                 strv_free(modes);
114                 strv_free(states);
115                 return log_oom();
116         }
117
118         *_modes = modes;
119         *_states = states;
120         return 0;
121 }
122
123 int can_sleep_state(char **types) {
124         char **type;
125         int r;
126         _cleanup_free_ char *p = NULL;
127
128         if (strv_isempty(types))
129                 return true;
130
131         /* If /sys is read-only we cannot sleep */
132         if (access("/sys/power/state", W_OK) < 0)
133                 return false;
134
135         r = read_one_line_file("/sys/power/state", &p);
136         if (r < 0)
137                 return false;
138
139         STRV_FOREACH(type, types) {
140                 const char *word, *state;
141                 size_t l, k;
142
143                 k = strlen(*type);
144                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
145                         if (l == k && memcmp(word, *type, l) == 0)
146                                 return true;
147         }
148
149         return false;
150 }
151
152 int can_sleep_disk(char **types) {
153         char **type;
154         int r;
155         _cleanup_free_ char *p = NULL;
156
157         if (strv_isempty(types))
158                 return true;
159
160         /* If /sys is read-only we cannot sleep */
161         if (access("/sys/power/disk", W_OK) < 0)
162                 return false;
163
164         r = read_one_line_file("/sys/power/disk", &p);
165         if (r < 0)
166                 return false;
167
168         STRV_FOREACH(type, types) {
169                 const char *word, *state;
170                 size_t l, k;
171
172                 k = strlen(*type);
173                 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
174                         if (l == k && memcmp(word, *type, l) == 0)
175                                 return true;
176
177                         if (l == k + 2 &&
178                             word[0] == '[' &&
179                             memcmp(word + 1, *type, l - 2) == 0 &&
180                             word[l-1] == ']')
181                                 return true;
182                 }
183         }
184
185         return false;
186 }
187
188 #define HIBERNATION_SWAP_THRESHOLD 0.98
189
190 static int hibernation_partition_size(size_t *size, size_t *used) {
191         _cleanup_fclose_ FILE *f;
192         unsigned i;
193
194         assert(size);
195         assert(used);
196
197         f = fopen("/proc/swaps", "re");
198         if (!f) {
199                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
200                          "Failed to retrieve open /proc/swaps: %m");
201                 assert(errno > 0);
202                 return -errno;
203         }
204
205         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
206
207         for (i = 1;; i++) {
208                 _cleanup_free_ char *dev = NULL, *type = NULL;
209                 size_t size_field, used_field;
210                 int k;
211
212                 k = fscanf(f,
213                            "%ms "   /* device/file */
214                            "%ms "   /* type of swap */
215                            "%zu "   /* swap size */
216                            "%zu "   /* used */
217                            "%*i\n", /* priority */
218                            &dev, &type, &size_field, &used_field);
219                 if (k != 4) {
220                         if (k == EOF)
221                                 break;
222
223                         log_warning("Failed to parse /proc/swaps:%u", i);
224                         continue;
225                 }
226
227                 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
228                         log_warning("Ignoring deleted swapfile '%s'.", dev);
229                         continue;
230                 }
231
232                 *size = size_field;
233                 *used = used_field;
234                 return 0;
235         }
236
237         log_debug("No swap partitions were found.");
238         return -ENOSYS;
239 }
240
241 static bool enough_memory_for_hibernation(void) {
242         _cleanup_free_ char *active = NULL;
243         unsigned long long act = 0;
244         size_t size = 0, used = 0;
245         int r;
246
247         r = hibernation_partition_size(&size, &used);
248         if (r < 0)
249                 return false;
250
251         r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
252         if (r < 0) {
253                 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
254                 return false;
255         }
256
257         r = safe_atollu(active, &act);
258         if (r < 0) {
259                 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
260                                 active);
261                 return false;
262         }
263
264         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
265         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
266                   r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
267
268         return r;
269 }
270
271 int can_sleep(const char *verb) {
272         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
273         int r;
274
275         assert(streq(verb, "suspend") ||
276                streq(verb, "hibernate") ||
277                streq(verb, "hybrid-sleep"));
278
279         r = parse_sleep_config(verb, &modes, &states);
280         if (r < 0)
281                 return false;
282
283         if (!can_sleep_state(states) || !can_sleep_disk(modes))
284                 return false;
285
286         return streq(verb, "suspend") || enough_memory_for_hibernation();
287 }