sleep: Always freeze user.slice

Previously, we'd only freeze user.slice in the case of s2h, because we
didn't want the user session to resume while systemd was transitioning
from suspend to hibernate.

This commit extends this freezing behavior to all sleep modes.

We also have an environment variable to disable the freezing behavior
outright. This is a necessary workaround for someone that has hooks
in /usr/lib/systemd/system-sleep/ which communicate with some
process running under user.slice, or if someone is using the proprietary
NVIDIA driver which breaks when user.slice is frozen (issue #27559)

Fixes #27559
This commit is contained in:
Adrian Vovk
2023-12-23 17:03:42 -05:00
parent 7483708131
commit 0b958bb3ee
3 changed files with 35 additions and 28 deletions

View File

@@ -630,6 +630,14 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
file (containing firmware measurement data) to read. This allows overriding
the default of `/sys/kernel/security/tpm0/binary_bios_measurements`.
`systemd-sleep`:
* `$SYSTEMD_SLEEP_FREEZE_USER_SESSIONS` - Takes a boolean. When true (the default),
`user.slice` will be frozen during sleep. When false it will not be. We recommend
against using this variable, because it can lead to undesired behavior, especially
for systems that use home directory encryption and for
`systemd-suspend-then-hibernate.service`.
Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
`busctl`):

View File

@@ -66,7 +66,9 @@
same executables are run, but the first argument is now
<literal>post</literal>. All executables in this directory are
executed in parallel, and execution of the action is not continued
until all executables have finished.</para>
until all executables have finished. Note that <filename>user.slice</filename> will
be frozen while the executables are running, so they should not attempt to
communicate with any user services expecting a reply.</para>
<para>Note that scripts or binaries dropped in
<filename>/usr/lib/systemd/system-sleep/</filename> are intended
@@ -90,6 +92,11 @@
<filename>sleep.conf.d</filename> file. See
<citerefentry><refentrytitle>systemd-sleep.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<para>Note that by default these services freeze <filename>user.slice</filename> while they run. This prevents
the execution of any process in any of the user sessions while the system is entering into and resuming from
sleep. Thus, this prevents the hooks in <filename>/usr/lib/systemd/system-sleep/</filename>, or any other process
for that matter, from communicating with any user session process during sleep.</para>
</refsect1>
<refsect1>

View File

@@ -23,10 +23,12 @@
#include "build.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-unit-util.h"
#include "bus-util.h"
#include "constants.h"
#include "devnum-util.h"
#include "efivars.h"
#include "env-util.h"
#include "exec-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -444,38 +446,11 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) {
return 1;
}
/* Freeze when invoked and thaw on cleanup */
static int freeze_thaw_user_slice(const char **method) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
if (!method || !*method)
return 0;
r = bus_connect_system_systemd(&bus);
if (r < 0)
return log_debug_errno(r, "Failed to open connection to systemd: %m");
(void) sd_bus_set_method_call_timeout(bus, FREEZE_TIMEOUT);
r = bus_call_method(bus, bus_systemd_mgr, *method, &error, NULL, "s", SPECIAL_USER_SLICE);
if (r < 0)
return log_debug_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r));
return 1;
}
static int execute_s2h(const SleepConfig *sleep_config) {
_unused_ _cleanup_(freeze_thaw_user_slice) const char *auto_method_thaw = "ThawUnit";
int r;
assert(sleep_config);
r = freeze_thaw_user_slice(&(const char*) { "FreezeUnit" });
if (r < 0)
log_warning_errno(r, "Failed to freeze unit user.slice, ignoring: %m");
/* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case
* we'll busy poll for the configured interval instead */
if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) {
@@ -599,6 +574,7 @@ static int parse_argv(int argc, char *argv[]) {
}
static int run(int argc, char *argv[]) {
_cleanup_(unit_freezer_done_thaw) UnitFreezer user_slice_freezer = {};
_cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL;
int r;
@@ -617,6 +593,22 @@ static int run(int argc, char *argv[]) {
"Sleep operation \"%s\" is disabled by configuration, refusing.",
sleep_operation_to_string(arg_operation));
/* Freeze the user sessions */
r = getenv_bool("SYSTEMD_SLEEP_FREEZE_USER_SESSIONS");
if (r < 0 && r != -ENXIO)
log_warning_errno(r, "Cannot parse value of $SYSTEMD_SLEEP_FREEZE_USER_SESSIONS, ignoring.");
if (r != 0) {
r = unit_freezer_new_freeze(SPECIAL_USER_SLICE, &user_slice_freezer);
if (r < 0)
log_warning_errno(r, "Failed to freeze user sessions, ignoring: %m");
else
log_info("Froze user sessions");
} else
log_notice("User sessions remain unfrozen on explicit request "
"($SYSTEMD_SLEEP_FREEZE_USER_SESSIONS is set to false). This is not recommended, "
"and might result in unexpected behavior, particularly in sysupend-then-hibernate "
"operations or setups with encrypted home directories.");
switch (arg_operation) {
case SLEEP_SUSPEND_THEN_HIBERNATE: