chiark / gitweb /
Always check asprintf return code
[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 *w, *state, **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                 size_t l, k;
118
119                 k = strlen(*type);
120                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
121                         if (l == k && memcmp(w, *type, l) == 0)
122                                 return true;
123         }
124
125         return false;
126 }
127
128 int can_sleep_disk(char **types) {
129         char *w, *state, **type;
130         int r;
131         _cleanup_free_ char *p = NULL;
132
133         if (strv_isempty(types))
134                 return true;
135
136         /* If /sys is read-only we cannot sleep */
137         if (access("/sys/power/disk", W_OK) < 0)
138                 return false;
139
140         r = read_one_line_file("/sys/power/disk", &p);
141         if (r < 0)
142                 return false;
143
144         STRV_FOREACH(type, types) {
145                 size_t l, k;
146
147                 k = strlen(*type);
148                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
149                         if (l == k && memcmp(w, *type, l) == 0)
150                                 return true;
151
152                         if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
153                                 return true;
154                 }
155         }
156
157         return false;
158 }
159
160 #define HIBERNATION_SWAP_THRESHOLD 0.98
161
162 static int hibernation_partition_size(size_t *size, size_t *used) {
163         _cleanup_fclose_ FILE *f;
164         int i;
165
166         assert(size);
167         assert(used);
168
169         f = fopen("/proc/swaps", "re");
170         if (!f) {
171                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
172                          "Failed to retrieve open /proc/swaps: %m");
173                 assert(errno > 0);
174                 return -errno;
175         }
176
177         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
178
179         for (i = 1;; i++) {
180                 _cleanup_free_ char *dev = NULL, *type = NULL;
181                 size_t size_field, used_field;
182                 int k;
183
184                 k = fscanf(f,
185                            "%ms "   /* device/file */
186                            "%ms "   /* type of swap */
187                            "%zd "   /* swap size */
188                            "%zd "   /* used */
189                            "%*i\n", /* priority */
190                            &dev, &type, &size_field, &used_field);
191                 if (k != 4) {
192                         if (k == EOF)
193                                 break;
194
195                         log_warning("Failed to parse /proc/swaps:%u", i);
196                         continue;
197                 }
198
199                 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
200                         log_warning("Ignoring deleted swapfile '%s'.", dev);
201                         continue;
202                 }
203
204                 *size = size_field;
205                 *used = used_field;
206                 return 0;
207         }
208
209         log_debug("No swap partitions were found.");
210         return -ENOSYS;
211 }
212
213 static bool enough_memory_for_hibernation(void) {
214         _cleanup_free_ char *active = NULL;
215         unsigned long long act = 0;
216         size_t size = 0, used = 0;
217         int r;
218
219         r = hibernation_partition_size(&size, &used);
220         if (r < 0)
221                 return false;
222
223         r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
224         if (r < 0) {
225                 log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r));
226                 return false;
227         }
228
229         r = safe_atollu(active, &act);
230         if (r < 0) {
231                 log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s",
232                           active, strerror(-r));
233                 return false;
234         }
235
236         r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
237         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
238                   r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
239
240         return r;
241 }
242
243 int can_sleep(const char *verb) {
244         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
245         int r;
246
247         assert(streq(verb, "suspend") ||
248                streq(verb, "hibernate") ||
249                streq(verb, "hybrid-sleep"));
250
251         r = parse_sleep_config(verb, &modes, &states);
252         if (r < 0)
253                 return false;
254
255         if (!can_sleep_state(states) || !can_sleep_disk(modes))
256                 return false;
257
258         return streq(verb, "suspend") || enough_memory_for_hibernation();
259 }