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