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