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