diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index ab09530355..77b5ee5b84 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -305,7 +305,7 @@ static int sleep_supported_internal( return false; } - if (IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP)) { + if (sleep_operation_is_hibernation(operation)) { r = sleep_mode_supported(sleep_config->modes[operation]); if (r < 0) return r; diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index bc10a5b39b..bc5aeb91bd 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -20,6 +20,10 @@ typedef enum SleepOperation { const char* sleep_operation_to_string(SleepOperation s) _const_; SleepOperation sleep_operation_from_string(const char *s) _pure_; +static inline bool sleep_operation_is_hibernation(SleepOperation operation) { + return IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP); +} + typedef struct SleepConfig { bool allow[_SLEEP_OPERATION_MAX]; diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 8bfc9ee6cc..21af3e9e52 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -7,11 +7,9 @@ #include #include #include -#include #include -#include -#include #include +#include #include #include @@ -22,7 +20,6 @@ #include "battery-capacity.h" #include "battery-util.h" -#include "btrfs-util.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -126,46 +123,187 @@ static int write_efi_hibernate_location(const HibernationDevice *hibernation_dev #endif } -static int write_mode(char **modes) { +static int write_state(int fd, char * const *states) { + int r = 0; + + assert(fd >= 0); + assert(states); + + STRV_FOREACH(state, states) { + _cleanup_fclose_ FILE *f = NULL; + int k; + + k = fdopen_independent(fd, "we", &f); + if (k < 0) + return RET_GATHER(r, k); + + k = write_string_stream(f, *state, WRITE_STRING_FILE_DISABLE_BUFFER); + if (k >= 0) { + log_debug("Using sleep state '%s'.", *state); + return 0; + } + + RET_GATHER(r, log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state)); + } + + return r; +} + +static int write_mode(char * const *modes) { int r = 0; STRV_FOREACH(mode, modes) { int k; k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER); - if (k >= 0) + if (k >= 0) { + log_debug("Using sleep disk mode '%s'.", *mode); return 0; + } - log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode); - if (r >= 0) - r = k; + RET_GATHER(r, log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode)); } return r; } -static int write_state(FILE **f, char **states) { - int r = 0; +static int lock_all_homes(void) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; - assert(f); - assert(*f); + /* Let's synchronously lock all home directories managed by homed that have been marked for it. This + * way the key material required to access these volumes is hopefully removed from memory. */ - STRV_FOREACH(state, states) { - int k; + r = bus_connect_system_systemd(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); - k = write_string_stream(*f, *state, WRITE_STRING_FILE_DISABLE_BUFFER); - if (k >= 0) - return 0; - log_debug_errno(k, "Failed to write '%s' to /sys/power/state: %m", *state); - if (r >= 0) - r = k; + r = bus_message_new_method_call(bus, &m, bus_home_mgr, "LockAllHomes"); + if (r < 0) + return bus_log_create_error(r); - fclose(*f); - *f = fopen("/sys/power/state", "we"); - if (!*f) - return -errno; + /* If homed is not running it can't have any home directories active either. */ + r = sd_bus_message_set_auto_start(m, false); + if (r < 0) + return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m"); + + r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + if (!bus_error_is_unknown_service(&error)) + return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r)); + + log_debug("systemd-homed is not running, locking of home directories skipped."); + } else + log_debug("Successfully requested locking of all home directories."); + return 0; +} + +static int execute( + const SleepConfig *sleep_config, + SleepOperation operation, + const char *action) { + + const char *arguments[] = { + NULL, + "pre", + /* NB: we use 'arg_operation' instead of 'operation' here, as we want to communicate the overall + * operation here, not the specific one, in case of s2h. */ + sleep_operation_to_string(arg_operation), + NULL + }; + static const char* const dirs[] = { + SYSTEM_SLEEP_PATH, + NULL + }; + + _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {}; + _cleanup_close_ int state_fd = -EBADF; + int r; + + assert(sleep_config); + assert(operation >= 0); + assert(operation < _SLEEP_OPERATION_CONFIG_MAX); /* Others are handled by execute_s2h() instead */ + + if (strv_isempty(sleep_config->states[operation])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No sleep states configured for sleep operation %s, can't sleep.", + sleep_operation_to_string(operation)); + + /* This file is opened first, so that if we hit an error, we can abort before modifying any state. */ + state_fd = open("/sys/power/state", O_WRONLY|O_CLOEXEC); + if (state_fd < 0) + return -errno; + + /* Configure hibernation settings if we are supposed to hibernate */ + if (sleep_operation_is_hibernation(operation)) { + bool resume_set; + + r = find_suitable_hibernation_device(&hibernation_device); + if (r < 0) + return log_error_errno(r, "Failed to find location to hibernate to: %m"); + resume_set = r > 0; + + r = write_efi_hibernate_location(&hibernation_device, !resume_set); + if (!resume_set) { + if (r == -EOPNOTSUPP) + return log_error_errno(r, "No valid 'resume=' option found, refusing to hibernate."); + if (r < 0) + return r; + + r = write_resume_config(hibernation_device.devno, hibernation_device.offset, hibernation_device.path); + if (r < 0) { + log_error_errno(r, "Failed to write hibernation device to /sys/power/resume or /sys/power/resume_offset: %m"); + goto fail; + } + } + + r = write_mode(sleep_config->modes[operation]); + if (r < 0) { + log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); + goto fail; + } } + /* Pass an action string to the call-outs. This is mostly our operation string, except if the + * hibernate step of s-t-h fails, in which case we communicate that with a separate action. */ + if (!action) + action = sleep_operation_to_string(operation); + + if (setenv("SYSTEMD_SLEEP_ACTION", action, /* overwrite = */ 1) < 0) + log_warning_errno(errno, "Failed to set SYSTEMD_SLEEP_ACTION=%s, ignoring: %m", action); + + (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, (char **) arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + (void) lock_all_homes(); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, + LOG_MESSAGE("Performing sleep operation '%s'...", sleep_operation_to_string(operation)), + "SLEEP=%s", sleep_operation_to_string(arg_operation)); + + r = write_state(state_fd, sleep_config->states[operation]); + if (r < 0) + log_struct_errno(LOG_ERR, r, + "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, + LOG_MESSAGE("Failed to put system to sleep. System resumed again: %m"), + "SLEEP=%s", sleep_operation_to_string(arg_operation)); + else + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, + LOG_MESSAGE("System returned from sleep operation '%s'.", sleep_operation_to_string(arg_operation)), + "SLEEP=%s", sleep_operation_to_string(arg_operation)); + + arguments[1] = "post"; + (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, (char **) arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); + + if (r >= 0) + return 0; + +fail: + if (sleep_operation_is_hibernation(operation) && is_efi_boot()) + (void) efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); + return r; } @@ -206,148 +344,6 @@ static int check_wakeup_type(void) { return false; } -static int lock_all_homes(void) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - /* Let's synchronously lock all home directories managed by homed that have been marked for it. This - * way the key material required to access these volumes is hopefully removed from memory. */ - - r = sd_bus_open_system(&bus); - if (r < 0) - return log_warning_errno(r, "Failed to connect to system bus, ignoring: %m"); - - r = bus_message_new_method_call(bus, &m, bus_home_mgr, "LockAllHomes"); - if (r < 0) - return bus_log_create_error(r); - - /* If homed is not running it can't have any home directories active either. */ - r = sd_bus_message_set_auto_start(m, false); - if (r < 0) - return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m"); - - r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL); - if (r < 0) { - if (!bus_error_is_unknown_service(&error)) - return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r)); - - log_debug("systemd-homed is not running, locking of home directories skipped."); - } else - log_debug("Successfully requested locking of all home directories."); - return 0; -} - -static int execute( - const SleepConfig *sleep_config, - SleepOperation operation, - const char *action) { - - char *arguments[] = { - NULL, - (char*) "pre", - /* NB: we use 'arg_operation' instead of 'operation' here, as we want to communicate the overall - * operation here, not the specific one, in case of s2h. */ - (char*) sleep_operation_to_string(arg_operation), - NULL - }; - static const char* const dirs[] = { - SYSTEM_SLEEP_PATH, - NULL - }; - - _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {}; - _cleanup_fclose_ FILE *f = NULL; - char **modes, **states; - int r; - - assert(sleep_config); - assert(operation >= 0); - assert(operation < _SLEEP_OPERATION_MAX); - assert(operation != SLEEP_SUSPEND_THEN_HIBERNATE); /* Handled by execute_s2h() instead */ - - states = sleep_config->states[operation]; - modes = sleep_config->modes[operation]; - - if (strv_isempty(states)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No sleep states configured for sleep operation %s, can't sleep.", - sleep_operation_to_string(operation)); - - /* This file is opened first, so that if we hit an error, - * we can abort before modifying any state. */ - f = fopen("/sys/power/state", "we"); - if (!f) - return log_error_errno(errno, "Failed to open /sys/power/state: %m"); - - setvbuf(f, NULL, _IONBF, 0); - - /* Configure hibernation settings if we are supposed to hibernate */ - if (!strv_isempty(modes)) { - bool resume_set; - - r = find_suitable_hibernation_device(&hibernation_device); - if (r < 0) - return log_error_errno(r, "Failed to find location to hibernate to: %m"); - resume_set = r > 0; - - r = write_efi_hibernate_location(&hibernation_device, !resume_set); - if (!resume_set) { - if (r == -EOPNOTSUPP) - return log_error_errno(r, "No valid 'resume=' option found, refusing to hibernate."); - if (r < 0) - return r; - - r = write_resume_config(hibernation_device.devno, hibernation_device.offset, hibernation_device.path); - if (r < 0) { - if (is_efi_boot()) - (void) efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); - - return log_error_errno(r, "Failed to prepare for hibernation: %m"); - } - } - - r = write_mode(modes); - if (r < 0) - return log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); - } - - /* Pass an action string to the call-outs. This is mostly our operation string, except if the - * hibernate step of s-t-h fails, in which case we communicate that with a separate action. */ - if (!action) - action = sleep_operation_to_string(operation); - - r = setenv("SYSTEMD_SLEEP_ACTION", action, 1); - if (r != 0) - log_warning_errno(errno, "Error setting SYSTEMD_SLEEP_ACTION=%s, ignoring: %m", action); - - (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); - (void) lock_all_homes(); - - log_struct(LOG_INFO, - "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR, - LOG_MESSAGE("Entering sleep state '%s'...", sleep_operation_to_string(operation)), - "SLEEP=%s", sleep_operation_to_string(arg_operation)); - - r = write_state(&f, states); - if (r < 0) - log_struct_errno(LOG_ERR, r, - "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, - LOG_MESSAGE("Failed to put system to sleep. System resumed again: %m"), - "SLEEP=%s", sleep_operation_to_string(arg_operation)); - else - log_struct(LOG_INFO, - "MESSAGE_ID=" SD_MESSAGE_SLEEP_STOP_STR, - LOG_MESSAGE("System returned from sleep state."), - "SLEEP=%s", sleep_operation_to_string(arg_operation)); - - arguments[1] = (char*) "post"; - (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); - - return r; -} - static int custom_timer_suspend(const SleepConfig *sleep_config) { usec_t hibernate_timestamp; int r; @@ -551,7 +547,8 @@ static int help(void) { " 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" + " the system after a fixed period of time or\n" + " when battery is low\n" "\nSee the %s for details.\n", program_invocation_short_name, link); @@ -560,6 +557,7 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { + enum { ARG_VERSION = 0x100, }; @@ -577,6 +575,7 @@ static int parse_argv(int argc, char *argv[]) { while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) switch (c) { + case 'h': return help(); @@ -588,6 +587,7 @@ static int parse_argv(int argc, char *argv[]) { default: assert_not_reached(); + } if (argc - optind != 1) @@ -634,7 +634,7 @@ static int run(int argc, char *argv[]) { * asked us to do both: suspend + hibernate, and it's almost certainly the * hibernation that failed, hence still do the other thing, the suspend. */ - log_notice("Couldn't hybrid sleep, will try to suspend instead."); + log_notice_errno(r, "Couldn't hybrid sleep, will try to suspend instead: %m"); r = execute(sleep_config, SLEEP_SUSPEND, "suspend-after-failed-hybrid-sleep"); } @@ -644,6 +644,7 @@ static int run(int argc, char *argv[]) { default: r = execute(sleep_config, arg_operation, NULL); break; + } return r;