Merge pull request #29740 from YHNdnzj/sleep-round-four
sleep: make sure we clear HibernateLocation on all error paths
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
+170
-169
@@ -7,11 +7,9 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/fiemap.h>
|
||||
#include <poll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user