]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/sleep/sleep.c
core: reduce scope of variants
[thirdparty/systemd.git] / src / sleep / sleep.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright © 2010-2017 Canonical
4 Copyright © 2018 Dell Inc.
5 ***/
6
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <getopt.h>
10 #include <linux/fiemap.h>
11 #include <poll.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/timerfd.h>
15 #include <unistd.h>
16
17 #include "sd-messages.h"
18
19 #include "btrfs-util.h"
20 #include "bus-error.h"
21 #include "def.h"
22 #include "exec-util.h"
23 #include "fd-util.h"
24 #include "fileio.h"
25 #include "format-util.h"
26 #include "io-util.h"
27 #include "log.h"
28 #include "main-func.h"
29 #include "parse-util.h"
30 #include "pretty-print.h"
31 #include "sleep-config.h"
32 #include "stdio-util.h"
33 #include "string-util.h"
34 #include "strv.h"
35 #include "time-util.h"
36 #include "util.h"
37
38 static char* arg_verb = NULL;
39
40 STATIC_DESTRUCTOR_REGISTER(arg_verb, freep);
41
42 static int write_hibernate_location_info(const HibernateLocation *hibernate_location) {
43 char offset_str[DECIMAL_STR_MAX(uint64_t)];
44 char resume_str[DECIMAL_STR_MAX(unsigned) * 2 + STRLEN(":")];
45 int r;
46
47 assert(hibernate_location);
48 assert(hibernate_location->swap);
49
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);
52 if (r < 0)
53 return log_debug_errno(r, "Failed to write partition device to /sys/power/resume for '%s': '%s': %m",
54 hibernate_location->swap->device, resume_str);
55
56 log_debug("Wrote resume= value for %s to /sys/power/resume: %s", hibernate_location->swap->device, resume_str);
57
58 /* if it's a swap partition, we're done */
59 if (streq(hibernate_location->swap->type, "partition"))
60 return r;
61
62 if (!streq(hibernate_location->swap->type, "file"))
63 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
64 "Invalid hibernate type: %s", hibernate_location->swap->type);
65
66 /* Only available in 4.17+ */
67 if (hibernate_location->offset > 0 && access("/sys/power/resume_offset", W_OK) < 0) {
68 if (errno == ENOENT) {
69 log_debug("Kernel too old, can't configure resume_offset for %s, ignoring: %" PRIu64,
70 hibernate_location->swap->device, hibernate_location->offset);
71 return 0;
72 }
73
74 return log_debug_errno(errno, "/sys/power/resume_offset not writable: %m");
75 }
76
77 xsprintf(offset_str, "%" PRIu64, hibernate_location->offset);
78 r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER);
79 if (r < 0)
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);
82
83 log_debug("Wrote resume_offset= value for %s to /sys/power/resume_offset: %s", hibernate_location->swap->device, offset_str);
84
85 return 0;
86 }
87
88 static int write_mode(char **modes) {
89 int r = 0;
90 char **mode;
91
92 STRV_FOREACH(mode, modes) {
93 int k;
94
95 k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER);
96 if (k >= 0)
97 return 0;
98
99 log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode);
100 if (r >= 0)
101 r = k;
102 }
103
104 return r;
105 }
106
107 static int write_state(FILE **f, char **states) {
108 char **state;
109 int r = 0;
110
111 assert(f);
112 assert(*f);
113
114 STRV_FOREACH(state, states) {
115 int k;
116
117 k = write_string_stream(*f, *state, WRITE_STRING_FILE_DISABLE_BUFFER);
118 if (k >= 0)
119 return 0;
120 log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state);
121 if (r >= 0)
122 r = k;
123
124 fclose(*f);
125 *f = fopen("/sys/power/state", "we");
126 if (!*f)
127 return -errno;
128 }
129
130 return r;
131 }
132
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) {
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));
165
166 return log_debug("systemd-homed is not running, locking of home directories skipped.");
167 }
168
169 return log_debug("Successfully requested locking of all home directories.");
170 }
171
172 static int execute(char **modes, char **states) {
173 char *arguments[] = {
174 NULL,
175 (char*) "pre",
176 arg_verb,
177 NULL
178 };
179 static const char* const dirs[] = {
180 SYSTEM_SLEEP_PATH,
181 NULL
182 };
183
184 _cleanup_fclose_ FILE *f = NULL;
185 _cleanup_(hibernate_location_freep) HibernateLocation *hibernate_location = NULL;
186 int r;
187
188 /* This file is opened first, so that if we hit an error,
189 * we can abort before modifying any state. */
190 f = fopen("/sys/power/state", "we");
191 if (!f)
192 return log_error_errno(errno, "Failed to open /sys/power/state: %m");
193
194 setvbuf(f, NULL, _IONBF, 0);
195
196 /* Configure hibernation settings if we are supposed to hibernate */
197 if (!strv_isempty(modes)) {
198 r = find_hibernate_location(&hibernate_location);
199 if (r < 0)
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. */
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 }
208
209 r = write_mode(modes);
210 if (r < 0)
211 return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");;
212 }
213
214 (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
215 (void) lock_all_homes();
216
217 log_struct(LOG_INFO,
218 "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
219 LOG_MESSAGE("Suspending system..."),
220 "SLEEP=%s", arg_verb);
221
222 r = write_state(&f, states);
223 if (r < 0)
224 log_struct_errno(LOG_ERR, r,
225 "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
226 LOG_MESSAGE("Failed to suspend system. System resumed again: %m"),
227 "SLEEP=%s", arg_verb);
228 else
229 log_struct(LOG_INFO,
230 "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
231 LOG_MESSAGE("System resumed."),
232 "SLEEP=%s", arg_verb);
233
234 arguments[1] = (char*) "post";
235 (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
236
237 return r;
238 }
239
240 static int execute_s2h(const SleepConfig *sleep_config) {
241 _cleanup_close_ int tfd = -1;
242 char buf[FORMAT_TIMESPAN_MAX];
243 struct itimerspec ts = {};
244 int r;
245
246 assert(sleep_config);
247
248 tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
249 if (tfd < 0)
250 return log_error_errno(errno, "Error creating timerfd: %m");
251
252 log_debug("Set timerfd wake alarm for %s",
253 format_timespan(buf, sizeof(buf), sleep_config->hibernate_delay_sec, USEC_PER_SEC));
254
255 timespec_store(&ts.it_value, sleep_config->hibernate_delay_sec);
256
257 r = timerfd_settime(tfd, 0, &ts, NULL);
258 if (r < 0)
259 return log_error_errno(errno, "Error setting hibernate timer: %m");
260
261 r = execute(sleep_config->suspend_modes, sleep_config->suspend_states);
262 if (r < 0)
263 return r;
264
265 r = fd_wait_for_event(tfd, POLLIN, 0);
266 if (r < 0)
267 return log_error_errno(r, "Error polling timerfd: %m");
268 if (!FLAGS_SET(r, POLLIN)) /* We woke up before the alarm time, we are done. */
269 return 0;
270
271 tfd = safe_close(tfd);
272
273 /* If woken up after alarm time, hibernate */
274 log_debug("Attempting to hibernate after waking from %s timer",
275 format_timespan(buf, sizeof(buf), sleep_config->hibernate_delay_sec, USEC_PER_SEC));
276
277 r = execute(sleep_config->hibernate_modes, sleep_config->hibernate_states);
278 if (r < 0) {
279 log_notice_errno(r, "Couldn't hibernate, will try to suspend again: %m");
280
281 r = execute(sleep_config->suspend_modes, sleep_config->suspend_states);
282 if (r < 0)
283 return log_error_errno(r, "Could neither hibernate nor suspend, giving up: %m");
284 }
285
286 return 0;
287 }
288
289 static int help(void) {
290 _cleanup_free_ char *link = NULL;
291 int r;
292
293 r = terminal_urlify_man("systemd-suspend.service", "8", &link);
294 if (r < 0)
295 return log_oom();
296
297 printf("%s COMMAND\n\n"
298 "Suspend the system, hibernate the system, or both.\n\n"
299 " -h --help Show this help and exit\n"
300 " --version Print version string and exit\n"
301 "\nCommands:\n"
302 " suspend Suspend the system\n"
303 " hibernate Hibernate the system\n"
304 " hybrid-sleep Both hibernate and suspend the system\n"
305 " suspend-then-hibernate Initially suspend and then hibernate\n"
306 " the system after a fixed period of time\n"
307 "\nSee the %s for details.\n"
308 , program_invocation_short_name
309 , link
310 );
311
312 return 0;
313 }
314
315 static int parse_argv(int argc, char *argv[]) {
316 enum {
317 ARG_VERSION = 0x100,
318 };
319
320 static const struct option options[] = {
321 { "help", no_argument, NULL, 'h' },
322 { "version", no_argument, NULL, ARG_VERSION },
323 {}
324 };
325
326 int c;
327
328 assert(argc >= 0);
329 assert(argv);
330
331 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
332 switch(c) {
333 case 'h':
334 return help();
335
336 case ARG_VERSION:
337 return version();
338
339 case '?':
340 return -EINVAL;
341
342 default:
343 assert_not_reached("Unhandled option");
344 }
345
346 if (argc - optind != 1)
347 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
348 "Usage: %s COMMAND",
349 program_invocation_short_name);
350
351 arg_verb = strdup(argv[optind]);
352 if (!arg_verb)
353 return log_oom();
354
355 if (!STR_IN_SET(arg_verb, "suspend", "hibernate", "hybrid-sleep", "suspend-then-hibernate"))
356 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
357 "Unknown command '%s'.", arg_verb);
358
359 return 1 /* work to do */;
360 }
361
362 static int run(int argc, char *argv[]) {
363 bool allow;
364 char **modes = NULL, **states = NULL;
365 _cleanup_(free_sleep_configp) SleepConfig *sleep_config = NULL;
366 int r;
367
368 log_setup_service();
369
370 r = parse_argv(argc, argv);
371 if (r <= 0)
372 return r;
373
374 r = parse_sleep_config(&sleep_config);
375 if (r < 0)
376 return r;
377
378 r = sleep_settings(arg_verb, sleep_config, &allow, &modes, &states);
379 if (r < 0)
380 return r;
381
382 if (!allow)
383 return log_error_errno(SYNTHETIC_ERRNO(EACCES),
384 "Sleep mode \"%s\" is disabled by configuration, refusing.",
385 arg_verb);
386
387 if (streq(arg_verb, "suspend-then-hibernate"))
388 return execute_s2h(sleep_config);
389 else
390 return execute(modes, states);
391 }
392
393 DEFINE_MAIN_FUNCTION(run);