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