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 },
52 FILE _cleanup_fclose_ *f;
54 f = fopen(PKGSYSCONFDIR "/sleep.conf", "re");
56 log_full(errno == ENOENT ? LOG_DEBUG: LOG_WARNING,
57 "Failed to open configuration file " PKGSYSCONFDIR "/sleep.conf: %m");
59 r = config_parse(NULL, PKGSYSCONFDIR "/sleep.conf", f, "Sleep\0",
60 config_item_table_lookup, (void*) items, false, false, NULL);
62 log_warning("Failed to parse configuration file: %s", strerror(-r));
65 if (streq(verb, "suspend")) {
66 /* empty by default */
67 USE(modes, suspend_mode);
70 USE(states, suspend_state);
72 states = strv_new("mem", "standby", "freeze", NULL);
74 } else if (streq(verb, "hibernate")) {
76 USE(modes, hibernate_mode);
78 modes = strv_new("platform", "shutdown", NULL);
81 USE(states, hibernate_state);
83 states = strv_new("disk", NULL);
85 } else if (streq(verb, "hybrid-sleep")) {
87 USE(modes, hybrid_mode);
89 modes = strv_new("suspend", "platform", "shutdown", NULL);
92 USE(states, hybrid_state);
94 states = strv_new("disk", NULL);
97 assert_not_reached("what verb");
99 if ((!modes && !streq(verb, "suspend")) || !states) {
110 int can_sleep_state(char **types) {
111 char *w, *state, **type;
113 _cleanup_free_ char *p = NULL;
115 if (strv_isempty(types))
118 /* If /sys is read-only we cannot sleep */
119 if (access("/sys/power/state", W_OK) < 0)
122 r = read_one_line_file("/sys/power/state", &p);
126 STRV_FOREACH(type, types) {
130 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state)
131 if (l == k && memcmp(w, *type, l) == 0)
138 int can_sleep_disk(char **types) {
139 char *w, *state, **type;
141 _cleanup_free_ char *p = NULL;
143 if (strv_isempty(types))
146 /* If /sys is read-only we cannot sleep */
147 if (access("/sys/power/disk", W_OK) < 0)
150 r = read_one_line_file("/sys/power/disk", &p);
154 STRV_FOREACH(type, types) {
158 FOREACH_WORD_SEPARATOR(w, l, p, WHITESPACE, state) {
159 if (l == k && memcmp(w, *type, l) == 0)
162 if (l == k + 2 && w[0] == '[' && memcmp(w + 1, *type, l - 2) == 0 && w[l-1] == ']')
170 #define HIBERNATION_SWAP_THRESHOLD 0.98
172 static int hibernation_partition_size(size_t *size, size_t *used) {
173 _cleanup_fclose_ FILE *f;
179 f = fopen("/proc/swaps", "re");
181 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
182 "Failed to retrieve open /proc/swaps: %m");
187 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
190 _cleanup_free_ char *dev = NULL, *type = NULL;
191 size_t size_field, used_field;
195 "%ms " /* device/file */
196 "%ms " /* type of swap */
197 "%zd " /* swap size */
199 "%*i\n", /* priority */
200 &dev, &type, &size_field, &used_field);
205 log_warning("Failed to parse /proc/swaps:%u", i);
209 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
210 log_warning("Ignoring deleted swapfile '%s'.", dev);
219 log_debug("No swap partitions were found.");
223 static bool enough_memory_for_hibernation(void) {
224 _cleanup_free_ char *active = NULL;
225 unsigned long long act = 0;
226 size_t size = 0, used = 0;
229 r = hibernation_partition_size(&size, &used);
233 r = get_status_field("/proc/meminfo", "\nActive(anon):", &active);
235 log_error("Failed to retrieve Active(anon) from /proc/meminfo: %s", strerror(-r));
239 r = safe_atollu(active, &act);
241 log_error("Failed to parse Active(anon) from /proc/meminfo: %s: %s",
242 active, strerror(-r));
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);
253 int can_sleep(const char *verb) {
254 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
257 assert(streq(verb, "suspend") ||
258 streq(verb, "hibernate") ||
259 streq(verb, "hybrid-sleep"));
261 r = parse_sleep_config(verb, &modes, &states);
265 if (!can_sleep_state(states) || !can_sleep_disk(modes))
268 return streq(verb, "suspend") || enough_memory_for_hibernation();