]>
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 | 37 | |
c8cd8ca3 | 38 | static SleepOperation arg_operation = _SLEEP_OPERATION_INVALID; |
98dc9d1f | 39 | |
7bdf56a2 | 40 | static int write_hibernate_location_info(const HibernateLocation *hibernate_location) { |
17c40b3a | 41 | char offset_str[DECIMAL_STR_MAX(uint64_t)]; |
52133271 | 42 | char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")]; |
17c40b3a ML |
43 | int r; |
44 | ||
7bdf56a2 ZS |
45 | assert(hibernate_location); |
46 | assert(hibernate_location->swap); | |
7bdf56a2 | 47 | |
52133271 ZS |
48 | xsprintf(resume_str, "%u:%u", major(hibernate_location->devno), minor(hibernate_location->devno)); |
49 | r = write_string_file("/sys/power/resume", resume_str, WRITE_STRING_FILE_DISABLE_BUFFER); | |
17c40b3a | 50 | if (r < 0) |
7bdf56a2 | 51 | return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m", |
52133271 | 52 | hibernate_location->swap->device, resume_str); |
17c40b3a | 53 | |
52133271 | 54 | log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, resume_str); |
ed698d30 | 55 | |
7bdf56a2 ZS |
56 | /* if it's a swap partition, we're done */ |
57 | if (streq(hibernate_location->swap->type, "partition")) | |
ed698d30 | 58 | return r; |
7bdf56a2 ZS |
59 | |
60 | if (!streq(hibernate_location->swap->type, "file")) | |
baaa35ad | 61 | return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), |
7bdf56a2 | 62 | "Invalid hibernate type: %s", hibernate_location->swap->type); |
17c40b3a ML |
63 | |
64 | /* Only available in 4.17+ */ | |
52133271 | 65 | if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) { |
c695101f | 66 | if (errno == ENOENT) { |
7bdf56a2 | 67 | log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64, |
52133271 | 68 | hibernate_location->swap->device, hibernate_location->offset); |
17c40b3a | 69 | return 0; |
c695101f | 70 | } |
ed698d30 | 71 | |
3f2e15ab | 72 | return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m"); |
c695101f | 73 | } |
17c40b3a | 74 | |
52133271 | 75 | xsprintf(offset_str, "%" PRIu64, hibernate_location->offset); |
57512c89 | 76 | r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER); |
17c40b3a | 77 | if (r < 0) |
7bdf56a2 ZS |
78 | return log_debug_errno(r, "Failed to write swap file offset to /sys/power/resume_offset for '%s': '%s': %m", |
79 | hibernate_location->swap->device, offset_str); | |
17c40b3a | 80 | |
7bdf56a2 | 81 | log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->device, offset_str); |
2002d8cd | 82 | |
17c40b3a ML |
83 | return 0; |
84 | } | |
85 | ||
19adb8a3 ZJS |
86 | static int write_mode(char **modes) { |
87 | int r = 0; | |
19adb8a3 ZJS |
88 | |
89 | STRV_FOREACH(mode, modes) { | |
aa62a893 LP |
90 | int k; |
91 | ||
57512c89 | 92 | k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER); |
ed698d30 | 93 | if (k >= 0) |
19adb8a3 | 94 | return 0; |
aa62a893 | 95 | |
ed698d30 LP |
96 | log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode); |
97 | if (r >= 0) | |
19adb8a3 ZJS |
98 | r = k; |
99 | } | |
6edd7d0a | 100 | |
19adb8a3 ZJS |
101 | return r; |
102 | } | |
6edd7d0a | 103 | |
2fd069b1 | 104 | static int write_state(FILE **f, char **states) { |
19adb8a3 ZJS |
105 | int r = 0; |
106 | ||
2c470205 LP |
107 | assert(f); |
108 | assert(*f); | |
109 | ||
19adb8a3 ZJS |
110 | STRV_FOREACH(state, states) { |
111 | int k; | |
112 | ||
57512c89 | 113 | k = write_string_stream(*f, *state, WRITE_STRING_FILE_DISABLE_BUFFER); |
ed698d30 | 114 | if (k >= 0) |
19adb8a3 | 115 | return 0; |
ed698d30 LP |
116 | log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state); |
117 | if (r >= 0) | |
19adb8a3 ZJS |
118 | r = k; |
119 | ||
2fd069b1 ZJS |
120 | fclose(*f); |
121 | *f = fopen("/sys/power/state", "we"); | |
4a62c710 | 122 | if (!*f) |
0ca906ac | 123 | return -errno; |
6edd7d0a LP |
124 | } |
125 | ||
19adb8a3 ZJS |
126 | return r; |
127 | } | |
6edd7d0a | 128 | |
ba0fb5ac LP |
129 | static int lock_all_homes(void) { |
130 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
131 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; | |
132 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; | |
133 | int r; | |
134 | ||
135 | /* Let's synchronously lock all home directories managed by homed that have been marked for it. This | |
136 | * way the key material required to access these volumes is hopefully removed from memory. */ | |
137 | ||
138 | r = sd_bus_open_system(&bus); | |
139 | if (r < 0) | |
140 | return log_warning_errno(r, "Failed to connect to system bus, ignoring: %m"); | |
141 | ||
142 | r = sd_bus_message_new_method_call( | |
143 | bus, | |
144 | &m, | |
145 | "org.freedesktop.home1", | |
146 | "/org/freedesktop/home1", | |
147 | "org.freedesktop.home1.Manager", | |
148 | "LockAllHomes"); | |
149 | if (r < 0) | |
150 | return bus_log_create_error(r); | |
151 | ||
152 | /* If homed is not running it can't have any home directories active either. */ | |
153 | r = sd_bus_message_set_auto_start(m, false); | |
154 | if (r < 0) | |
155 | return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m"); | |
156 | ||
157 | r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL); | |
158 | if (r < 0) { | |
1c5950bd ZJS |
159 | if (!bus_error_is_unknown_service(&error)) |
160 | return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r)); | |
ba0fb5ac | 161 | |
75029e15 ZJS |
162 | log_debug("systemd-homed is not running, locking of home directories skipped."); |
163 | } else | |
164 | log_debug("Successfully requested locking of all home directories."); | |
165 | return 0; | |
ba0fb5ac LP |
166 | } |
167 | ||
c8cd8ca3 LP |
168 | static int execute( |
169 | const SleepConfig *sleep_config, | |
170 | SleepOperation operation, | |
171 | const char *action) { | |
172 | ||
e2cc6eca LP |
173 | char *arguments[] = { |
174 | NULL, | |
175 | (char*) "pre", | |
c8cd8ca3 LP |
176 | /* NB: we use 'arg_operation' instead of 'operation' here, as we want to communicate the overall |
177 | * operation here, not the specific one, in case of s2h. */ | |
178 | (char*) sleep_operation_to_string(arg_operation), | |
e2cc6eca LP |
179 | NULL |
180 | }; | |
b5084605 LP |
181 | static const char* const dirs[] = { |
182 | SYSTEM_SLEEP_PATH, | |
183 | NULL | |
184 | }; | |
e2cc6eca | 185 | |
7bdf56a2 | 186 | _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL; |
c8cd8ca3 LP |
187 | _cleanup_fclose_ FILE *f = NULL; |
188 | char **modes, **states; | |
7bdf56a2 | 189 | int r; |
6524990f | 190 | |
c8cd8ca3 LP |
191 | assert(sleep_config); |
192 | assert(operation >= 0); | |
193 | assert(operation < _SLEEP_OPERATION_MAX); | |
194 | assert(operation != SLEEP_SUSPEND_THEN_HIBERNATE); /* Handled by execute_s2h() instead */ | |
195 | ||
196 | states = sleep_config->states[operation]; | |
197 | modes = sleep_config->modes[operation]; | |
198 | ||
199 | if (strv_isempty(states)) | |
200 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
201 | "No sleep states configured for sleep operation %s, can't sleep.", | |
202 | sleep_operation_to_string(operation)); | |
203 | ||
19adb8a3 | 204 | /* This file is opened first, so that if we hit an error, |
09692409 | 205 | * we can abort before modifying any state. */ |
6edd7d0a | 206 | f = fopen("/sys/power/state", "we"); |
4a62c710 MS |
207 | if (!f) |
208 | return log_error_errno(errno, "Failed to open /sys/power/state: %m"); | |
6edd7d0a | 209 | |
57512c89 YW |
210 | setvbuf(f, NULL, _IONBF, 0); |
211 | ||
c02540dc | 212 | /* Configure hibernation settings if we are supposed to hibernate */ |
17c40b3a | 213 | if (!strv_isempty(modes)) { |
7bdf56a2 | 214 | r = find_hibernate_location(&hibernate_location); |
17c40b3a | 215 | if (r < 0) |
c02540dc LP |
216 | return log_error_errno(r, "Failed to find location to hibernate to: %m"); |
217 | if (r == 0) { /* 0 means: no hibernation location was configured in the kernel so far, let's | |
218 | * do it ourselves then. > 0 means: kernel already had a configured hibernation | |
219 | * location which we shouldn't touch. */ | |
7bdf56a2 ZS |
220 | r = write_hibernate_location_info(hibernate_location); |
221 | if (r < 0) | |
222 | return log_error_errno(r, "Failed to prepare for hibernation: %m"); | |
223 | } | |
2002d8cd | 224 | |
17c40b3a ML |
225 | r = write_mode(modes); |
226 | if (r < 0) | |
aea29a30 | 227 | return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); |
17c40b3a | 228 | } |
19adb8a3 | 229 | |
c8cd8ca3 LP |
230 | /* Pass an action string to the call-outs. This is mostly our operation string, except if the |
231 | * hibernate step of s-t-h fails, in which case we communicate that with a separate action. */ | |
232 | if (!action) | |
233 | action = sleep_operation_to_string(operation); | |
234 | ||
ae463e4e ZS |
235 | r = setenv("SYSTEMD_SLEEP_ACTION", action, 1); |
236 | if (r != 0) | |
c3565fe8 | 237 | log_warning_errno(errno, "Error setting SYSTEMD_SLEEP_ACTION=%s, ignoring: %m", action); |
ae463e4e | 238 | |
aed98342 | 239 | (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); |
ba0fb5ac | 240 | (void) lock_all_homes(); |
6edd7d0a | 241 | |
19adb8a3 | 242 | log_struct(LOG_INFO, |
2b044526 | 243 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, |
c8cd8ca3 LP |
244 | LOG_MESSAGE("Entering sleep state '%s'...", sleep_operation_to_string(operation)), |
245 | "SLEEP=%s", sleep_operation_to_string(arg_operation)); | |
19adb8a3 | 246 | |
2fd069b1 | 247 | r = write_state(&f, states); |
19adb8a3 | 248 | if (r < 0) |
14250f09 LP |
249 | log_struct_errno(LOG_ERR, r, |
250 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, | |
c8cd8ca3 LP |
251 | LOG_MESSAGE("Failed to put system to sleep. System resumed again: %m"), |
252 | "SLEEP=%s", sleep_operation_to_string(arg_operation)); | |
14250f09 LP |
253 | else |
254 | log_struct(LOG_INFO, | |
255 | "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, | |
c8cd8ca3 LP |
256 | LOG_MESSAGE("System returned from sleep state."), |
257 | "SLEEP=%s", sleep_operation_to_string(arg_operation)); | |
eb267289 | 258 | |
6edd7d0a | 259 | arguments[1] = (char*) "post"; |
aed98342 | 260 | (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); |
6edd7d0a | 261 | |
19adb8a3 ZJS |
262 | return r; |
263 | } | |
6edd7d0a | 264 | |
28ca9c24 | 265 | static int execute_s2h(const SleepConfig *sleep_config) { |
1bbbefe7 | 266 | _cleanup_close_ int tfd = -1; |
1bbbefe7 | 267 | struct itimerspec ts = {}; |
c58493c0 ML |
268 | int r; |
269 | ||
28ca9c24 | 270 | assert(sleep_config); |
c58493c0 | 271 | |
1bbbefe7 ZS |
272 | tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC); |
273 | if (tfd < 0) | |
274 | return log_error_errno(errno, "Error creating timerfd: %m"); | |
c58493c0 | 275 | |
1bbbefe7 | 276 | log_debug("Set timerfd wake alarm for %s", |
5291f26d | 277 | FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC)); |
c58493c0 | 278 | |
28ca9c24 | 279 | timespec_store(&ts.it_value, sleep_config->hibernate_delay_sec); |
c58493c0 | 280 | |
1bbbefe7 | 281 | r = timerfd_settime(tfd, 0, &ts, NULL); |
c58493c0 | 282 | if (r < 0) |
1bbbefe7 | 283 | return log_error_errno(errno, "Error setting hibernate timer: %m"); |
c58493c0 | 284 | |
c8cd8ca3 | 285 | r = execute(sleep_config, SLEEP_SUSPEND, NULL); |
c58493c0 | 286 | if (r < 0) |
033cea5c | 287 | return r; |
c58493c0 | 288 | |
0f2d351f | 289 | r = fd_wait_for_event(tfd, POLLIN, 0); |
c58493c0 | 290 | if (r < 0) |
0f2d351f LP |
291 | return log_error_errno(r, "Error polling timerfd: %m"); |
292 | if (!FLAGS_SET(r, POLLIN)) /* We woke up before the alarm time, we are done. */ | |
293 | return 0; | |
c58493c0 | 294 | |
1bbbefe7 | 295 | tfd = safe_close(tfd); |
c58493c0 | 296 | |
eabcf200 | 297 | /* If woken up after alarm time, hibernate */ |
1bbbefe7 | 298 | log_debug("Attempting to hibernate after waking from %s timer", |
5291f26d | 299 | FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC)); |
28ca9c24 | 300 | |
c8cd8ca3 | 301 | r = execute(sleep_config, SLEEP_HIBERNATE, NULL); |
f05e1ae6 | 302 | if (r < 0) { |
b0c035e3 | 303 | log_notice("Couldn't hibernate, will try to suspend again."); |
0f2d351f | 304 | |
c8cd8ca3 | 305 | r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hibernate"); |
0f2d351f | 306 | if (r < 0) |
b0c035e3 | 307 | return r; |
f05e1ae6 LP |
308 | } |
309 | ||
310 | return 0; | |
c58493c0 ML |
311 | } |
312 | ||
37ec0fdd LP |
313 | static int help(void) { |
314 | _cleanup_free_ char *link = NULL; | |
315 | int r; | |
316 | ||
317 | r = terminal_urlify_man("systemd-suspend.service", "8", &link); | |
318 | if (r < 0) | |
319 | return log_oom(); | |
320 | ||
19adb8a3 ZJS |
321 | printf("%s COMMAND\n\n" |
322 | "Suspend the system, hibernate the system, or both.\n\n" | |
37ec0fdd LP |
323 | " -h --help Show this help and exit\n" |
324 | " --version Print version string and exit\n" | |
325 | "\nCommands:\n" | |
326 | " suspend Suspend the system\n" | |
327 | " hibernate Hibernate the system\n" | |
328 | " hybrid-sleep Both hibernate and suspend the system\n" | |
e68c79db | 329 | " suspend-then-hibernate Initially suspend and then hibernate\n" |
37ec0fdd | 330 | " the system after a fixed period of time\n" |
bc556335 DDM |
331 | "\nSee the %s for details.\n", |
332 | program_invocation_short_name, | |
333 | link); | |
37ec0fdd LP |
334 | |
335 | return 0; | |
19adb8a3 | 336 | } |
6edd7d0a | 337 | |
19adb8a3 ZJS |
338 | static int parse_argv(int argc, char *argv[]) { |
339 | enum { | |
340 | ARG_VERSION = 0x100, | |
341 | }; | |
342 | ||
343 | static const struct option options[] = { | |
344 | { "help", no_argument, NULL, 'h' }, | |
345 | { "version", no_argument, NULL, ARG_VERSION }, | |
eb9da376 | 346 | {} |
19adb8a3 ZJS |
347 | }; |
348 | ||
349 | int c; | |
350 | ||
351 | assert(argc >= 0); | |
352 | assert(argv); | |
353 | ||
601185b4 | 354 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) |
79893116 | 355 | switch (c) { |
19adb8a3 | 356 | case 'h': |
37ec0fdd | 357 | return help(); |
19adb8a3 ZJS |
358 | |
359 | case ARG_VERSION: | |
3f6fd1ba | 360 | return version(); |
19adb8a3 ZJS |
361 | |
362 | case '?': | |
363 | return -EINVAL; | |
364 | ||
365 | default: | |
04499a70 | 366 | assert_not_reached(); |
19adb8a3 ZJS |
367 | } |
368 | ||
baaa35ad ZJS |
369 | if (argc - optind != 1) |
370 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
371 | "Usage: %s COMMAND", | |
372 | program_invocation_short_name); | |
19adb8a3 | 373 | |
c8cd8ca3 LP |
374 | arg_operation = sleep_operation_from_string(argv[optind]); |
375 | if (arg_operation < 0) | |
376 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command '%s'.", argv[optind]); | |
19adb8a3 ZJS |
377 | |
378 | return 1 /* work to do */; | |
379 | } | |
380 | ||
7caefb81 | 381 | static int run(int argc, char *argv[]) { |
28ca9c24 | 382 | _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL; |
19adb8a3 ZJS |
383 | int r; |
384 | ||
d2acb93d | 385 | log_setup(); |
19adb8a3 ZJS |
386 | |
387 | r = parse_argv(argc, argv); | |
388 | if (r <= 0) | |
7caefb81 | 389 | return r; |
19adb8a3 | 390 | |
28ca9c24 ZS |
391 | r = parse_sleep_config(&sleep_config); |
392 | if (r < 0) | |
393 | return r; | |
394 | ||
c8cd8ca3 | 395 | if (!sleep_config->allow[arg_operation]) |
baaa35ad | 396 | return log_error_errno(SYNTHETIC_ERRNO(EACCES), |
c8cd8ca3 LP |
397 | "Sleep operation \"%s\" is disabled by configuration, refusing.", |
398 | sleep_operation_to_string(arg_operation)); | |
e8f1d00d | 399 | |
3d132111 LP |
400 | switch (arg_operation) { |
401 | ||
402 | case SLEEP_SUSPEND_THEN_HIBERNATE: | |
403 | r = execute_s2h(sleep_config); | |
404 | break; | |
405 | ||
406 | case SLEEP_HYBRID_SLEEP: | |
407 | r = execute(sleep_config, SLEEP_HYBRID_SLEEP, NULL); | |
408 | if (r < 0) { | |
409 | /* If we can't hybrid sleep, then let's try to suspend at least. After all, the user | |
410 | * asked us to do both: suspend + hibernate, and it's almost certainly the | |
411 | * hibernation that failed, hence still do the other thing, the suspend. */ | |
412 | ||
413 | log_notice("Couldn't hybrid sleep, will try to suspend instead."); | |
414 | ||
415 | r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); | |
416 | } | |
417 | ||
418 | break; | |
419 | ||
420 | default: | |
421 | r = execute(sleep_config, arg_operation, NULL); | |
422 | break; | |
423 | } | |
424 | ||
425 | return r; | |
6edd7d0a | 426 | } |
7caefb81 ZJS |
427 | |
428 | DEFINE_MAIN_FUNCTION(run); |