1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

Merge pull request #30307 from bluca/enforce_inhibitors

logind: always check for inhibitor locks
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2024-07-26 13:52:34 +02:00 committed by GitHub
commit e520b1258c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 101 additions and 72 deletions

11
NEWS
View File

@ -31,6 +31,17 @@ CHANGES WITH 257 in spe:
by default when combined with --scope, will be changed in a future
release to be enabled by default.
* systemd-logind now always obeys inhibitor locks, where previously it
ignored locks taken by the caller or when the caller was root. A
privileged caller can always close the other sessions, remove the
inhibitor locks, or use --force or --check-inhibitors=no to ignore the
inhibitors. This change thus doesn't affect security, since everything
that was possible before at a given privilege level is still possible,
but it should make the inhibitor logic easier to use and understand,
and also help avoiding accidental reboots and shutdowns. New 'delay-weak'
and 'block-weak' inhibitor modes were added, if taken they will make
the inhibitor lock work as in the previous versions.
— <place>, <date>
CHANGES WITH 256:

View File

@ -42,6 +42,9 @@ If such a lock is taken the operation will fail (but still may be overridden if
The InhibitDelayMaxSec= setting in [logind.conf(5)](http://www.freedesktop.org/software/systemd/man/logind.conf.html) controls the timeout for this. This is intended to be used by applications which need a synchronous way to execute actions before system suspend but shall not be allowed to block suspend indefinitely.
This mode is only available for _sleep_ and _shutdown_ locks.
3. _block-weak_ and _delay-weak_ that work as the non-weak counterparts, but that in addition may be ignored
automatically and silently under certain circumstances, unlike the formers which are always respected.
Inhibitor locks are taken via the Inhibit() D-Bus call on the logind Manager object:
```

View File

@ -665,15 +665,18 @@ node /org/freedesktop/login1 {
#define SD_LOGIND_KEXEC_REBOOT (UINT64_C(1) &lt;&lt; 1)
#define SD_LOGIND_SOFT_REBOOT (UINT64_C(1) &lt;&lt; 2)
#define SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP (UINT64_C(1) &lt;&lt; 3)
#define SD_LOGIND_SKIP_INHIBITORS (UINT64_C(1) &lt;&lt; 4)
</programlisting>
<para>When the <varname>flags</varname> is 0 then these methods behave just like the versions without
flags. When <constant>SD_LOGIND_ROOT_CHECK_INHIBITORS</constant> (0x01) is set, active inhibitors are
honoured for privileged users too. When <constant>SD_LOGIND_KEXEC_REBOOT</constant> (0x02) is set,
then <function>RebootWithFlags()</function> performs a kexec reboot if kexec kernel is loaded. When
<constant>SD_LOGIND_SOFT_REBOOT</constant> (0x04) is set, or
<constant>SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP</constant> (0x08) is set and a new root file system
has been set up on <literal>/run/nextroot/</literal>, then <function>RebootWithFlags()</function>
performs a userspace reboot only. <constant>SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP</constant> and
flags. Since systemd version 256 <constant>SD_LOGIND_ROOT_CHECK_INHIBITORS</constant> (0x01) is deprecated,
and active inhibitors are always honoured by default for privileged users too, and a new flag
<constant>SD_LOGIND_SKIP_INHIBITORS</constant> (0x04) can be specified to bypass inhibitors. When
<constant>SD_LOGIND_KEXEC_REBOOT</constant> (0x02) is set, then <function>RebootWithFlags()</function>
performs a kexec reboot if kexec kernel is loaded. When <constant>SD_LOGIND_SOFT_REBOOT</constant>
(0x04) is set, or <constant>SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP</constant> (0x08) is set and a
new root file system has been set up on <literal>/run/nextroot/</literal>, then
<function>RebootWithFlags()</function> performs a userspace reboot only.
<constant>SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP</constant> and
<constant>SD_LOGIND_KEXEC_REBOOT</constant> can be combined, with soft-reboot having precedence.</para>
<para><function>SetRebootParameter()</function> sets a parameter for a subsequent reboot operation.
@ -731,8 +734,10 @@ node /org/freedesktop/login1 {
should be a short human readable string identifying the reason why the lock is taken. Finally,
<varname>mode</varname> is either <literal>block</literal> or <literal>delay</literal> which encodes
whether the inhibit shall be consider mandatory or whether it should just delay the operation to a
certain maximum time. The method returns a file descriptor. The lock is released the moment this file
descriptor and all its duplicates are closed. For more information on the inhibition logic see
certain maximum time, while the <literal>block-weak</literal> and <literal>delay-weak</literal>
variants will create an inhibitor that is automatically ignored in some circumstances. The method
returns a file descriptor. The lock is released the moment this file descriptor and all its duplicates
are closed. For more information on the inhibition logic see
<ulink url="https://systemd.io/INHIBITOR_LOCKS">Inhibitor Locks</ulink>.
</para>
</refsect2>

View File

@ -2288,17 +2288,15 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err
<listitem>
<para>When system shutdown or sleep state is requested, this option controls checking of inhibitor
locks. It takes one of <literal>auto</literal>, <literal>yes</literal> or
<literal>no</literal>. Defaults to <literal>auto</literal>, which will behave like
<literal>yes</literal> for interactive invocations (i.e. from a TTY) and <literal>no</literal> for
non-interactive invocations. <literal>yes</literal> lets the request respect inhibitor locks.
<literal>no</literal> lets the request ignore inhibitor locks.</para>
<literal>no</literal>. Defaults to <literal>auto</literal>, which means logind will perform the
check and respect active inhibitor locks, but systemctl will only do a client-side check for
interactive invocations (i.e. from a TTY), so that a more friendly and informative error can be
returned to users. <literal>no</literal> disables both the systemctl and logind checks.</para>
<para>Applications can establish inhibitor locks to prevent certain important operations (such as
CD burning) from being interrupted by system shutdown or sleep. Any user may take these locks and
privileged users may override these locks. If any locks are taken, shutdown and sleep state
requests will normally fail (unless privileged). However, if <literal>no</literal> is specified or
<literal>auto</literal> is specified on a non-interactive requests, the operation will be
attempted. If locks are present, the operation may require additional privileges.</para>
requests will normally fail (unless explicitly overridden with <literal>no</literal>).</para>
<para>Option <option>--force</option> provides another way to override inhibitors.</para>

View File

@ -92,19 +92,17 @@
<varlistentry>
<term><option>--mode=</option></term>
<listitem><para>Takes either <literal>block</literal> or
<literal>delay</literal> and describes how the lock is
applied. If <literal>block</literal> is used (the default),
the lock prohibits any of the requested operations without
time limit, and only privileged users may override it. If
<literal>delay</literal> is used, the lock can only delay the
requested operations for a limited time. If the time elapses,
the lock is ignored and the operation executed. The time limit
may be specified in
<listitem><para>Takes <literal>block</literal>, <literal>delay</literal>,
<literal>block-weak</literal> or <literal>delay-weak</literal> and describes how the lock is
applied. If <literal>block</literal> is used (the default), the lock prohibits any of the requested
operations without time limit, and only privileged users may override it. If
<literal>delay</literal> is used, the lock can only delay the requested operations for a limited
time. If the time elapses, the lock is ignored and the operation executed. The time limit may be
specified in
<citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Note that <literal>delay</literal> is only available for
<literal>sleep</literal> and
<literal>shutdown</literal>.</para></listitem>
Note that <literal>delay</literal> is only available for <literal>sleep</literal> and
<literal>shutdown</literal>. In addition, the weak variants will automatically and silently be
bypassed under some circumstances.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -33,6 +33,6 @@ _arguments \
'--what=[Operations to inhibit]:options:_systemd-inhibit_what' \
'--who=[A descriptive string who is inhibiting]:who is inhibiting:' \
'--why=[A descriptive string why is being inhibited]:reason for the lock:' \
'--mode=[One of block or delay]:lock mode:( block delay )' \
'--mode=[One of block, block-weak, delay, or delay-weak]:lock mode:( block block-weak delay delay-weak )' \
'--list[List active inhibitors]' \
'*:commands:_systemd-inhibit_commands'

View File

@ -8,11 +8,12 @@
#define SD_LOGIND_REBOOT_VIA_KEXEC (UINT64_C(1) << 1)
#define SD_LOGIND_SOFT_REBOOT (UINT64_C(1) << 2)
#define SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP (UINT64_C(1) << 3)
#define SD_LOGIND_SKIP_INHIBITORS (UINT64_C(1) << 4)
/* For internal use only */
#define SD_LOGIND_INTERACTIVE (UINT64_C(1) << 63)
#define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC (SD_LOGIND_ROOT_CHECK_INHIBITORS|SD_LOGIND_REBOOT_VIA_KEXEC|SD_LOGIND_SOFT_REBOOT|SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP)
#define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC (SD_LOGIND_ROOT_CHECK_INHIBITORS|SD_LOGIND_REBOOT_VIA_KEXEC|SD_LOGIND_SOFT_REBOOT|SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP|SD_LOGIND_SKIP_INHIBITORS)
#define SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_ALL (SD_LOGIND_SHUTDOWN_AND_SLEEP_FLAGS_PUBLIC|SD_LOGIND_INTERACTIVE)
bool session_id_valid(const char *id);

View File

@ -157,7 +157,7 @@ static int help(void) {
" handle-lid-switch\n"
" --who=STRING A descriptive string who is inhibiting\n"
" --why=STRING A descriptive string why is being inhibited\n"
" --mode=MODE One of block or delay\n"
" --mode=MODE One of block, block-weak, delay, or delay-weak\n"
" --list List active inhibitors\n"
"\nSee the %s for details.\n",
program_invocation_short_name,

View File

@ -234,7 +234,7 @@ static int handle_action_execute(
/* If the actual operation is inhibited, warn and fail */
if (inhibit_what_is_valid(inhibit_operation) &&
!ignore_inhibited &&
manager_is_inhibited(m, inhibit_operation, INHIBIT_BLOCK, NULL, false, false, 0, &offending)) {
manager_is_inhibited(m, inhibit_operation, /* block= */ true, NULL, false, false, 0, &offending)) {
_cleanup_free_ char *comm = NULL, *u = NULL;
(void) pidref_get_comm(&offending->pid, &comm);
@ -372,7 +372,7 @@ int manager_handle_action(
/* If the key handling is inhibited, don't do anything */
if (inhibit_key > 0) {
if (manager_is_inhibited(m, inhibit_key, INHIBIT_BLOCK, NULL, true, false, 0, NULL)) {
if (manager_is_inhibited(m, inhibit_key, /* block= */ true, NULL, true, false, 0, NULL)) {
log_debug("Refusing %s operation, %s is inhibited.",
handle_action_to_string(handle),
inhibit_what_to_string(inhibit_key));

View File

@ -411,7 +411,7 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
assert(m);
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, INHIBIT_BLOCK, t, false, false, 0, NULL);
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, /* block= */ true, t, false, false, 0, NULL);
HASHMAP_FOREACH(s, m->sessions) {
dual_timestamp k;

View File

@ -311,7 +311,7 @@ static int property_get_inhibited(
assert(bus);
assert(reply);
w = manager_inhibit_what(m, streq(property, "BlockInhibited") ? INHIBIT_BLOCK : INHIBIT_DELAY);
w = manager_inhibit_what(m, /* block= */ streq(property, "BlockInhibited"));
return sd_bus_message_append(reply, "s", inhibit_what_to_string(w));
}
@ -1881,7 +1881,7 @@ int manager_dispatch_delayed(Manager *manager, bool timeout) {
if (!manager->delayed_action || manager->action_job)
return 0;
if (manager_is_inhibited(manager, manager->delayed_action->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, &offending)) {
if (manager_is_inhibited(manager, manager->delayed_action->inhibit_what, /* block= */ false, NULL, false, false, 0, &offending)) {
_cleanup_free_ char *comm = NULL, *u = NULL;
if (!timeout)
@ -1978,7 +1978,7 @@ int bus_manager_shutdown_or_sleep_now_or_later(
delayed =
m->inhibit_delay_max > 0 &&
manager_is_inhibited(m, a->inhibit_what, INHIBIT_DELAY, NULL, false, false, 0, NULL);
manager_is_inhibited(m, a->inhibit_what, /* block= */ false, NULL, false, false, 0, NULL);
if (delayed)
/* Shutdown is delayed, keep in mind what we
@ -2021,7 +2021,7 @@ static int verify_shutdown_creds(
return r;
multiple_sessions = r > 0;
blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
blocked = manager_is_inhibited(m, a->inhibit_what, /* block= */ true, NULL, false, true, uid, NULL);
interactive = flags & SD_LOGIND_INTERACTIVE;
if (multiple_sessions) {
@ -2040,17 +2040,23 @@ static int verify_shutdown_creds(
}
if (blocked) {
/* We don't check polkit for root here, because you can't be more privileged than root */
if (uid == 0 && (flags & SD_LOGIND_ROOT_CHECK_INHIBITORS))
if (!FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS))
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED,
"Access denied to root due to active block inhibitor");
"Access denied due to active block inhibitor");
/* We want to always ask here, even for root, to only allow bypassing if explicitly allowed
* by polkit */
PolkitFlags polkit_flags = POLKIT_ALWAYS_QUERY;
if (interactive)
polkit_flags |= POLKIT_ALLOW_INTERACTIVE;
r = bus_verify_polkit_async_full(
message,
a->polkit_action_ignore_inhibit,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
interactive ? POLKIT_ALLOW_INTERACTIVE : 0,
polkit_flags,
&m->polkit_registry,
error);
if (r < 0)
@ -2749,7 +2755,7 @@ static int method_can_shutdown_or_sleep(
return r;
multiple_sessions = r > 0;
blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL);
blocked = manager_is_inhibited(m, a->inhibit_what, /* block= */ true, NULL, false, true, uid, NULL);
if (check_unit_state && a->target) {
_cleanup_free_ char *load_state = NULL;
@ -3587,7 +3593,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
"Invalid mode specification %s", mode);
/* Delay is only supported for shutdown/sleep */
if (mm == INHIBIT_DELAY && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP)))
if (IN_SET(mm, INHIBIT_DELAY, INHIBIT_DELAY_WEAK) && (w & ~(INHIBIT_SHUTDOWN|INHIBIT_SLEEP)))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Delay inhibitors only supported for shutdown and sleep");
@ -3604,8 +3610,8 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error
r = bus_verify_polkit_async(
message,
v == INHIBIT_SHUTDOWN ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-shutdown" : "org.freedesktop.login1.inhibit-delay-shutdown") :
v == INHIBIT_SLEEP ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-sleep" : "org.freedesktop.login1.inhibit-delay-sleep") :
v == INHIBIT_SHUTDOWN ? (IN_SET(mm, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK) ? "org.freedesktop.login1.inhibit-block-shutdown" : "org.freedesktop.login1.inhibit-delay-shutdown") :
v == INHIBIT_SLEEP ? (IN_SET(mm, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK) ? "org.freedesktop.login1.inhibit-block-sleep" : "org.freedesktop.login1.inhibit-delay-sleep") :
v == INHIBIT_IDLE ? "org.freedesktop.login1.inhibit-block-idle" :
v == INHIBIT_HANDLE_POWER_KEY ? "org.freedesktop.login1.inhibit-handle-power-key" :
v == INHIBIT_HANDLE_SUSPEND_KEY ? "org.freedesktop.login1.inhibit-handle-suspend-key" :

View File

@ -165,7 +165,7 @@ static int bus_manager_send_inhibited_change(Inhibitor *i) {
assert(i);
property = i->mode == INHIBIT_BLOCK ? "BlockInhibited" : "DelayInhibited";
property = IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK) ? "BlockInhibited" : "DelayInhibited";
return manager_send_changed(i->manager, property, NULL);
}
@ -363,14 +363,16 @@ bool inhibitor_is_orphan(Inhibitor *i) {
return false;
}
InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm) {
InhibitWhat manager_inhibit_what(Manager *m, bool block) {
Inhibitor *i;
InhibitWhat what = 0;
assert(m);
HASHMAP_FOREACH(i, m->inhibitors)
if (i->mode == mm && i->started)
if (i->started &&
((!block && IN_SET(i->mode, INHIBIT_DELAY, INHIBIT_DELAY_WEAK)) ||
(block && IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK))))
what |= i->what;
return what;
@ -399,7 +401,7 @@ static int pidref_is_active_session(Manager *m, const PidRef *pid) {
bool manager_is_inhibited(
Manager *m,
InhibitWhat w,
InhibitMode mm,
bool block,
dual_timestamp *since,
bool ignore_inactive,
bool ignore_uid,
@ -421,13 +423,14 @@ bool manager_is_inhibited(
if (!(i->what & w))
continue;
if (i->mode != mm)
if ((block && !IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK)) ||
(!block && !IN_SET(i->mode, INHIBIT_DELAY, INHIBIT_DELAY_WEAK)))
continue;
if (ignore_inactive && pidref_is_active_session(m, &i->pid) <= 0)
continue;
if (ignore_uid && i->uid == uid)
if (IN_SET(i->mode, INHIBIT_BLOCK_WEAK, INHIBIT_DELAY_WEAK) && ignore_uid && i->uid == uid)
continue;
if (!inhibited ||
@ -525,8 +528,10 @@ int inhibit_what_from_string(const char *s) {
}
static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = {
[INHIBIT_BLOCK] = "block",
[INHIBIT_DELAY] = "delay"
[INHIBIT_BLOCK] = "block",
[INHIBIT_BLOCK_WEAK] = "block-weak",
[INHIBIT_DELAY] = "delay",
[INHIBIT_DELAY_WEAK] = "delay-weak"
};
DEFINE_STRING_TABLE_LOOKUP(inhibit_mode, InhibitMode);

View File

@ -20,7 +20,9 @@ typedef enum InhibitWhat {
typedef enum InhibitMode {
INHIBIT_BLOCK,
INHIBIT_BLOCK_WEAK,
INHIBIT_DELAY,
INHIBIT_DELAY_WEAK,
_INHIBIT_MODE_MAX,
_INHIBIT_MODE_INVALID = -EINVAL,
} InhibitMode;
@ -65,8 +67,8 @@ int inhibitor_create_fifo(Inhibitor *i);
bool inhibitor_is_orphan(Inhibitor *i);
InhibitWhat manager_inhibit_what(Manager *m, InhibitMode mm);
bool manager_is_inhibited(Manager *m, InhibitWhat w, InhibitMode mm, dual_timestamp *since, bool ignore_inactive, bool ignore_uid, uid_t uid, Inhibitor **offending);
InhibitWhat manager_inhibit_what(Manager *m, bool block);
bool manager_is_inhibited(Manager *m, InhibitWhat w, bool block, dual_timestamp *since, bool ignore_inactive, bool ignore_uid, uid_t uid, Inhibitor **offending);
static inline bool inhibit_what_is_valid(InhibitWhat w) {
return w > 0 && w < _INHIBIT_WHAT_MAX;

View File

@ -82,6 +82,7 @@ int logind_reboot(enum action a) {
return 0;
SET_FLAG(flags, SD_LOGIND_ROOT_CHECK_INHIBITORS, arg_check_inhibitors > 0);
SET_FLAG(flags, SD_LOGIND_SKIP_INHIBITORS, arg_check_inhibitors == 0);
SET_FLAG(flags,
SD_LOGIND_REBOOT_VIA_KEXEC,
a == ACTION_KEXEC || (a == ACTION_REBOOT && getenv_bool("SYSTEMCTL_SKIP_AUTO_KEXEC") <= 0));
@ -94,17 +95,17 @@ int logind_reboot(enum action a) {
SET_FLAG(flags, SD_LOGIND_SOFT_REBOOT, a == ACTION_SOFT_REBOOT);
r = bus_call_method(bus, bus_login_mgr, method_with_flags, &error, NULL, "t", flags);
if (r < 0 && FLAGS_SET(flags, SD_LOGIND_SKIP_INHIBITORS) &&
sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) {
sd_bus_error_free(&error);
flags &= ~SD_LOGIND_SKIP_INHIBITORS;
r = bus_call_method(bus, bus_login_mgr, method_with_flags, &error, NULL, "t", flags);
}
if (r < 0 && FLAGS_SET(flags, SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP) &&
sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)) {
sd_bus_error_free(&error);
r = bus_call_method(
bus,
bus_login_mgr,
method_with_flags,
&error,
NULL,
"t",
flags & ~SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP);
flags &= ~SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP;
r = bus_call_method(bus, bus_login_mgr, method_with_flags, &error, NULL, "t", flags);
}
if (r >= 0)
return 0;
@ -144,13 +145,8 @@ int logind_check_inhibitors(enum action a) {
if (arg_when > 0)
return 0;
if (arg_check_inhibitors < 0) {
if (geteuid() == 0)
return 0;
if (!on_tty())
return 0;
}
if (arg_check_inhibitors < 0 && !on_tty())
return 0;
if (arg_transport != BUS_TRANSPORT_LOCAL)
return 0;
@ -172,7 +168,10 @@ int logind_check_inhibitors(enum action a) {
_cleanup_free_ char *comm = NULL, *user = NULL;
_cleanup_strv_free_ char **sv = NULL;
if (!streq(mode, "block"))
if (!STR_IN_SET(mode, "block", "block-weak"))
continue;
if (streq(mode, "block-weak") && (geteuid() == 0 || geteuid() == uid || !on_tty()))
continue;
sv = strv_split(what, ":");
@ -233,6 +232,7 @@ int logind_check_inhibitors(enum action a) {
return log_error_errno(SYNTHETIC_ERRNO(EPERM),
"Please retry operation after closing inhibitors and logging out other users.\n"
"'systemd-inhibit' can be used to list active inhibitors.\n"
"Alternatively, ignore inhibitors and users with 'systemctl %s -i'.",
action_table[a].verb);
#else