diff options
Diffstat (limited to 'src/sleep/sleep.c')
-rw-r--r-- | src/sleep/sleep.c | 198 |
1 files changed, 165 insertions, 33 deletions
diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 518032ec69..f26aa453c9 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -1,42 +1,91 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ /*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 Zbigniew Jędrzejewski-Szmek - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see <http://www.gnu.org/licenses/>. + Copyright © 2010-2017 Canonical + Copyright © 2018 Dell Inc. ***/ #include <errno.h> #include <getopt.h> +#include <linux/fiemap.h> #include <stdio.h> #include "sd-messages.h" +#include "parse-util.h" #include "def.h" #include "exec-util.h" #include "fd-util.h" #include "fileio.h" #include "log.h" #include "sleep-config.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "util.h" static char* arg_verb = NULL; +static int write_hibernate_location_info(void) { + _cleanup_free_ char *device = NULL, *type = NULL; + _cleanup_free_ struct fiemap *fiemap = NULL; + char offset_str[DECIMAL_STR_MAX(uint64_t)]; + char device_str[DECIMAL_STR_MAX(uint64_t)]; + _cleanup_close_ int fd = -1; + struct stat stb; + uint64_t offset; + int r; + + r = find_hibernate_location(&device, &type, NULL, NULL); + if (r < 0) + return log_debug_errno(r, "Unable to find hibernation location: %m"); + + /* if it's a swap partition, we just write the disk to /sys/power/resume */ + if (streq(type, "partition")) + return write_string_file("/sys/power/resume", device, 0); + else if (!streq(type, "file")) + return log_debug_errno(EINVAL, "Invalid hibernate type %s: %m", + type); + + /* Only available in 4.17+ */ + if (access("/sys/power/resume_offset", F_OK) < 0) { + if (errno == ENOENT) + return 0; + return log_debug_errno(errno, "/sys/power/resume_offset unavailable: %m"); + } + + r = access("/sys/power/resume_offset", W_OK); + if (r < 0) + return log_debug_errno(errno, "/sys/power/resume_offset not writeable: %m"); + + fd = open(device, O_RDONLY | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) + return log_debug_errno(errno, "Unable to open '%s': %m", device); + r = fstat(fd, &stb); + if (r < 0) + return log_debug_errno(errno, "Unable to stat %s: %m", device); + r = read_fiemap(fd, &fiemap); + if (r < 0) + return log_debug_errno(r, "Unable to read extent map for '%s': %m", + device); + if (fiemap->fm_mapped_extents == 0) { + log_debug("No extents found in '%s'", device); + return -EINVAL; + } + offset = fiemap->fm_extents[0].fe_physical / page_size(); + xsprintf(offset_str, "%" PRIu64, offset); + r = write_string_file("/sys/power/resume_offset", offset_str, 0); + if (r < 0) + return log_debug_errno(r, "Failed to write offset '%s': %m", + offset_str); + + xsprintf(device_str, "%lx", (unsigned long)stb.st_dev); + r = write_string_file("/sys/power/resume", device_str, 0); + if (r < 0) + return log_debug_errno(r, "Failed to write device '%s': %m", + device_str); + return 0; +} + static int write_mode(char **modes) { int r = 0; char **mode; @@ -54,9 +103,6 @@ static int write_mode(char **modes) { r = k; } - if (r < 0) - log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); - return r; } @@ -78,7 +124,7 @@ static int write_state(FILE **f, char **states) { fclose(*f); *f = fopen("/sys/power/state", "we"); if (!*f) - return log_error_errno(errno, "Failed to open /sys/power/state: %m"); + return -errno; } return r; @@ -107,27 +153,30 @@ static int execute(char **modes, char **states) { return log_error_errno(errno, "Failed to open /sys/power/state: %m"); /* Configure the hibernation mode */ - r = write_mode(modes); - if (r < 0) - return r; + if (!strv_isempty(modes)) { + r = write_hibernate_location_info(); + if (r < 0) + return log_error_errno(r, "Failed to write hibernation disk offset: %m"); + r = write_mode(modes); + if (r < 0) + return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m");; + } execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments); log_struct(LOG_INFO, "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, LOG_MESSAGE("Suspending system..."), - "SLEEP=%s", arg_verb, - NULL); + "SLEEP=%s", arg_verb); r = write_state(&f, states); if (r < 0) - return r; + return log_error_errno(r, "Failed to write /sys/power/state: %m"); log_struct(LOG_INFO, "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, LOG_MESSAGE("System resumed."), - "SLEEP=%s", arg_verb, - NULL); + "SLEEP=%s", arg_verb); arguments[1] = (char*) "post"; execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments); @@ -135,6 +184,83 @@ static int execute(char **modes, char **states) { return r; } +static int read_wakealarm(uint64_t *result) { + _cleanup_free_ char *t = NULL; + + if (read_one_line_file("/sys/class/rtc/rtc0/since_epoch", &t) >= 0) + return safe_atou64(t, result); + return -EBADF; +} + +static int write_wakealarm(const char *str) { + + _cleanup_fclose_ FILE *f = NULL; + int r; + + f = fopen("/sys/class/rtc/rtc0/wakealarm", "we"); + if (!f) + return log_error_errno(errno, "Failed to open /sys/class/rtc/rtc0/wakealarm: %m"); + + r = write_string_stream(f, str, 0); + if (r < 0) + return log_error_errno(r, "Failed to write '%s' to /sys/class/rtc/rtc0/wakealarm: %m", str); + + return 0; +} + +static int execute_s2h(usec_t hibernate_delay_sec) { + + _cleanup_strv_free_ char **hibernate_modes = NULL, **hibernate_states = NULL, + **suspend_modes = NULL, **suspend_states = NULL; + usec_t orig_time, cmp_time; + char time_str[DECIMAL_STR_MAX(uint64_t)]; + int r; + + r = parse_sleep_config("suspend", &suspend_modes, &suspend_states, + NULL); + if (r < 0) + return r; + + r = parse_sleep_config("hibernate", &hibernate_modes, + &hibernate_states, NULL); + if (r < 0) + return r; + + r = read_wakealarm(&orig_time); + if (r < 0) + return log_error_errno(errno, "Failed to read time: %d", r); + + orig_time += hibernate_delay_sec / USEC_PER_SEC; + xsprintf(time_str, "%" PRIu64, orig_time); + + r = write_wakealarm(time_str); + if (r < 0) + return r; + + log_debug("Set RTC wake alarm for %s", time_str); + + r = execute(suspend_modes, suspend_states); + if (r < 0) + return r; + + r = read_wakealarm(&cmp_time); + if (r < 0) + return log_error_errno(errno, "Failed to read time: %d", r); + + /* reset RTC */ + r = write_wakealarm("0"); + if (r < 0) + return r; + + log_debug("Woke up at %"PRIu64, cmp_time); + + /* if woken up after alarm time, hibernate */ + if (cmp_time >= orig_time) + r = execute(hibernate_modes, hibernate_states); + + return r; +} + static void help(void) { printf("%s COMMAND\n\n" "Suspend the system, hibernate the system, or both.\n\n" @@ -144,6 +270,8 @@ static void help(void) { " suspend Suspend the system\n" " hibernate Hibernate the system\n" " hybrid-sleep Both hibernate and suspend the system\n" + " suspend-then-hibernate Initially suspend and then hibernate\n" + " the system after a fixed period of time\n" , program_invocation_short_name); } @@ -189,7 +317,8 @@ static int parse_argv(int argc, char *argv[]) { if (!streq(arg_verb, "suspend") && !streq(arg_verb, "hibernate") && - !streq(arg_verb, "hybrid-sleep")) { + !streq(arg_verb, "hybrid-sleep") && + !streq(arg_verb, "suspend-then-hibernate")) { log_error("Unknown command '%s'.", arg_verb); return -EINVAL; } @@ -199,6 +328,7 @@ static int parse_argv(int argc, char *argv[]) { int main(int argc, char *argv[]) { _cleanup_strv_free_ char **modes = NULL, **states = NULL; + usec_t delay = 0; int r; log_set_target(LOG_TARGET_AUTO); @@ -209,12 +339,14 @@ int main(int argc, char *argv[]) { if (r <= 0) goto finish; - r = parse_sleep_config(arg_verb, &modes, &states); + r = parse_sleep_config(arg_verb, &modes, &states, &delay); if (r < 0) goto finish; - r = execute(modes, states); - + if (streq(arg_verb, "suspend-then-hibernate")) + r = execute_s2h(delay); + else + r = execute(modes, states); finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } |