1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
6 Copyright 2018 Dell Inc.
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/>.
30 #include "alloc-util.h"
31 #include "conf-parser.h"
38 #include "parse-util.h"
39 #include "sleep-config.h"
40 #include "string-util.h"
43 #define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
45 int parse_sleep_config(const char *verb
, char ***_modes
, char ***_states
, usec_t
*_delay
) {
47 _cleanup_strv_free_
char
48 **suspend_mode
= NULL
, **suspend_state
= NULL
,
49 **hibernate_mode
= NULL
, **hibernate_state
= NULL
,
50 **hybrid_mode
= NULL
, **hybrid_state
= NULL
;
51 char **modes
, **states
;
52 usec_t delay
= 180 * USEC_PER_MINUTE
;
54 const ConfigTableItem items
[] = {
55 { "Sleep", "SuspendMode", config_parse_strv
, 0, &suspend_mode
},
56 { "Sleep", "SuspendState", config_parse_strv
, 0, &suspend_state
},
57 { "Sleep", "HibernateMode", config_parse_strv
, 0, &hibernate_mode
},
58 { "Sleep", "HibernateState", config_parse_strv
, 0, &hibernate_state
},
59 { "Sleep", "HybridSleepMode", config_parse_strv
, 0, &hybrid_mode
},
60 { "Sleep", "HybridSleepState", config_parse_strv
, 0, &hybrid_state
},
61 { "Sleep", "HibernateDelaySec", config_parse_sec
, 0, &delay
},
65 (void) config_parse_many_nulstr(PKGSYSCONFDIR
"/sleep.conf",
66 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
67 "Sleep\0", config_item_table_lookup
, items
,
68 CONFIG_PARSE_WARN
, NULL
);
70 if (streq(verb
, "suspend")) {
71 /* empty by default */
72 USE(modes
, suspend_mode
);
75 USE(states
, suspend_state
);
77 states
= strv_new("mem", "standby", "freeze", NULL
);
79 } else if (streq(verb
, "hibernate")) {
81 USE(modes
, hibernate_mode
);
83 modes
= strv_new("platform", "shutdown", NULL
);
86 USE(states
, hibernate_state
);
88 states
= strv_new("disk", NULL
);
90 } else if (streq(verb
, "hybrid-sleep")) {
92 USE(modes
, hybrid_mode
);
94 modes
= strv_new("suspend", "platform", "shutdown", NULL
);
97 USE(states
, hybrid_state
);
99 states
= strv_new("disk", NULL
);
101 } else if (streq(verb
, "suspend-to-hibernate"))
102 modes
= states
= NULL
;
104 assert_not_reached("what verb");
106 if ((!modes
&& STR_IN_SET(verb
, "hibernate", "hybrid-sleep")) ||
107 (!states
&& !streq(verb
, "suspend-to-hibernate"))) {
123 int can_sleep_state(char **types
) {
126 _cleanup_free_
char *p
= NULL
;
128 if (strv_isempty(types
))
131 /* If /sys is read-only we cannot sleep */
132 if (access("/sys/power/state", W_OK
) < 0)
135 r
= read_one_line_file("/sys/power/state", &p
);
139 STRV_FOREACH(type
, types
) {
140 const char *word
, *state
;
144 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
)
145 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
152 int can_sleep_disk(char **types
) {
155 _cleanup_free_
char *p
= NULL
;
157 if (strv_isempty(types
))
160 /* If /sys is read-only we cannot sleep */
161 if (access("/sys/power/disk", W_OK
) < 0)
164 r
= read_one_line_file("/sys/power/disk", &p
);
168 STRV_FOREACH(type
, types
) {
169 const char *word
, *state
;
173 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
) {
174 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
179 memcmp(word
+ 1, *type
, l
- 2) == 0 &&
188 #define HIBERNATION_SWAP_THRESHOLD 0.98
190 static int hibernation_partition_size(size_t *size
, size_t *used
) {
191 _cleanup_fclose_
FILE *f
;
197 f
= fopen("/proc/swaps", "re");
199 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
200 "Failed to retrieve open /proc/swaps: %m");
205 (void) fscanf(f
, "%*s %*s %*s %*s %*s\n");
208 _cleanup_free_
char *dev
= NULL
, *type
= NULL
;
209 size_t size_field
, used_field
;
213 "%ms " /* device/file */
214 "%ms " /* type of swap */
215 "%zu " /* swap size */
217 "%*i\n", /* priority */
218 &dev
, &type
, &size_field
, &used_field
);
223 log_warning("Failed to parse /proc/swaps:%u", i
);
227 if (streq(type
, "partition") && endswith(dev
, "\\040(deleted)")) {
228 log_warning("Ignoring deleted swapfile '%s'.", dev
);
237 log_debug("No swap partitions were found.");
241 static bool enough_memory_for_hibernation(void) {
242 _cleanup_free_
char *active
= NULL
;
243 unsigned long long act
= 0;
244 size_t size
= 0, used
= 0;
247 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
250 r
= hibernation_partition_size(&size
, &used
);
254 r
= get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE
, &active
);
256 log_error_errno(r
, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
260 r
= safe_atollu(active
, &act
);
262 log_error_errno(r
, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
267 r
= act
<= (size
- used
) * HIBERNATION_SWAP_THRESHOLD
;
268 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
269 r
? "" : "im", act
, size
, used
, 100*HIBERNATION_SWAP_THRESHOLD
);
274 static bool can_s2h(void) {
277 r
= access("/sys/class/rtc/rtc0/wakealarm", W_OK
);
279 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
280 "/sys/class/rct/rct0/wakealarm is not writable %m");
284 r
= can_sleep("suspend");
286 log_debug_errno(r
, "Unable to suspend system.");
290 r
= can_sleep("hibernate");
292 log_debug_errno(r
, "Unable to hibernate system.");
299 int can_sleep(const char *verb
) {
300 _cleanup_strv_free_
char **modes
= NULL
, **states
= NULL
;
303 assert(STR_IN_SET(verb
, "suspend", "hibernate", "hybrid-sleep", "suspend-to-hibernate"));
305 if (streq(verb
, "suspend-to-hibernate"))
308 r
= parse_sleep_config(verb
, &modes
, &states
, NULL
);
312 if (!can_sleep_state(states
) || !can_sleep_disk(modes
))
315 return streq(verb
, "suspend") || enough_memory_for_hibernation();