]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/sleep-config.c
Merge pull request #1654 from poettering/util-lib
[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
33 #define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
34
35 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
36
37 _cleanup_strv_free_ char
38 **suspend_mode = NULL, **suspend_state = NULL,
39 **hibernate_mode = NULL, **hibernate_state = NULL,
40 **hybrid_mode = NULL, **hybrid_state = NULL;
41 char **modes, **states;
42
43 const ConfigTableItem items[] = {
44 { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
45 { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
46 { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
47 { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
48 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
49 { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
50 {}
51 };
52
53 config_parse_many(PKGSYSCONFDIR "/sleep.conf",
54 CONF_DIRS_NULSTR("systemd/sleep.conf"),
55 "Sleep\0", config_item_table_lookup, items,
56 false, NULL);
57
58 if (streq(verb, "suspend")) {
59 /* empty by default */
60 USE(modes, suspend_mode);
61
62 if (suspend_state)
63 USE(states, suspend_state);
64 else
65 states = strv_new("mem", "standby", "freeze", NULL);
66
67 } else if (streq(verb, "hibernate")) {
68 if (hibernate_mode)
69 USE(modes, hibernate_mode);
70 else
71 modes = strv_new("platform", "shutdown", NULL);
72
73 if (hibernate_state)
74 USE(states, hibernate_state);
75 else
76 states = strv_new("disk", NULL);
77
78 } else if (streq(verb, "hybrid-sleep")) {
79 if (hybrid_mode)
80 USE(modes, hybrid_mode);
81 else
82 modes = strv_new("suspend", "platform", "shutdown", NULL);
83
84 if (hybrid_state)
85 USE(states, hybrid_state);
86 else
87 states = strv_new("disk", NULL);
88
89 } else
90 assert_not_reached("what verb");
91
92 if ((!modes && !streq(verb, "suspend")) || !states) {
93 strv_free(modes);
94 strv_free(states);
95 return log_oom();
96 }
97
98 *_modes = modes;
99 *_states = states;
100 return 0;
101 }
102
103 int can_sleep_state(char **types) {
104 char **type;
105 int r;
106 _cleanup_free_ char *p = NULL;
107
108 if (strv_isempty(types))
109 return true;
110
111 /* If /sys is read-only we cannot sleep */
112 if (access("/sys/power/state", W_OK) < 0)
113 return false;
114
115 r = read_one_line_file("/sys/power/state", &p);
116 if (r < 0)
117 return false;
118
119 STRV_FOREACH(type, types) {
120 const char *word, *state;
121 size_t l, k;
122
123 k = strlen(*type);
124 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
125 if (l == k && memcmp(word, *type, l) == 0)
126 return true;
127 }
128
129 return false;
130 }
131
132 int can_sleep_disk(char **types) {
133 char **type;
134 int r;
135 _cleanup_free_ char *p = NULL;
136
137 if (strv_isempty(types))
138 return true;
139
140 /* If /sys is read-only we cannot sleep */
141 if (access("/sys/power/disk", W_OK) < 0)
142 return false;
143
144 r = read_one_line_file("/sys/power/disk", &p);
145 if (r < 0)
146 return false;
147
148 STRV_FOREACH(type, types) {
149 const char *word, *state;
150 size_t l, k;
151
152 k = strlen(*type);
153 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
154 if (l == k && memcmp(word, *type, l) == 0)
155 return true;
156
157 if (l == k + 2 &&
158 word[0] == '[' &&
159 memcmp(word + 1, *type, l - 2) == 0 &&
160 word[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 unsigned 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 "%zu " /* swap size */
196 "%zu " /* 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_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
232 if (r < 0) {
233 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
234 return false;
235 }
236
237 r = safe_atollu(active, &act);
238 if (r < 0) {
239 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
240 active);
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 }