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