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