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