1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Zbigniew Jędrzejewski-Szmek
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.
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.
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/>.
24 #include "conf-parser.h"
25 #include "sleep-config.h"
31 #define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
33 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
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;
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 },
51 config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", NULL,
53 config_item_table_lookup, items, false, false, true, NULL);
55 if (streq(verb, "suspend")) {
56 /* empty by default */
57 USE(modes, suspend_mode);
60 USE(states, suspend_state);
62 states = strv_new("mem", "standby", "freeze", NULL);
64 } else if (streq(verb, "hibernate")) {
66 USE(modes, hibernate_mode);
68 modes = strv_new("platform", "shutdown", NULL);
71 USE(states, hibernate_state);
73 states = strv_new("disk", NULL);
75 } else if (streq(verb, "hybrid-sleep")) {
77 USE(modes, hybrid_mode);
79 modes = strv_new("suspend", "platform", "shutdown", NULL);
82 USE(states, hybrid_state);
84 states = strv_new("disk", NULL);
87 assert_not_reached("what verb");
89 if ((!modes && !streq(verb, "suspend")) || !states) {
100 int can_sleep_state(char **types) {
101 char *w, *state, **type;
103 _cleanup_free_ char *p = NULL;
105 if (strv_isempty(types))
108 /* If /sys is read-only we cannot sleep */
109 if (access("/sys/power/state", W_OK) < 0)
112 r = read_one_line_file("/sys/power/state", &p);
116 STRV_FOREACH(type, types) {
120 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
121 if (l == k && memcmp(w, *type, l) == 0)
128 int can_sleep_disk(char **types) {
129 char *w, *state, **type;
131 _cleanup_free_ char *p = NULL;
133 if (strv_isempty(types))
136 /* If /sys is read-only we cannot sleep */
137 if (access("/sys/power/disk", W_OK) < 0)
140 r = read_one_line_file("/sys/power/disk", &p);
144 STRV_FOREACH(type, types) {
148 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
149 if (l == k && memcmp(w, *type, l) == 0)
152 if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
160 #define HIBERNATION_SWAP_THRESHOLD 0.98
162 static int hibernation_partition_size(size_t *size, size_t *used) {
163 _cleanup_fclose_ FILE *f;
169 f = fopen("/proc/swaps", "re");
171 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
172 "Failed to retrieve open /proc/swaps: %m");
177 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
180 _cleanup_free_ char *dev = NULL, *type = NULL;
181 size_t size_field, used_field;
185 "%ms " /* device/file */
186 "%ms " /* type of swap */
187 "%zd " /* swap size */
189 "%*i\n", /* priority */
190 &dev, &type, &size_field, &used_field);
195 log_warning("Failed to parse /proc/swaps:%u", i);
199 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
200 log_warning("Ignoring deleted swapfile '%s'.", dev);
209 log_debug("No swap partitions were found.");
213 static bool enough_memory_for_hibernation(void) {
214 _cleanup_free_ char *active = NULL;
215 unsigned long long act = 0;
216 size_t size = 0, used = 0;
219 r = hibernation_partition_size(&size, &used);
223 r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
225 log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r));
229 r = safe_atollu(active, &act);
231 log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s",
232 active, strerror(-r));
236 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
237 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
238 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
243 int can_sleep(const char *verb) {
244 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
247 assert(streq(verb, "suspend") ||
248 streq(verb, "hibernate") ||
249 streq(verb, "hybrid-sleep"));
251 r = parse_sleep_config(verb, &modes, &states);
255 if (!can_sleep_state(states) || !can_sleep_disk(modes))
258 return streq(verb, "suspend") || enough_memory_for_hibernation();