From 46075fc92b5e53168e7e5de79f5192fecb012e0a Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Mon, 19 Jan 2026 18:15:53 -0500 Subject: [PATCH 1/3] 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 --- man/org.freedesktop.login1.xml | 19 +++++--- src/login/logind-dbus.c | 81 ++++++++++++++++------------------ 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/man/org.freedesktop.login1.xml b/man/org.freedesktop.login1.xml index 464fdab108..bc95253859 100644 --- a/man/org.freedesktop.login1.xml +++ b/man/org.freedesktop.login1.xml @@ -711,12 +711,19 @@ node /org/freedesktop/login1 { CanRebootParameter(), CanRebootToFirmwareSetup(), CanRebootToBootLoaderMenu(), and CanRebootToBootLoaderEntry() test whether the system supports the respective operation and whether the calling user is allowed to - execute it. Returns one of na, yes, no, and - challenge. If na is returned, the operation is not available because - hardware, kernel, or drivers do not support it. If yes is returned, the operation is - supported and the user may execute the operation without further authentication. If no - is returned, the operation is available but the user is not allowed to execute the operation. If - challenge is returned, the operation is available but only after authorization. + execute it. Returns one of na, yes, no, + challenge, and inhibited. If na is returned, + the operation is not available because hardware, kernel, or drivers do not support it. If yes + is returned, the operation is supported and the user may execute the operation without further authentication. + If no is returned, the operation is available but the user is not allowed to execute + the operation. If challenge is returned, the operation is available but only after + authorization. If inhibited 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 inhibitor-blocked 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. challenge-inhibitor-blocked is similar: + the operation is normally available after authorization but a held inhibitor disallows the user from + executing the operation. ScheduleShutdown() schedules a shutdown operation type at time usec in microseconds since the UNIX epoch. Alternatively, if diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 87cc4f14bc..7f7d9e70d2 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -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); } From 482aa607a573c9430ee12390a0f3fc8f8290cc9b Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Mon, 19 Jan 2026 19:26:30 -0500 Subject: [PATCH 2/3] login1: Emit NCurrentInhibitors changed events Currently, there's no way for a desktop environment to detect when a systemd inhibitor has been added or removed. This means that a desktop environment that wishes to show current inhibitors to the user will be either forced to poll systemd or just use stale data. Now, the desktop can listen for changed events on NCurrentInhibitors and call GetInhibitors() in response to that. --- man/org.freedesktop.login1.xml | 1 - src/login/logind-dbus.c | 2 +- src/login/logind-inhibit.c | 8 +++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/man/org.freedesktop.login1.xml b/man/org.freedesktop.login1.xml index bc95253859..dc80e96717 100644 --- a/man/org.freedesktop.login1.xml +++ b/man/org.freedesktop.login1.xml @@ -276,7 +276,6 @@ node /org/freedesktop/login1 { readonly t RuntimeDirectoryInodesMax = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t InhibitorsMax = ...; - @org.freedesktop.DBus.Property.EmitsChangedSignal("false") readonly t NCurrentInhibitors = ...; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly t SessionsMax = ...; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 7f7d9e70d2..b50e69809f 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -3960,7 +3960,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("RuntimeDirectorySize", "t", NULL, offsetof(Manager, runtime_dir_size), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimeDirectoryInodesMax", "t", NULL, offsetof(Manager, runtime_dir_inodes), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("InhibitorsMax", "t", NULL, offsetof(Manager, inhibitors_max), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NCurrentInhibitors", "t", property_get_hashmap_size, offsetof(Manager, inhibitors), 0), + SD_BUS_PROPERTY("NCurrentInhibitors", "t", property_get_hashmap_size, offsetof(Manager, inhibitors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("SessionsMax", "t", NULL, offsetof(Manager, sessions_max), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NCurrentSessions", "t", property_get_hashmap_size, offsetof(Manager, sessions), 0), SD_BUS_PROPERTY("UserTasksMax", "t", property_get_compat_user_tasks_max, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index 2ef15588f2..b78d39d02d 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -148,13 +148,11 @@ static int inhibitor_save(Inhibitor *i) { } static int bus_manager_send_inhibited_change(Inhibitor *i) { - const char *property; - assert(i); - property = IN_SET(i->mode, INHIBIT_BLOCK, INHIBIT_BLOCK_WEAK) ? "BlockInhibited" : "DelayInhibited"; - - return manager_send_changed(i->manager, property); + return manager_send_changed(i->manager, + i->mode == INHIBIT_DELAY ? "DelayInhibited" : "BlockInhibited", + "NCurrentInhibitors"); } int inhibitor_start(Inhibitor *i) { From b965ce61aa4bd2bddcb8ffbf1f906cd59b79bdfa Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Mon, 23 Feb 2026 20:02:03 -0500 Subject: [PATCH 3/3] NEWS: Add note about logind's Can* return values We've changed them to return additional values, which may be a breaking change for desktop environments. In practice, it doesn't seem to be a major issue. However, let's make sure to document the changes in the NEWS anyway! --- NEWS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NEWS b/NEWS index a47f0f37a1..f0ddeb4432 100644 --- a/NEWS +++ b/NEWS @@ -64,6 +64,14 @@ CHANGES WITH 260 in spe: directory or not, we make use of it unconditionally and have dropped support for the old way using protofiles. + * The org.systemd.login1.Manager D-Bus interface has a minor API break. + The `CanPowerOff()`, `CanReboot()`, `CanSuspend()`, etc. family of + methods have introduced new return values which may break downstream + consumers, such as desktop environments. The new return values more + precisely communicate the status of inhibitors. This allows desktops + to differentiate between system administrator policy and + temporary restrictions imposed by inhibitors. + New system interfaces and components: * The os-release(3) gained a new field FANCY_NAME= that is similar