chiark / gitweb /
bus: properly shift cgroup data returned from kdbus by the container's root before...
[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 int parse_sleep_config(const char *verb, char ***modes, char ***states) {
32         _cleanup_strv_free_ char
33                 **suspend_mode = NULL, **suspend_state = NULL,
34                 **hibernate_mode = NULL, **hibernate_state = NULL,
35                 **hybrid_mode = NULL, **hybrid_state = NULL;
36
37         const ConfigTableItem items[] = {
38                 { "Sleep",   "SuspendMode",      config_parse_strv,  0, &suspend_mode  },
39                 { "Sleep",   "SuspendState",     config_parse_strv,  0, &suspend_state },
40                 { "Sleep",   "HibernateMode",    config_parse_strv,  0, &hibernate_mode  },
41                 { "Sleep",   "HibernateState",   config_parse_strv,  0, &hibernate_state },
42                 { "Sleep",   "HybridSleepMode",  config_parse_strv,  0, &hybrid_mode  },
43                 { "Sleep",   "HybridSleepState", config_parse_strv,  0, &hybrid_state },
44                 {}};
45
46         int r;
47         FILE _cleanup_fclose_ *f;
48
49         f = fopen(PKGSYSCONFDIR "/sleep.conf", "re");
50         if (!f)
51                 log_full(errno == ENOENT ? LOG_DEBUG: LOG_WARNING,
52                          "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m");
53         else {
54                 r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0",
55                                  config_item_table_lookup, (void*) items, false, false, NULL);
56                 if (r < 0)
57                         log_warning("Failed to parse configuration file: %s", strerror(-r));
58         }
59
60         if (streq(verb, "suspend")) {
61                 /* empty by default */
62                 *modes = suspend_mode;
63
64                 if (suspend_state)
65                         *states = suspend_state;
66                 else
67                         *states = strv_split_nulstr("mem\0standby\0freeze\0");
68
69                 suspend_mode = suspend_state = NULL;
70         } else if (streq(verb, "hibernate")) {
71                 if (hibernate_mode)
72                         *modes = hibernate_mode;
73                 else
74                         *modes = strv_split_nulstr("platform\0shutdown\0");
75
76                 if (hibernate_state)
77                         *states = hibernate_state;
78                 else
79                         *states = strv_split_nulstr("disk\0");
80
81                 hibernate_mode = hibernate_state = NULL;
82         } else if (streq(verb, "hybrid-sleep")) {
83                 if (hybrid_mode)
84                         *modes = hybrid_mode;
85                 else
86                         *modes = strv_split_nulstr("suspend\0platform\0shutdown\0");
87
88                 if (hybrid_state)
89                         *states = hybrid_state;
90                 else
91                         *states = strv_split_nulstr("disk\0");
92
93                 hybrid_mode = hybrid_state = NULL;
94         } else
95                 assert_not_reached("what verb");
96
97         if (!modes || !states) {
98                 strv_free(*modes);
99                 strv_free(*states);
100                 return log_oom();
101         }
102
103         return 0;
104 }
105
106 int can_sleep_state(char **types) {
107         char *w, *state, **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                 size_t l, k;
124
125                 k = strlen(*type);
126                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
127                         if (l == k && memcmp(w, *type, l) == 0)
128                                 return true;
129         }
130
131         return false;
132 }
133
134 int can_sleep_disk(char **types) {
135         char *w, *state, **type;
136         int r;
137         _cleanup_free_ char *p = NULL;
138
139         if (strv_isempty(types))
140                 return true;
141
142         /* If /sys is read-only we cannot sleep */
143         if (access("/sys/power/disk", W_OK) < 0)
144                 return false;
145
146         r = read_one_line_file("/sys/power/disk", &p);
147         if (r < 0)
148                 return false;
149
150         STRV_FOREACH(type, types) {
151                 size_t l, k;
152
153                 k = strlen(*type);
154                 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
155                         if (l == k && memcmp(w, *type, l) == 0)
156                                 return true;
157
158                         if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
159                                 return true;
160                 }
161         }
162
163         return false;
164 }
165
166 #define HIBERNATION_SWAP_THRESHOLD 0.98
167
168 static int hibernation_partition_size(size_t *size, size_t *used) {
169         _cleanup_fclose_ FILE *f;
170         int i;
171
172         assert(size);
173         assert(used);
174
175         f = fopen("/proc/swaps", "r");
176         if (!f) {
177                 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
178                          "Failed to retrieve open /proc/swaps: %m");
179                 assert(errno > 0);
180                 return -errno;
181         }
182
183         (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
184
185         for (i = 1;; i++) {
186                 _cleanup_free_ char *dev = NULL, *d = NULL, *type = NULL;
187                 size_t size_field, used_field;
188                 int k;
189
190                 k = fscanf(f,
191                            "%ms "   /* device/file */
192                            "%ms "   /* type of swap */
193                            "%zd "   /* swap size */
194                            "%zd "   /* used */
195                            "%*i\n", /* priority */
196                            &dev, &type, &size_field, &used_field);
197                 if (k != 4) {
198                         if (k == EOF)
199                                 break;
200
201                         log_warning("Failed to parse /proc/swaps:%u", i);
202                         continue;
203                 }
204
205                 d = cunescape(dev);
206                 if (!d)
207                         return -ENOMEM;
208
209                 if (!streq(type, "partition") && !streq(type, "file")) {
210                         log_debug("Partition %s has type %s, ignoring.", d, type);
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;
226         size_t size, used;
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 }