login1: Add "inhibited" state to Can* methods

Desktops cannot currently tell the difference between a power action
being set to challenge permanently (i.e. because the sysadmin wanted to
restrict the given user) and a power action being set to challenge
because it's temporarily inhibited.

Thus, a desktop might take an action that is valid in the first case but
not the second case. For instance: GNOME hides all of its automatic
suspend settings from the UI whenever a sleep inhibitor is active.

This now returns a new state: "inhibited". It communicates to the
desktop that the action is available normally, but at the moment the
desktop isn't allowed to perform the action due to an inhibitor.

Related: https://github.com/systemd/systemd/issues/37311
This commit is contained in:
Adrian Vovk
2026-02-23 20:03:06 -05:00
parent 04d2a86e66
commit 46075fc92b
2 changed files with 52 additions and 48 deletions
+13 -6
View File
@@ -711,12 +711,19 @@ node /org/freedesktop/login1 {
<function>CanRebootParameter()</function>, <function>CanRebootToFirmwareSetup()</function>,
<function>CanRebootToBootLoaderMenu()</function>, and <function>CanRebootToBootLoaderEntry()</function>
test whether the system supports the respective operation and whether the calling user is allowed to
execute it. Returns one of <literal>na</literal>, <literal>yes</literal>, <literal>no</literal>, and
<literal>challenge</literal>. If <literal>na</literal> is returned, the operation is not available because
hardware, kernel, or drivers do not support it. If <literal>yes</literal> is returned, the operation is
supported and the user may execute the operation without further authentication. If <literal>no</literal>
is returned, the operation is available but the user is not allowed to execute the operation. If
<literal>challenge</literal> is returned, the operation is available but only after authorization.</para>
execute it. Returns one of <literal>na</literal>, <literal>yes</literal>, <literal>no</literal>,
<literal>challenge</literal>, and <literal>inhibited</literal>. If <literal>na</literal> is returned,
the operation is not available because hardware, kernel, or drivers do not support it. If <literal>yes</literal>
is returned, the operation is supported and the user may execute the operation without further authentication.
If <literal>no</literal> is returned, the operation is available but the user is not allowed to execute
the operation. If <literal>challenge</literal> is returned, the operation is available but only after
authorization. If <literal>inhibited</literal> is returned, the operation is normally available without
authorization but is currently inhibited. The operation is available only if inhibitors are ignored and
after authorization. If <literal>inhibitor-blocked</literal> is returned, the operation is normally
available without authorization but is currently inhibited. While the inhibitor remains active, the user
is not allowed to execute the operation. <literal>challenge-inhibitor-blocked</literal> is similar:
the operation is normally available after authorization but a held inhibitor disallows the user from
executing the operation.</para>
<para><function>ScheduleShutdown()</function> schedules a shutdown operation <varname>type</varname> at
time <varname>usec</varname> in microseconds since the UNIX epoch. Alternatively, if
+39 -42
View File
@@ -2927,7 +2927,6 @@ static int method_can_shutdown_or_sleep(
_cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
bool multiple_sessions, challenge, blocked, check_unit_state = true;
const HandleActionData *a;
const char *result = NULL;
uid_t uid;
int r;
@@ -2984,12 +2983,27 @@ static int method_can_shutdown_or_sleep(
if (r < 0)
return r;
if (!streq(load_state, "loaded")) {
result = "no";
goto finish;
}
if (!streq(load_state, "loaded"))
return sd_bus_reply_method_return(message, "s", "no");
}
const char *result;
r = bus_test_polkit(
message,
a->polkit_action,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
&challenge,
error);
if (r < 0)
return r;
if (r > 0)
result = "yes";
else if (challenge)
result = "challenge";
else
result = "no";
if (multiple_sessions) {
r = bus_test_polkit(
message,
@@ -3001,12 +3015,13 @@ static int method_can_shutdown_or_sleep(
if (r < 0)
return r;
if (r > 0)
result = "yes";
else if (challenge)
result = "challenge";
else
result = "no";
if (r == 0) {
if (challenge) {
if (streq(result, "yes")) /* Avoid upgrading no -> challenge */
result = "challenge";
} else
result = "no";
}
}
if (blocked) {
@@ -3020,39 +3035,21 @@ static int method_can_shutdown_or_sleep(
if (r < 0)
return r;
if (r > 0) {
if (!result)
result = "yes";
} else if (challenge) {
if (!result || streq(result, "yes"))
result = "challenge";
} else
result = "no";
if (r == 0) {
if (challenge) {
if (streq(result, "yes"))
result = "inhibited";
/* If result is already "challenge" or "no", the held inhibitor has no effect */
} else {
if (streq(result, "yes"))
result = "inhibitor-blocked";
else if (streq(result, "challenge"))
result = "challenge-inhibitor-blocked";
/* If the result is already "no", the held inhibitor has no effect */
}
}
}
if (!multiple_sessions && !blocked) {
/* If neither inhibit nor multiple sessions
* apply then just check the normal policy */
r = bus_test_polkit(
message,
a->polkit_action,
/* details= */ NULL,
/* good_user= */ UID_INVALID,
&challenge,
error);
if (r < 0)
return r;
if (r > 0)
result = "yes";
else if (challenge)
result = "challenge";
else
result = "no";
}
finish:
return sd_bus_reply_method_return(message, "s", result);
}