1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2012 Lennart Poettering
6 Copyright 2013 Zbigniew Jędrzejewski-Szmek
7 Copyright 2010-2017 Canonical
8 Copyright 2018 Dell Inc.
10 systemd is free software; you can redistribute it and/or modify it
11 under the terms of the GNU Lesser General Public License as published by
12 the Free Software Foundation; either version 2.1 of the License, or
13 (at your option) any later version.
15 systemd is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
20 You should have received a copy of the GNU Lesser General Public License
21 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 //#include <linux/fiemap.h>
29 #include "sd-messages.h"
31 //#include "parse-util.h"
33 #include "exec-util.h"
37 //#include "sleep-config.h"
38 //#include "stdio-util.h"
39 //#include "string-util.h"
43 /// Additional includes needed by elogind
46 static char* arg_verb = NULL;
48 static int write_hibernate_location_info(void) {
49 _cleanup_free_ char *device = NULL, *type = NULL;
50 _cleanup_free_ struct fiemap *fiemap = NULL;
51 char offset_str[DECIMAL_STR_MAX(uint64_t)];
52 char device_str[DECIMAL_STR_MAX(uint64_t)];
53 _cleanup_close_ int fd = -1;
58 r = find_hibernate_location(&device, &type, NULL, NULL);
60 return log_debug_errno(r, "Unable to find hibernation location: %m");
62 /* if it's a swap partition, we just write the disk to /sys/power/resume */
63 if (streq(type, "partition"))
64 return write_string_file("/sys/power/resume", device, 0);
65 else if (!streq(type, "file"))
66 return log_debug_errno(EINVAL, "Invalid hibernate type %s: %m",
69 /* Only available in 4.17+ */
70 if (access("/sys/power/resume_offset", F_OK) < 0) {
73 return log_debug_errno(errno, "/sys/power/resume_offset unavailable: %m");
76 r = access("/sys/power/resume_offset", W_OK);
78 return log_debug_errno(errno, "/sys/power/resume_offset not writeable: %m");
80 fd = open(device, O_RDONLY | O_CLOEXEC | O_NONBLOCK);
82 return log_debug_errno(errno, "Unable to open '%s': %m", device);
85 return log_debug_errno(errno, "Unable to stat %s: %m", device);
86 r = read_fiemap(fd, &fiemap);
88 return log_debug_errno(r, "Unable to read extent map for '%s': %m",
90 if (fiemap->fm_mapped_extents == 0) {
91 log_debug("No extents found in '%s'", device);
94 offset = fiemap->fm_extents[0].fe_physical / page_size();
95 xsprintf(offset_str, "%" PRIu64, offset);
96 r = write_string_file("/sys/power/resume_offset", offset_str, 0);
98 return log_debug_errno(r, "Failed to write offset '%s': %m",
101 xsprintf(device_str, "%lx", (unsigned long)stb.st_dev);
102 r = write_string_file("/sys/power/resume", device_str, 0);
104 return log_debug_errno(r, "Failed to write device '%s': %m",
109 static int write_mode(char **modes) {
113 STRV_FOREACH(mode, modes) {
116 k = write_string_file("/sys/power/disk", *mode, 0);
120 log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m",
129 static int write_state(FILE **f, char **states) {
133 STRV_FOREACH(state, states) {
136 k = write_string_stream(*f, *state, 0);
139 log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m",
145 *f = fopen("/sys/power/state", "we");
153 static int execute(char **modes, char **states) {
155 char *arguments[] = {
161 static const char* const dirs[] = {
167 _cleanup_fclose_ FILE *f = NULL;
169 /* This file is opened first, so that if we hit an error,
170 * we can abort before modifying any state. */
171 f = fopen("/sys/power/state", "we");
173 return log_error_errno(errno, "Failed to open /sys/power/state: %m");
175 /* Configure the hibernation mode */
176 if (!strv_isempty(modes)) {
177 r = write_hibernate_location_info();
179 return log_error_errno(r, "Failed to write hibernation disk offset: %m");
180 r = write_mode(modes);
182 return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");;
185 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
188 "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
189 LOG_MESSAGE("Suspending system..."),
190 "SLEEP=%s", arg_verb,
193 r = write_state(&f, states);
195 return log_error_errno(r, "Failed to write /sys/power/state: %m");
198 "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR,
199 LOG_MESSAGE("System resumed."),
200 "SLEEP=%s", arg_verb,
203 arguments[1] = (char*) "post";
204 execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments);
209 static int read_wakealarm(uint64_t *result) {
210 _cleanup_free_ char *t = NULL;
212 if (read_one_line_file("/sys/class/rtc/rtc0/since_epoch", &t) >= 0)
213 return safe_atou64(t, result);
217 static int write_wakealarm(const char *str) {
219 _cleanup_fclose_ FILE *f = NULL;
222 f = fopen("/sys/class/rtc/rtc0/wakealarm", "we");
224 return log_error_errno(errno, "Failed to open /sys/class/rtc/rtc0/wakealarm: %m");
226 r = write_string_stream(f, str, 0);
228 return log_error_errno(r, "Failed to write '%s' to /sys/class/rtc/rtc0/wakealarm: %m", str);
233 static int execute_s2h(usec_t hibernate_delay_sec) {
235 _cleanup_strv_free_ char **hibernate_modes = NULL, **hibernate_states = NULL,
236 **suspend_modes = NULL, **suspend_states = NULL;
237 usec_t orig_time, cmp_time;
238 char time_str[DECIMAL_STR_MAX(uint64_t)];
241 r = parse_sleep_config("suspend", &suspend_modes, &suspend_states,
246 r = parse_sleep_config("hibernate", &hibernate_modes,
247 &hibernate_states, NULL);
251 r = read_wakealarm(&orig_time);
253 return log_error_errno(errno, "Failed to read time: %d", r);
255 orig_time += hibernate_delay_sec / USEC_PER_SEC;
256 xsprintf(time_str, "%" PRIu64, orig_time);
258 r = write_wakealarm(time_str);
262 log_debug("Set RTC wake alarm for %s", time_str);
264 r = execute(suspend_modes, suspend_states);
268 r = read_wakealarm(&cmp_time);
270 return log_error_errno(errno, "Failed to read time: %d", r);
273 r = write_wakealarm("0");
277 log_debug("Woke up at %"PRIu64, cmp_time);
279 /* if woken up after alarm time, hibernate */
280 if (cmp_time >= orig_time)
281 r = execute(hibernate_modes, hibernate_states);
286 #if 0 /// elogind calls execute() by itself and does not need another binary
287 static void help(void) {
288 printf("%s COMMAND\n\n"
289 "Suspend the system, hibernate the system, or both.\n\n"
291 " -h --help Show this help and exit\n"
292 " --version Print version string and exit\n"
293 " suspend Suspend the system\n"
294 " hibernate Hibernate the system\n"
295 " hybrid-sleep Both hibernate and suspend the system\n"
296 " suspend-then-hibernate Initially suspend and then hibernate\n"
297 " the system after a fixed period of time\n"
298 , program_invocation_short_name);
301 static int parse_argv(int argc, char *argv[]) {
306 static const struct option options[] = {
307 { "help", no_argument, NULL, 'h' },
308 { "version", no_argument, NULL, ARG_VERSION },
317 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
330 assert_not_reached("Unhandled option");
333 if (argc - optind != 1) {
334 log_error("Usage: %s COMMAND",
335 program_invocation_short_name);
339 arg_verb = argv[optind];
341 if (!streq(arg_verb, "suspend") &&
342 !streq(arg_verb, "hibernate") &&
343 !streq(arg_verb, "hybrid-sleep") &&
344 !streq(arg_verb, "suspend-then-hibernate")) {
345 log_error("Unknown command '%s'.", arg_verb);
349 return 1 /* work to do */;
352 int main(int argc, char *argv[]) {
353 _cleanup_strv_free_ char **modes = NULL, **states = NULL;
357 log_set_target(LOG_TARGET_AUTO);
358 log_parse_environment();
361 r = parse_argv(argc, argv);
365 r = parse_sleep_config(arg_verb, &modes, &states, &delay);
369 if (streq(arg_verb, "suspend-then-hibernate"))
370 r = execute_s2h(delay);
372 r = execute(modes, states);
374 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
377 int do_sleep(const char *verb, char **modes, char **states) {
379 arg_verb = (char*)verb;
380 return execute(modes, states);