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