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