chiark / gitweb /
Assume that /proc/meminfo can be missing
[elogind.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 bool enough_memory_for_hibernation(void) {
169         _cleanup_free_ char *active = NULL, *swapfree = NULL;
170         unsigned long long act, swap;
171         int r;
172
173         r = get_status_field("/proc/meminfo", "\nSwapFree:", &swapfree);
174         if (r < 0) {
175                 log_full(r == -ENOENT ? LOG_DEBUG : LOG_WARNING,
176                          "Failed to retrieve SwapFree from /proc/meminfo: %s", strerror(-r));
177                 return false;
178         }
179
180         r = safe_atollu(swapfree, &swap);
181         if (r < 0) {
182                 log_error("Failed to parse SwapFree from /proc/meminfo: %s: %s",
183                           swapfree, strerror(-r));
184                 return false;
185         }
186
187         r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
188         if (r < 0) {
189                 log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r));
190                 return false;
191         }
192
193         r = safe_atollu(active, &act);
194         if (r < 0) {
195                 log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s",
196                           active, strerror(-r));
197                 return false;
198         }
199
200         r = act <= swap * HIBERNATION_SWAP_THRESHOLD;
201         log_debug("Hibernation is %spossible, Active(anon)=%llu kB, SwapFree=%llu kB, threshold=%.2g%%",
202                   r ? "" : "im", act, swap, 100*HIBERNATION_SWAP_THRESHOLD);
203
204         return r;
205 }
206
207 int can_sleep(const char *verb) {
208         _cleanup_strv_free_ char **modes = NULL, **states = NULL;
209         int r;
210
211         assert(streq(verb, "suspend") ||
212                streq(verb, "hibernate") ||
213                streq(verb, "hybrid-sleep"));
214
215         r = parse_sleep_config(verb, &modes, &states);
216         if (r < 0)
217                 return false;
218
219         if (!can_sleep_state(states) || !can_sleep_disk(modes))
220                 return false;
221
222         return streq(verb, "suspend") || enough_memory_for_hibernation();
223 }