chiark / gitweb /
missing: simplifications
[elogind.git] / src / shared / sleep-config.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Zbigniew JÄ™drzejewski-Szmek
7
8   systemd 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   systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23
24 #include "conf-parser.h"
25 #include "sleep-config.h"
26 #include "fileio.h"
27 #include "log.h"
28 #include "strv.h"
29 #include "util.h"
30
31 #define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
32
33 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
34         _cleanup_strv_free_ char
35                 **suspend_mode = NULL, **suspend_state = NULL,
36                 **hibernate_mode = NULL, **hibernate_state = NULL,
37                 **hybrid_mode = NULL, **hybrid_state = NULL;
38         char **modes, **states;
39
40         const ConfigTableItem items[] = {
41                 { "Sleep",   "SuspendMode",      config_parse_strv,  0, &suspend_mode  },
42                 { "Sleep",   "SuspendState",     config_parse_strv,  0, &suspend_state },
43                 { "Sleep",   "HibernateMode",    config_parse_strv,  0, &hibernate_mode  },
44                 { "Sleep",   "HibernateState",   config_parse_strv,  0, &hibernate_state },
45                 { "Sleep",   "HybridSleepMode",  config_parse_strv,  0, &hybrid_mode  },
46                 { "Sleep",   "HybridSleepState", config_parse_strv,  0, &hybrid_state },
47                 {}};
48
49         int r;
50         FILE _cleanup_fclose_ *f;
51
52         f = fopen(PKGSYSCONFDIR "/sleep.conf", "re");
53         if (!f)
54                 log_full(errno == ENOENT ? LOG_DEBUG: LOG_WARNING,
55                          "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m");
56         else {
57                 r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0",
58                                  config_item_table_lookup, (void*) items, false, false, NULL);
59                 if (r < 0)
60                         log_warning("Failed to parse configuration file: %s", strerror(-r));
61         }
62
63         if (streq(verb, "suspend")) {
64                 /* empty by default */
65                 USE(modes, suspend_mode);
66
67                 if (suspend_state)
68                         USE(states, suspend_state);
69                 else
70                         states = strv_new("mem", "standby", "freeze", NULL);
71
72         } else if (streq(verb, "hibernate")) {
73                 if (hibernate_mode)
74                         USE(modes, hibernate_mode);
75                 else
76                         modes = strv_new("platform", "shutdown", NULL);
77
78                 if (hibernate_state)
79                         USE(states, hibernate_state);
80                 else
81                         states = strv_new("disk", NULL);
82
83         } else if (streq(verb, "hybrid-sleep")) {
84                 if (hybrid_mode)
85                         USE(modes, hybrid_mode);
86                 else
87                         modes = strv_new("suspend", "platform", "shutdown", NULL);
88
89                 if (hybrid_state)
90                         USE(states, hybrid_state);
91                 else
92                         states = strv_new("disk", NULL);
93
94         } else
95                 assert_not_reached("what verb");
96
97         if ((!modes && !streq(verb, "suspend")) || !states) {
98                 strv_free(modes);
99                 strv_free(states);
100                 return log_oom();
101         }
102
103         *_modes = modes;
104         *_states = states;
105         return 0;
106 }
107
108 int can_sleep_state(char **types) {
109         char *w, *state, **type;
110         int r;
111         _cleanup_free_ char *p = NULL;
112
113         if (strv_isempty(types))
114                 return true;
115
116         /* If /sys is read-only we cannot sleep */
117         if (access("/sys/power/state", W_OK) < 0)
118                 return false;
119
120         r = read_one_line_file("/sys/power/state", &p);
121         if (r < 0)
122                 return false;
123
124         STRV_FOREACH(type, types) {
125                 size_t l, k;
126
127                 k = strlen(*type);
128                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
129                         if (l == k && memcmp(w, *type, l) == 0)
130                                 return true;
131         }
132
133         return false;
134 }
135
136 int can_sleep_disk(char **types) {
137         char *w, *state, **type;
138         int r;
139         _cleanup_free_ char *p = NULL;
140
141         if (strv_isempty(types))
142                 return true;
143
144         /* If /sys is read-only we cannot sleep */
145         if (access("/sys/power/disk", W_OK) < 0)
146                 return false;
147
148         r = read_one_line_file("/sys/power/disk", &p);
149         if (r < 0)
150                 return false;
151
152         STRV_FOREACH(type, types) {
153                 size_t l, k;
154
155                 k = strlen(*type);
156                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
157                         if (l == k && memcmp(w, *type, l) == 0)
158                                 return true;
159
160                         if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
161                                 return true;
162                 }
163         }
164
165         return false;
166 }
167
168 #define HIBERNATION_SWAP_THRESHOLD 0.98
169
170 static int hibernation_partition_size(size_t *size, size_t *used) {
171         _cleanup_fclose_ FILE *f;
172         int i;
173
174         assert(size);
175         assert(used);
176
177         f = fopen("/proc/swaps", "re");
178         if (!f) {
179                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
180                          "Failed to retrieve open /proc/swaps: %m");
181                 assert(errno > 0);
182                 return -errno;
183         }
184
185         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
186
187         for (i = 1;; i++) {
188                 _cleanup_free_ char *dev = NULL, *type = NULL;
189                 size_t size_field, used_field;
190                 int k;
191
192                 k = fscanf(f,
193                            "%ms "   /* device/file */
194                            "%ms "   /* type of swap */
195                            "%zd "   /* swap size */
196                            "%zd "   /* used */
197                            "%*i\n", /* priority */
198                            &dev, &type, &size_field, &used_field);
199                 if (k != 4) {
200                         if (k == EOF)
201                                 break;
202
203                         log_warning("Failed to parse /proc/swaps:%u", i);
204                         continue;
205                 }
206
207                 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
208                         log_warning("Ignoring deleted swapfile '%s'.", dev);
209                         continue;
210                 }
211
212                 *size = size_field;
213                 *used = used_field;
214                 return 0;
215         }
216
217         log_debug("No swap partitions were found.");
218         return -ENOSYS;
219 }
220
221 static bool enough_memory_for_hibernation(void) {
222         _cleanup_free_ char *active = NULL;
223         unsigned long long act = 0;
224         size_t size = 0, used = 0;
225         int r;
226
227         r = hibernation_partition_size(&size, &used);
228         if (r < 0)
229                 return false;
230
231         r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
232         if (r < 0) {
233                 log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r));
234                 return false;
235         }
236
237         r = safe_atollu(active, &act);
238         if (r < 0) {
239                 log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s",
240                           active, strerror(-r));
241                 return false;
242         }
243
244         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
245         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
246                   r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
247
248         return r;
249 }
250
251 int can_sleep(const char *verb) {
252         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
253         int r;
254
255         assert(streq(verb, "suspend") ||
256                streq(verb, "hibernate") ||
257                streq(verb, "hybrid-sleep"));
258
259         r = parse_sleep_config(verb, &modes, &states);
260         if (r < 0)
261                 return false;
262
263         if (!can_sleep_state(states) || !can_sleep_disk(modes))
264                 return false;
265
266         return streq(verb, "suspend") || enough_memory_for_hibernation();
267 }