]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
6edd7d0a | 2 | /*** |
96b2fb93 LP |
3 | Copyright © 2010-2017 Canonical |
4 | Copyright © 2018 Dell Inc. | |
6edd7d0a LP |
5 | ***/ |
6 | ||
6edd7d0a | 7 | #include <errno.h> |
ca78ad1d | 8 | #include <fcntl.h> |
19adb8a3 | 9 | #include <getopt.h> |
17c40b3a | 10 | #include <linux/fiemap.h> |
1bbbefe7 | 11 | #include <poll.h> |
ca78ad1d ZJS |
12 | #include <sys/stat.h> |
13 | #include <sys/types.h> | |
1bbbefe7 | 14 | #include <sys/timerfd.h> |
ca78ad1d | 15 | #include <unistd.h> |
6edd7d0a | 16 | |
aa62a893 | 17 | #include "sd-messages.h" |
3f6fd1ba | 18 | |
64602c84 | 19 | #include "btrfs-util.h" |
ba0fb5ac | 20 | #include "bus-error.h" |
3f6fd1ba | 21 | #include "def.h" |
89711996 | 22 | #include "exec-util.h" |
3ffd4af2 | 23 | #include "fd-util.h" |
a5c32cff | 24 | #include "fileio.h" |
ba0fb5ac | 25 | #include "format-util.h" |
0f2d351f | 26 | #include "io-util.h" |
3f6fd1ba | 27 | #include "log.h" |
5e332028 | 28 | #include "main-func.h" |
ed698d30 | 29 | #include "parse-util.h" |
294bf0c3 | 30 | #include "pretty-print.h" |
19adb8a3 | 31 | #include "sleep-config.h" |
c58493c0 | 32 | #include "stdio-util.h" |
07630cea | 33 | #include "string-util.h" |
3f6fd1ba | 34 | #include "strv.h" |
1bbbefe7 | 35 | #include "time-util.h" |
3f6fd1ba | 36 | #include "util.h" |
19adb8a3 ZJS |
37 | |
38 | static char* arg_verb = NULL; | |
39 | ||
98dc9d1f LP |
40 | STATIC_DESTRUCTOR_REGISTER(arg_verb, freep); |
41 | ||
7bdf56a2 | 42 | static int write_hibernate_location_info(const HibernateLocation *hibernate_location) { |
17c40b3a | 43 | char offset_str[DECIMAL_STR_MAX(uint64_t)]; |
52133271 | 44 | char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")]; |
17c40b3a ML |
45 | int r; |
46 | ||
7bdf56a2 ZS |
47 | assert(hibernate_location); |
48 | assert(hibernate_location->swap); | |
7bdf56a2 | 49 | |
52133271 ZS |
50 | xsprintf(resume_str, "%u:%u", major(hibernate_location->devno), minor(hibernate_location->devno)); |
51 | r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER); | |
17c40b3a | 52 | if (r < 0) |
7bdf56a2 | 53 | return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m", |
52133271 | 54 | hibernate_location->swap->device, resume_str); |
17c40b3a | 55 | |
52133271 | 56 | log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, resume_str); |
ed698d30 | 57 | |
7bdf56a2 ZS |
58 | /* if it's a swap partition, we're done */ |
59 | if (streq(hibernate_location->swap->type, "partition")) | |
ed698d30 | 60 | return r; |
7bdf56a2 ZS |
61 | |
62 | if (!streq(hibernate_location->swap->type, "file")) | |
baaa35ad | 63 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), |
7bdf56a2 | 64 | "Invalid hibernate type: %s", hibernate_location->swap->type); |
17c40b3a ML |
65 | |
66 | /* Only available in 4.17+ */ | |
52133271 | 67 | if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) { |
c695101f | 68 | if (errno == ENOENT) { |
7bdf56a2 | 69 | log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64, |
52133271 | 70 | hibernate_location->swap->device, hibernate_location->offset); |
17c40b3a | 71 | return 0; |
c695101f | 72 | } |
ed698d30 | 73 | |
3f2e15ab | 74 | return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m"); |
c695101f | 75 | } |
17c40b3a | 76 | |
52133271 | 77 | xsprintf(offset_str, "%" PRIu64, hibernate_location->offset); |
57512c89 | 78 | r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER); |
17c40b3a | 79 | if (r < 0) |
7bdf56a2 ZS |
80 | return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m", |
81 | hibernate_location->swap->device, offset_str); | |
17c40b3a | 82 | |
7bdf56a2 | 83 | log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->device, offset_str); |
2002d8cd | 84 | |
17c40b3a ML |
85 | return 0; |
86 | } | |
87 | ||
19adb8a3 ZJS |
88 | static int write_mode(char **modes) { |
89 | int r = 0; | |
90 | char **mode; | |
91 | ||
92 | STRV_FOREACH(mode, modes) { | |
aa62a893 LP |
93 | int k; |
94 | ||
57512c89 | 95 | k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER); |
ed698d30 | 96 | if (k >= 0) |
19adb8a3 | 97 | return 0; |
aa62a893 | 98 | |
ed698d30 LP |
99 | log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode); |
100 | if (r >= 0) | |
19adb8a3 ZJS |
101 | r = k; |
102 | } | |
6edd7d0a | 103 | |
19adb8a3 ZJS |
104 | return r; |
105 | } | |
6edd7d0a | 106 | |
2fd069b1 | 107 | static int write_state(FILE **f, char **states) { |
19adb8a3 ZJS |
108 | char **state; |
109 | int r = 0; | |
110 | ||
2c470205 LP |
111 | assert(f); |
112 | assert(*f); | |
113 | ||
19adb8a3 ZJS |
114 | STRV_FOREACH(state, states) { |
115 | int k; | |
116 | ||
57512c89 | 117 | k = write_string_stream(*f, *state, WRITE_STRING_FILE_DISABLE_BUFFER); |
ed698d30 | 118 | if (k >= 0) |
19adb8a3 | 119 | return 0; |
ed698d30 LP |
120 | log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state); |
121 | if (r >= 0) | |
19adb8a3 ZJS |
122 | r = k; |
123 | ||
2fd069b1 ZJS |
124 | fclose(*f); |
125 | *f = fopen("/sys/power/state", "we"); | |
4a62c710 | 126 | if (!*f) |
0ca906ac | 127 | return -errno; |
6edd7d0a LP |
128 | } |
129 | ||
19adb8a3 ZJS |
130 | return r; |
131 | } | |
6edd7d0a | 132 | |
ba0fb5ac LP |
133 | static int lock_all_homes(void) { |
134 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
135 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
136 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
137 | int r; | |
138 | ||
139 | /* Let's synchronously lock all home directories managed by homed that have been marked for it. This | |
140 | * way the key material required to access these volumes is hopefully removed from memory. */ | |
141 | ||
142 | r = sd_bus_open_system(&bus); | |
143 | if (r < 0) | |
144 | return log_warning_errno(r, "Failed to connect to system bus, ignoring: %m"); | |
145 | ||
146 | r = sd_bus_message_new_method_call( | |
147 | bus, | |
148 | &m, | |
149 | "org.freedesktop.home1", | |
150 | "/org/freedesktop/home1", | |
151 | "org.freedesktop.home1.Manager", | |
152 | "LockAllHomes"); | |
153 | if (r < 0) | |
154 | return bus_log_create_error(r); | |
155 | ||
156 | /* If homed is not running it can't have any home directories active either. */ | |
157 | r = sd_bus_message_set_auto_start(m, false); | |
158 | if (r < 0) | |
159 | return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m"); | |
160 | ||
161 | r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL); | |
162 | if (r < 0) { | |
1c5950bd ZJS |
163 | if (!bus_error_is_unknown_service(&error)) |
164 | return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r)); | |
ba0fb5ac | 165 | |
1c5950bd | 166 | return log_debug("systemd-homed is not running, locking of home directories skipped."); |
ba0fb5ac LP |
167 | } |
168 | ||
1c5950bd | 169 | return log_debug("Successfully requested locking of all home directories."); |
ba0fb5ac LP |
170 | } |
171 | ||
ae463e4e | 172 | static int execute(char **modes, char **states, const char *action) { |
e2cc6eca LP |
173 | char *arguments[] = { |
174 | NULL, | |
175 | (char*) "pre", | |
176 | arg_verb, | |
177 | NULL | |
178 | }; | |
b5084605 LP |
179 | static const char* const dirs[] = { |
180 | SYSTEM_SLEEP_PATH, | |
181 | NULL | |
182 | }; | |
e2cc6eca | 183 | |
2fd069b1 | 184 | _cleanup_fclose_ FILE *f = NULL; |
7bdf56a2 ZS |
185 | _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL; |
186 | int r; | |
6524990f | 187 | |
19adb8a3 | 188 | /* This file is opened first, so that if we hit an error, |
09692409 | 189 | * we can abort before modifying any state. */ |
6edd7d0a | 190 | f = fopen("/sys/power/state", "we"); |
4a62c710 MS |
191 | if (!f) |
192 | return log_error_errno(errno, "Failed to open /sys/power/state: %m"); | |
6edd7d0a | 193 | |
57512c89 YW |
194 | setvbuf(f, NULL, _IONBF, 0); |
195 | ||
c02540dc | 196 | /* Configure hibernation settings if we are supposed to hibernate */ |
17c40b3a | 197 | if (!strv_isempty(modes)) { |
7bdf56a2 | 198 | r = find_hibernate_location(&hibernate_location); |
17c40b3a | 199 | if (r < 0) |
c02540dc LP |
200 | return log_error_errno(r, "Failed to find location to hibernate to: %m"); |
201 | if (r == 0) { /* 0 means: no hibernation location was configured in the kernel so far, let's | |
202 | * do it ourselves then. > 0 means: kernel already had a configured hibernation | |
203 | * location which we shouldn't touch. */ | |
7bdf56a2 ZS |
204 | r = write_hibernate_location_info(hibernate_location); |
205 | if (r < 0) | |
206 | return log_error_errno(r, "Failed to prepare for hibernation: %m"); | |
207 | } | |
2002d8cd | 208 | |
17c40b3a ML |
209 | r = write_mode(modes); |
210 | if (r < 0) | |
0ca906ac | 211 | return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");; |
17c40b3a | 212 | } |
19adb8a3 | 213 | |
ae463e4e ZS |
214 | r = setenv("SYSTEMD_SLEEP_ACTION", action, 1); |
215 | if (r != 0) | |
216 | log_warning_errno(errno, "Error setting SYSTEMD_SLEEP_ACTION=%s: %m", action); | |
217 | ||
aed98342 | 218 | (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); |
ba0fb5ac | 219 | (void) lock_all_homes(); |
6edd7d0a | 220 | |
19adb8a3 | 221 | log_struct(LOG_INFO, |
2b044526 | 222 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, |
e2cc6eca | 223 | LOG_MESSAGE("Suspending system..."), |
a1230ff9 | 224 | "SLEEP=%s", arg_verb); |
19adb8a3 | 225 | |
2fd069b1 | 226 | r = write_state(&f, states); |
19adb8a3 | 227 | if (r < 0) |
14250f09 LP |
228 | log_struct_errno(LOG_ERR, r, |
229 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, | |
230 | LOG_MESSAGE("Failed to suspend system. System resumed again: %m"), | |
231 | "SLEEP=%s", arg_verb); | |
232 | else | |
233 | log_struct(LOG_INFO, | |
234 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, | |
235 | LOG_MESSAGE("System resumed."), | |
236 | "SLEEP=%s", arg_verb); | |
eb267289 | 237 | |
6edd7d0a | 238 | arguments[1] = (char*) "post"; |
aed98342 | 239 | (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); |
6edd7d0a | 240 | |
19adb8a3 ZJS |
241 | return r; |
242 | } | |
6edd7d0a | 243 | |
28ca9c24 | 244 | static int execute_s2h(const SleepConfig *sleep_config) { |
1bbbefe7 ZS |
245 | _cleanup_close_ int tfd = -1; |
246 | char buf[FORMAT_TIMESPAN_MAX]; | |
247 | struct itimerspec ts = {}; | |
c58493c0 ML |
248 | int r; |
249 | ||
28ca9c24 | 250 | assert(sleep_config); |
c58493c0 | 251 | |
1bbbefe7 ZS |
252 | tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); |
253 | if (tfd < 0) | |
254 | return log_error_errno(errno, "Error creating timerfd: %m"); | |
c58493c0 | 255 | |
1bbbefe7 | 256 | log_debug("Set timerfd wake alarm for %s", |
28ca9c24 | 257 | format_timespan(buf, sizeof(buf), sleep_config->hibernate_delay_sec, USEC_PER_SEC)); |
c58493c0 | 258 | |
28ca9c24 | 259 | timespec_store(&ts.it_value, sleep_config->hibernate_delay_sec); |
c58493c0 | 260 | |
1bbbefe7 | 261 | r = timerfd_settime(tfd, 0, &ts, NULL); |
c58493c0 | 262 | if (r < 0) |
1bbbefe7 | 263 | return log_error_errno(errno, "Error setting hibernate timer: %m"); |
c58493c0 | 264 | |
ae463e4e | 265 | r = execute(sleep_config->suspend_modes, sleep_config->suspend_states, "suspend"); |
c58493c0 | 266 | if (r < 0) |
033cea5c | 267 | return r; |
c58493c0 | 268 | |
0f2d351f | 269 | r = fd_wait_for_event(tfd, POLLIN, 0); |
c58493c0 | 270 | if (r < 0) |
0f2d351f LP |
271 | return log_error_errno(r, "Error polling timerfd: %m"); |
272 | if (!FLAGS_SET(r, POLLIN)) /* We woke up before the alarm time, we are done. */ | |
273 | return 0; | |
c58493c0 | 274 | |
1bbbefe7 | 275 | tfd = safe_close(tfd); |
c58493c0 | 276 | |
eabcf200 | 277 | /* If woken up after alarm time, hibernate */ |
1bbbefe7 | 278 | log_debug("Attempting to hibernate after waking from %s timer", |
28ca9c24 ZS |
279 | format_timespan(buf, sizeof(buf), sleep_config->hibernate_delay_sec, USEC_PER_SEC)); |
280 | ||
ae463e4e | 281 | r = execute(sleep_config->hibernate_modes, sleep_config->hibernate_states, "hibernate"); |
f05e1ae6 | 282 | if (r < 0) { |
2c470205 | 283 | log_notice_errno(r, "Couldn't hibernate, will try to suspend again: %m"); |
0f2d351f | 284 | |
ae463e4e | 285 | r = execute(sleep_config->suspend_modes, sleep_config->suspend_states, "suspend-after-failed-hibernate"); |
0f2d351f | 286 | if (r < 0) |
2c470205 | 287 | return log_error_errno(r, "Could neither hibernate nor suspend, giving up: %m"); |
f05e1ae6 LP |
288 | } |
289 | ||
290 | return 0; | |
c58493c0 ML |
291 | } |
292 | ||
37ec0fdd LP |
293 | static int help(void) { |
294 | _cleanup_free_ char *link = NULL; | |
295 | int r; | |
296 | ||
297 | r = terminal_urlify_man("systemd-suspend.service", "8", &link); | |
298 | if (r < 0) | |
299 | return log_oom(); | |
300 | ||
19adb8a3 ZJS |
301 | printf("%s COMMAND\n\n" |
302 | "Suspend the system, hibernate the system, or both.\n\n" | |
37ec0fdd LP |
303 | " -h --help Show this help and exit\n" |
304 | " --version Print version string and exit\n" | |
305 | "\nCommands:\n" | |
306 | " suspend Suspend the system\n" | |
307 | " hibernate Hibernate the system\n" | |
308 | " hybrid-sleep Both hibernate and suspend the system\n" | |
e68c79db | 309 | " suspend-then-hibernate Initially suspend and then hibernate\n" |
37ec0fdd LP |
310 | " the system after a fixed period of time\n" |
311 | "\nSee the %s for details.\n" | |
312 | , program_invocation_short_name | |
313 | , link | |
314 | ); | |
315 | ||
316 | return 0; | |
19adb8a3 | 317 | } |
6edd7d0a | 318 | |
19adb8a3 ZJS |
319 | static int parse_argv(int argc, char *argv[]) { |
320 | enum { | |
321 | ARG_VERSION = 0x100, | |
322 | }; | |
323 | ||
324 | static const struct option options[] = { | |
325 | { "help", no_argument, NULL, 'h' }, | |
326 | { "version", no_argument, NULL, ARG_VERSION }, | |
eb9da376 | 327 | {} |
19adb8a3 ZJS |
328 | }; |
329 | ||
330 | int c; | |
331 | ||
332 | assert(argc >= 0); | |
333 | assert(argv); | |
334 | ||
601185b4 | 335 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) |
19adb8a3 ZJS |
336 | switch(c) { |
337 | case 'h': | |
37ec0fdd | 338 | return help(); |
19adb8a3 ZJS |
339 | |
340 | case ARG_VERSION: | |
3f6fd1ba | 341 | return version(); |
19adb8a3 ZJS |
342 | |
343 | case '?': | |
344 | return -EINVAL; | |
345 | ||
346 | default: | |
eb9da376 | 347 | assert_not_reached("Unhandled option"); |
19adb8a3 ZJS |
348 | } |
349 | ||
baaa35ad ZJS |
350 | if (argc - optind != 1) |
351 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
352 | "Usage: %s COMMAND", | |
353 | program_invocation_short_name); | |
19adb8a3 | 354 | |
98dc9d1f LP |
355 | arg_verb = strdup(argv[optind]); |
356 | if (!arg_verb) | |
357 | return log_oom(); | |
6edd7d0a | 358 | |
baaa35ad ZJS |
359 | if (!STR_IN_SET(arg_verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate")) |
360 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
361 | "Unknown command '%s'.", arg_verb); | |
19adb8a3 ZJS |
362 | |
363 | return 1 /* work to do */; | |
364 | } | |
365 | ||
7caefb81 | 366 | static int run(int argc, char *argv[]) { |
e8f1d00d | 367 | bool allow; |
28ca9c24 ZS |
368 | char **modes = NULL, **states = NULL; |
369 | _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL; | |
19adb8a3 ZJS |
370 | int r; |
371 | ||
6bf3c61c | 372 | log_setup_service(); |
19adb8a3 ZJS |
373 | |
374 | r = parse_argv(argc, argv); | |
375 | if (r <= 0) | |
7caefb81 | 376 | return r; |
19adb8a3 | 377 | |
28ca9c24 ZS |
378 | r = parse_sleep_config(&sleep_config); |
379 | if (r < 0) | |
380 | return r; | |
381 | ||
382 | r = sleep_settings(arg_verb, sleep_config, &allow, &modes, &states); | |
19adb8a3 | 383 | if (r < 0) |
7caefb81 | 384 | return r; |
19adb8a3 | 385 | |
baaa35ad ZJS |
386 | if (!allow) |
387 | return log_error_errno(SYNTHETIC_ERRNO(EACCES), | |
388 | "Sleep mode \"%s\" is disabled by configuration, refusing.", | |
389 | arg_verb); | |
e8f1d00d | 390 | |
e68c79db | 391 | if (streq(arg_verb, "suspend-then-hibernate")) |
28ca9c24 | 392 | return execute_s2h(sleep_config); |
c58493c0 | 393 | else |
ae463e4e | 394 | return execute(modes, states, arg_verb); |
6edd7d0a | 395 | } |
7caefb81 ZJS |
396 | |
397 | DEFINE_MAIN_FUNCTION(run); |