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 "alloc-util.h"
25 #include "conf-parser.h"
30 #include "parse-util.h"
31 #include "sleep-config.h"
32 #include "string-util.h"
36 #define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0)
38 int parse_sleep_config(const char *verb, char ***_modes, char ***_states) {
40 _cleanup_strv_free_ char
41 **suspend_mode = NULL, **suspend_state = NULL,
42 **hibernate_mode = NULL, **hibernate_state = NULL,
43 **hybrid_mode = NULL, **hybrid_state = NULL;
44 char **modes, **states;
46 const ConfigTableItem items[] = {
47 { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode },
48 { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state },
49 { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode },
50 { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state },
51 { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode },
52 { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state },
56 config_parse_many(PKGSYSCONFDIR "/sleep.conf",
57 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
58 "Sleep\0", config_item_table_lookup, items,
61 if (streq(verb, "suspend")) {
62 /* empty by default */
63 USE(modes, suspend_mode);
66 USE(states, suspend_state);
68 states = strv_new("mem", "standby", "freeze", NULL);
70 } else if (streq(verb, "hibernate")) {
72 USE(modes, hibernate_mode);
74 modes = strv_new("platform", "shutdown", NULL);
77 USE(states, hibernate_state);
79 states = strv_new("disk", NULL);
81 } else if (streq(verb, "hybrid-sleep")) {
83 USE(modes, hybrid_mode);
85 modes = strv_new("suspend", "platform", "shutdown", NULL);
88 USE(states, hybrid_state);
90 states = strv_new("disk", NULL);
93 assert_not_reached("what verb");
95 if ((!modes && !streq(verb, "suspend")) || !states) {
106 int can_sleep_state(char **types) {
109 _cleanup_free_ char *p = NULL;
111 if (strv_isempty(types))
114 /* If /sys is read-only we cannot sleep */
115 if (access("/sys/power/state", W_OK) < 0)
118 r = read_one_line_file("/sys/power/state", &p);
122 STRV_FOREACH(type, types) {
123 const char *word, *state;
127 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state)
128 if (l == k && memcmp(word, *type, l) == 0)
135 int can_sleep_disk(char **types) {
138 _cleanup_free_ char *p = NULL;
140 if (strv_isempty(types))
143 /* If /sys is read-only we cannot sleep */
144 if (access("/sys/power/disk", W_OK) < 0)
147 r = read_one_line_file("/sys/power/disk", &p);
151 STRV_FOREACH(type, types) {
152 const char *word, *state;
156 FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) {
157 if (l == k && memcmp(word, *type, l) == 0)
162 memcmp(word + 1, *type, l - 2) == 0 &&
171 #define HIBERNATION_SWAP_THRESHOLD 0.98
173 static int hibernation_partition_size(size_t *size, size_t *used) {
174 _cleanup_fclose_ FILE *f;
180 f = fopen("/proc/swaps", "re");
182 log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING,
183 "Failed to retrieve open /proc/swaps: %m");
188 (void) fscanf(f, "%*s %*s %*s %*s %*s\n");
191 _cleanup_free_ char *dev = NULL, *type = NULL;
192 size_t size_field, used_field;
196 "%ms " /* device/file */
197 "%ms " /* type of swap */
198 "%zu " /* swap size */
200 "%*i\n", /* priority */
201 &dev, &type, &size_field, &used_field);
206 log_warning("Failed to parse /proc/swaps:%u", i);
210 if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) {
211 log_warning("Ignoring deleted swapfile '%s'.", dev);
220 log_debug("No swap partitions were found.");
224 static bool enough_memory_for_hibernation(void) {
225 _cleanup_free_ char *active = NULL;
226 unsigned long long act = 0;
227 size_t size = 0, used = 0;
230 r = hibernation_partition_size(&size, &used);
234 r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active);
236 log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
240 r = safe_atollu(active, &act);
242 log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
247 r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD;
248 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
249 r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD);
254 int can_sleep(const char *verb) {
255 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
258 assert(streq(verb, "suspend") ||
259 streq(verb, "hibernate") ||
260 streq(verb, "hybrid-sleep"));
262 r = parse_sleep_config(verb, &modes, &states);
266 if (!can_sleep_state(states) || !can_sleep_disk(modes))
269 return streq(verb, "suspend") || enough_memory_for_hibernation();