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 int parse_sleep_config(const char *verb
, char ***_modes
, char ***_states
, usec_t
*_delay
) {
45 _cleanup_strv_free_
char
46 **suspend_mode
= NULL
, **suspend_state
= NULL
,
47 **hibernate_mode
= NULL
, **hibernate_state
= NULL
,
48 **hybrid_mode
= NULL
, **hybrid_state
= NULL
;
49 char **modes
, **states
;
50 usec_t delay
= 180 * USEC_PER_MINUTE
;
52 const ConfigTableItem items
[] = {
53 { "Sleep", "SuspendMode", config_parse_strv
, 0, &suspend_mode
},
54 { "Sleep", "SuspendState", config_parse_strv
, 0, &suspend_state
},
55 { "Sleep", "HibernateMode", config_parse_strv
, 0, &hibernate_mode
},
56 { "Sleep", "HibernateState", config_parse_strv
, 0, &hibernate_state
},
57 { "Sleep", "HybridSleepMode", config_parse_strv
, 0, &hybrid_mode
},
58 { "Sleep", "HybridSleepState", config_parse_strv
, 0, &hybrid_state
},
59 { "Sleep", "HibernateDelaySec", config_parse_sec
, 0, &delay
},
63 (void) config_parse_many_nulstr(PKGSYSCONFDIR
"/sleep.conf",
64 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
65 "Sleep\0", config_item_table_lookup
, items
,
66 CONFIG_PARSE_WARN
, NULL
);
68 if (streq(verb
, "suspend")) {
69 /* empty by default */
70 modes
= TAKE_PTR(suspend_mode
);
73 states
= TAKE_PTR(suspend_state
);
75 states
= strv_new("mem", "standby", "freeze", NULL
);
77 } else if (streq(verb
, "hibernate")) {
79 modes
= TAKE_PTR(hibernate_mode
);
81 modes
= strv_new("platform", "shutdown", NULL
);
84 states
= TAKE_PTR(hibernate_state
);
86 states
= strv_new("disk", NULL
);
88 } else if (streq(verb
, "hybrid-sleep")) {
90 modes
= TAKE_PTR(hybrid_mode
);
92 modes
= strv_new("suspend", "platform", "shutdown", NULL
);
95 states
= TAKE_PTR(hybrid_state
);
97 states
= strv_new("disk", NULL
);
99 } else if (streq(verb
, "suspend-then-hibernate"))
100 modes
= states
= NULL
;
102 assert_not_reached("what verb");
104 if ((!modes
&& STR_IN_SET(verb
, "hibernate", "hybrid-sleep")) ||
105 (!states
&& !streq(verb
, "suspend-then-hibernate"))) {
121 int can_sleep_state(char **types
) {
124 _cleanup_free_
char *p
= NULL
;
126 if (strv_isempty(types
))
129 /* If /sys is read-only we cannot sleep */
130 if (access("/sys/power/state", W_OK
) < 0)
133 r
= read_one_line_file("/sys/power/state", &p
);
137 STRV_FOREACH(type
, types
) {
138 const char *word
, *state
;
142 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
)
143 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
150 int can_sleep_disk(char **types
) {
153 _cleanup_free_
char *p
= NULL
;
155 if (strv_isempty(types
))
158 /* If /sys is read-only we cannot sleep */
159 if (access("/sys/power/disk", W_OK
) < 0)
162 r
= read_one_line_file("/sys/power/disk", &p
);
166 STRV_FOREACH(type
, types
) {
167 const char *word
, *state
;
171 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
) {
172 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
177 memcmp(word
+ 1, *type
, l
- 2) == 0 &&
186 #define HIBERNATION_SWAP_THRESHOLD 0.98
188 static int hibernation_partition_size(size_t *size
, size_t *used
) {
189 _cleanup_fclose_
FILE *f
;
195 f
= fopen("/proc/swaps", "re");
197 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
198 "Failed to retrieve open /proc/swaps: %m");
203 (void) fscanf(f
, "%*s %*s %*s %*s %*s\n");
206 _cleanup_free_
char *dev
= NULL
, *type
= NULL
;
207 size_t size_field
, used_field
;
211 "%ms " /* device/file */
212 "%ms " /* type of swap */
213 "%zu " /* swap size */
215 "%*i\n", /* priority */
216 &dev
, &type
, &size_field
, &used_field
);
221 log_warning("Failed to parse /proc/swaps:%u", i
);
225 if (streq(type
, "partition") && endswith(dev
, "\\040(deleted)")) {
226 log_warning("Ignoring deleted swapfile '%s'.", dev
);
235 log_debug("No swap partitions were found.");
239 static bool enough_memory_for_hibernation(void) {
240 _cleanup_free_
char *active
= NULL
;
241 unsigned long long act
= 0;
242 size_t size
= 0, used
= 0;
245 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
248 r
= hibernation_partition_size(&size
, &used
);
252 r
= get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE
, &active
);
254 log_error_errno(r
, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
258 r
= safe_atollu(active
, &act
);
260 log_error_errno(r
, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
265 r
= act
<= (size
- used
) * HIBERNATION_SWAP_THRESHOLD
;
266 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
267 r
? "" : "im", act
, size
, used
, 100*HIBERNATION_SWAP_THRESHOLD
);
272 static bool can_s2h(void) {
275 r
= access("/sys/class/rtc/rtc0/wakealarm", W_OK
);
277 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
278 "/sys/class/rct/rct0/wakealarm is not writable %m");
282 r
= can_sleep("suspend");
284 log_debug_errno(r
, "Unable to suspend system.");
288 r
= can_sleep("hibernate");
290 log_debug_errno(r
, "Unable to hibernate system.");
297 int can_sleep(const char *verb
) {
298 _cleanup_strv_free_
char **modes
= NULL
, **states
= NULL
;
301 assert(STR_IN_SET(verb
, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"));
303 if (streq(verb
, "suspend-then-hibernate"))
306 r
= parse_sleep_config(verb
, &modes
, &states
, NULL
);
310 if (!can_sleep_state(states
) || !can_sleep_disk(modes
))
313 return streq(verb
, "suspend") || enough_memory_for_hibernation();