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_many(PKGSYSCONFDIR "/sleep.conf",
52 CONF_DIRS_NULSTR("systemd/sleep.conf"),
53 "Sleep\0", config_item_table_lookup, items,
56 if (streq(verb, "suspend")) {
57 /* empty by default */
58 USE(modes, suspend_mode);
61 USE(states, suspend_state);
63 states = strv_new("mem", "standby", "freeze", NULL);
65 } else if (streq(verb, "hibernate")) {
67 USE(modes, hibernate_mode);
69 modes = strv_new("platform", "shutdown", NULL);
72 USE(states, hibernate_state);
74 states = strv_new("disk", NULL);
76 } else if (streq(verb, "hybrid-sleep")) {
78 USE(modes, hybrid_mode);
80 modes = strv_new("suspend", "platform", "shutdown", NULL);
83 USE(states, hybrid_state);
85 states = strv_new("disk", NULL);
88 assert_not_reached("what verb");
90 if ((!modes && !streq(verb, "suspend")) || !states) {
101 int can_sleep_state(char **types) {
104 _cleanup_free_ char *p = NULL;
106 if (strv_isempty(types))
109 /* If /sys is read-only we cannot sleep */
110 if (access("/sys/power/state", W_OK) < 0)
113 r = read_one_line_file("/sys/power/state", &p);
117 STRV_FOREACH(type, types) {
118 const char *word, *state;
122 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
123 if (l == k && memcmp(word, *type, l) == 0)
130 int can_sleep_disk(char **types) {
133 _cleanup_free_ char *p = NULL;
135 if (strv_isempty(types))
138 /* If /sys is read-only we cannot sleep */
139 if (access("/sys/power/disk", W_OK) < 0)
142 r = read_one_line_file("/sys/power/disk", &p);
146 STRV_FOREACH(type, types) {
147 const char *word, *state;
151 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
152 if (l == k && memcmp(word, *type, l) == 0)
157 memcmp(word + 1, *type, l - 2) == 0 &&
166 #define HIBERNATION_SWAP_THRESHOLD 0.98
168 static int hibernation_partition_size(size_t *size, size_t *used) {
169 _cleanup_fclose_ FILE *f;
175 f = fopen("/proc/swaps", "re");
177 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
178 "Failed to retrieve open /proc/swaps: %m");
183 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
186 _cleanup_free_ char *dev = NULL, *type = NULL;
187 size_t size_field, used_field;
191 "%ms " /* device/file */
192 "%ms " /* type of swap */
193 "%zd " /* swap size */
195 "%*i\n", /* priority */
196 &dev, &type, &size_field, &used_field);
201 log_warning("Failed to parse /proc/swaps:%u", i);
205 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
206 log_warning("Ignoring deleted swapfile '%s'.", dev);
215 log_debug("No swap partitions were found.");
219 static bool enough_memory_for_hibernation(void) {
220 _cleanup_free_ char *active = NULL;
221 unsigned long long act = 0;
222 size_t size = 0, used = 0;
225 r = hibernation_partition_size(&size, &used);
229 r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
231 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
235 r = safe_atollu(active, &act);
237 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
242 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
243 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
244 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
249 int can_sleep(const char *verb) {
250 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
253 assert(streq(verb, "suspend") ||
254 streq(verb, "hibernate") ||
255 streq(verb, "hybrid-sleep"));
257 r = parse_sleep_config(verb, &modes, &states);
261 if (!can_sleep_state(states) || !can_sleep_disk(modes))
264 return streq(verb, "suspend") || enough_memory_for_hibernation();