1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 #include "alloc-util.h"
30 #include "conf-parser.h"
37 #include "parse-util.h"
38 #include "sleep-config.h"
39 #include "string-util.h"
42 #define USE(x, y) do { (x) = (y); (y) = NULL; } while (0)
44 int parse_sleep_config(const char *verb
, char ***_modes
, char ***_states
) {
46 _cleanup_strv_free_
char
47 **suspend_mode
= NULL
, **suspend_state
= NULL
,
48 **hibernate_mode
= NULL
, **hibernate_state
= NULL
,
49 **hybrid_mode
= NULL
, **hybrid_state
= NULL
;
50 char **modes
, **states
;
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
},
62 (void) config_parse_many_nulstr(PKGSYSCONFDIR
"/sleep.conf",
63 CONF_PATHS_NULSTR("systemd/sleep.conf.d"),
64 "Sleep\0", config_item_table_lookup
, items
,
65 CONFIG_PARSE_WARN
, NULL
);
67 if (streq(verb
, "suspend")) {
68 /* empty by default */
69 USE(modes
, suspend_mode
);
72 USE(states
, suspend_state
);
74 states
= strv_new("mem", "standby", "freeze", NULL
);
76 } else if (streq(verb
, "hibernate")) {
78 USE(modes
, hibernate_mode
);
80 modes
= strv_new("platform", "shutdown", NULL
);
83 USE(states
, hibernate_state
);
85 states
= strv_new("disk", NULL
);
87 } else if (streq(verb
, "hybrid-sleep")) {
89 USE(modes
, hybrid_mode
);
91 modes
= strv_new("suspend", "platform", "shutdown", NULL
);
94 USE(states
, hybrid_state
);
96 states
= strv_new("disk", NULL
);
99 assert_not_reached("what verb");
101 if ((!modes
&& !streq(verb
, "suspend")) || !states
) {
112 int can_sleep_state(char **types
) {
115 _cleanup_free_
char *p
= NULL
;
117 if (strv_isempty(types
))
120 /* If /sys is read-only we cannot sleep */
121 if (access("/sys/power/state", W_OK
) < 0)
124 r
= read_one_line_file("/sys/power/state", &p
);
128 STRV_FOREACH(type
, types
) {
129 const char *word
, *state
;
133 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
)
134 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
141 int can_sleep_disk(char **types
) {
144 _cleanup_free_
char *p
= NULL
;
146 if (strv_isempty(types
))
149 /* If /sys is read-only we cannot sleep */
150 if (access("/sys/power/disk", W_OK
) < 0)
153 r
= read_one_line_file("/sys/power/disk", &p
);
157 STRV_FOREACH(type
, types
) {
158 const char *word
, *state
;
162 FOREACH_WORD_SEPARATOR(word
, l
, p
, WHITESPACE
, state
) {
163 if (l
== k
&& memcmp(word
, *type
, l
) == 0)
168 memcmp(word
+ 1, *type
, l
- 2) == 0 &&
177 #define HIBERNATION_SWAP_THRESHOLD 0.98
179 static int hibernation_partition_size(size_t *size
, size_t *used
) {
180 _cleanup_fclose_
FILE *f
;
186 f
= fopen("/proc/swaps", "re");
188 log_full(errno
== ENOENT
? LOG_DEBUG
: LOG_WARNING
,
189 "Failed to retrieve open /proc/swaps: %m");
194 (void) fscanf(f
, "%*s %*s %*s %*s %*s\n");
197 _cleanup_free_
char *dev
= NULL
, *type
= NULL
;
198 size_t size_field
, used_field
;
202 "%ms " /* device/file */
203 "%ms " /* type of swap */
204 "%zu " /* swap size */
206 "%*i\n", /* priority */
207 &dev
, &type
, &size_field
, &used_field
);
212 log_warning("Failed to parse /proc/swaps:%u", i
);
216 if (streq(type
, "partition") && endswith(dev
, "\\040(deleted)")) {
217 log_warning("Ignoring deleted swapfile '%s'.", dev
);
226 log_debug("No swap partitions were found.");
230 static bool enough_memory_for_hibernation(void) {
231 _cleanup_free_
char *active
= NULL
;
232 unsigned long long act
= 0;
233 size_t size
= 0, used
= 0;
236 if (getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0)
239 r
= hibernation_partition_size(&size
, &used
);
243 r
= get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE
, &active
);
245 log_error_errno(r
, "Failed to retrieve Active(anon) from /proc/meminfo: %m");
249 r
= safe_atollu(active
, &act
);
251 log_error_errno(r
, "Failed to parse Active(anon) from /proc/meminfo: %s: %m",
256 r
= act
<= (size
- used
) * HIBERNATION_SWAP_THRESHOLD
;
257 log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%",
258 r
? "" : "im", act
, size
, used
, 100*HIBERNATION_SWAP_THRESHOLD
);
263 int can_sleep(const char *verb
) {
264 _cleanup_strv_free_
char **modes
= NULL
, **states
= NULL
;
267 assert(streq(verb
, "suspend") ||
268 streq(verb
, "hibernate") ||
269 streq(verb
, "hybrid-sleep"));
271 r
= parse_sleep_config(verb
, &modes
, &states
);
275 if (!can_sleep_state(states
) || !can_sleep_disk(modes
))
278 return streq(verb
, "suspend") || enough_memory_for_hibernation();