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