logind: add ListInhibitors Varlink method

The Varlink ListInhibitors method is the counterpart of D-Bus
ListInhibitors. Like its D-Bus counterpart it is zero-filter and streams
the full list of currently registered inhibitors using the
SD_VARLINK_METHOD_MORE pattern, returning InhibitorInfo objects with
Id, What, Who, Why, Mode, UID, PID, and Since fields.

There is no D-Bus GetInhibitor getter to fold in, so no unique-key
filter is introduced here.
This commit is contained in:
Yaping Li
2026-05-22 02:56:24 +00:00
parent b4de3e5759
commit d2bdaab55e
7 changed files with 204 additions and 4 deletions
+62
View File
@@ -17,6 +17,7 @@
#include "fs-util.h"
#include "hashmap.h"
#include "io-util.h"
#include "json-util.h"
#include "log.h"
#include "logind-session.h"
#include "logind.h"
@@ -528,6 +529,67 @@ int inhibit_what_from_string(const char *s) {
}
}
static int inhibit_what_build_json(sd_json_variant **ret, const char *name, void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
InhibitWhat *what = ASSERT_PTR(userdata);
int r;
assert(ret);
assert(inhibit_what_is_valid(*what));
for (unsigned bit = 0; (1U << bit) < (unsigned) _INHIBIT_WHAT_MAX; bit++) {
InhibitWhat w = 1U << bit;
if (!FLAGS_SET(*what, w))
continue;
r = sd_json_variant_append_arrayb(&v, JSON_BUILD_STRING_UNDERSCORIFY(inhibit_what_to_string(w)));
if (r < 0)
return r;
}
*ret = TAKE_PTR(v);
return 0;
}
static int inhibitor_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Inhibitor *i = ASSERT_PTR(userdata);
assert(ret);
assert(i->mode >= 0 && i->mode < _INHIBIT_MODE_MAX);
assert(inhibit_what_is_valid(i->what));
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("Id", i->id),
SD_JSON_BUILD_PAIR_CALLBACK("What", inhibit_what_build_json, &i->what),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Who", i->who),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Why", i->why),
JSON_BUILD_PAIR_ENUM("Mode", inhibit_mode_to_string(i->mode)),
SD_JSON_BUILD_PAIR_UNSIGNED("UID", i->uid));
}
static int inhibitor_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Inhibitor *i = ASSERT_PTR(userdata);
assert(ret);
return sd_json_buildo(
ret,
JSON_BUILD_PAIR_PIDREF_NON_NULL("PID", &i->pid),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Since", &i->since));
}
int inhibitor_build_json(Inhibitor *i, sd_json_variant **ret) {
assert(i);
assert(ret);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_CALLBACK("context", inhibitor_context_build_json, i),
SD_JSON_BUILD_PAIR_CALLBACK("runtime", inhibitor_runtime_build_json, i));
}
static const char* const inhibit_mode_table[_INHIBIT_MODE_MAX] = {
[INHIBIT_BLOCK] = "block",
[INHIBIT_BLOCK_WEAK] = "block-weak",
+2
View File
@@ -80,6 +80,8 @@ bool manager_is_inhibited(
uid_t uid_to_ignore,
Inhibitor **ret_offending);
int inhibitor_build_json(Inhibitor *i, sd_json_variant **ret);
static inline bool inhibit_what_is_valid(InhibitWhat w) {
return w > 0 && w < _INHIBIT_WHAT_MAX;
}
+36
View File
@@ -14,6 +14,7 @@
#include "login-util.h"
#include "logind.h"
#include "logind-dbus.h"
#include "logind-inhibit.h"
#include "logind-seat.h"
#include "logind-session.h"
#include "logind-shutdown.h"
@@ -690,6 +691,40 @@ static int vl_method_list_seats(sd_varlink *link, sd_json_variant *parameters, s
return 0;
}
static int vl_method_list_inhibitors(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
/* IDL uses SD_VARLINK_REQUIRES_MORE, so the framework rejects non-more calls before this handler. */
r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
if (r != 0)
return r;
/* Required for multi-reply streaming: sd_varlink_reply() only stashes the previous reply (for
* the 'continues' flag machinery) when v->sentinel is set. Pass NULL to send an empty reply
* (not an error) when the inhibitor hashmap is empty — this method has no point-lookup, so an
* empty list is a valid result, not a "not found" failure. */
r = sd_varlink_set_sentinel(link, /* error_id= */ NULL);
if (r < 0)
return r;
Inhibitor *inhibitor;
HASHMAP_FOREACH(inhibitor, m->inhibitors) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
r = inhibitor_build_json(inhibitor, &v);
if (r < 0)
return r;
r = sd_varlink_reply(link, v);
if (r < 0)
return r;
}
return 0;
}
static int vl_method_release_session(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
@@ -860,6 +895,7 @@ int manager_varlink_init(Manager *m, int fd) {
"io.systemd.Login.ListSessions", vl_method_list_sessions,
"io.systemd.Login.ListUsers", vl_method_list_users,
"io.systemd.Login.ListSeats", vl_method_list_seats,
"io.systemd.Login.ListInhibitors", vl_method_list_inhibitors,
"io.systemd.Shutdown.PowerOff", vl_method_power_off,
"io.systemd.Shutdown.Reboot", vl_method_reboot,
"io.systemd.Shutdown.Halt", vl_method_halt,
+66 -3
View File
@@ -230,9 +230,60 @@ static SD_VARLINK_DEFINE_METHOD_FULL(
SD_VARLINK_FIELD_COMMENT("Runtime information of the seat"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, SeatRuntime, 0));
SD_VARLINK_DEFINE_ENUM_TYPE(
InhibitMode,
SD_VARLINK_FIELD_COMMENT("Hard inhibition; blocks the operation entirely"),
SD_VARLINK_DEFINE_ENUM_VALUE(block),
SD_VARLINK_FIELD_COMMENT("Weak hard inhibition; like block, but the user who set it can override it without polkit authorization"),
SD_VARLINK_DEFINE_ENUM_VALUE(block_weak),
SD_VARLINK_FIELD_COMMENT("Soft inhibition; delays the operation but ultimately permits it"),
SD_VARLINK_DEFINE_ENUM_VALUE(delay));
SD_VARLINK_DEFINE_ENUM_TYPE(
InhibitWhat,
SD_VARLINK_DEFINE_ENUM_VALUE(shutdown),
SD_VARLINK_DEFINE_ENUM_VALUE(sleep),
SD_VARLINK_DEFINE_ENUM_VALUE(idle),
SD_VARLINK_DEFINE_ENUM_VALUE(handle_power_key),
SD_VARLINK_DEFINE_ENUM_VALUE(handle_suspend_key),
SD_VARLINK_DEFINE_ENUM_VALUE(handle_hibernate_key),
SD_VARLINK_DEFINE_ENUM_VALUE(handle_lid_switch),
SD_VARLINK_DEFINE_ENUM_VALUE(handle_reboot_key));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
InhibitorContext,
SD_VARLINK_FIELD_COMMENT("The inhibitor identifier"),
SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("What is being inhibited"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(What, InhibitWhat, SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("A human-readable descriptive string of who is taking the inhibition"),
SD_VARLINK_DEFINE_FIELD(Who, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("A human-readable descriptive string of why the inhibition is taken"),
SD_VARLINK_DEFINE_FIELD(Why, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The inhibition mode"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Mode, InhibitMode, 0),
SD_VARLINK_FIELD_COMMENT("The UID of the user taking the inhibition"),
SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
InhibitorRuntime,
SD_VARLINK_FIELD_COMMENT("The PID of the process taking the inhibition"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The inhibitor timestamps"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Since, Timestamp, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
ListInhibitors,
SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_FIELD_COMMENT("Configuration of the inhibitor"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, InhibitorContext, 0),
SD_VARLINK_FIELD_COMMENT("Runtime information of the inhibitor"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, InhibitorRuntime, 0));
static SD_VARLINK_DEFINE_ERROR(NoSuchSession);
static SD_VARLINK_DEFINE_ERROR(NoSuchUser);
static SD_VARLINK_DEFINE_ERROR(NoSuchSeat);
static SD_VARLINK_DEFINE_ERROR(NoSuchInhibitor);
static SD_VARLINK_DEFINE_ERROR(AlreadySessionMember);
static SD_VARLINK_DEFINE_ERROR(VirtualTerminalAlreadyTaken);
static SD_VARLINK_DEFINE_ERROR(TooManySessions);
@@ -259,26 +310,38 @@ SD_VARLINK_DEFINE_INTERFACE(
&vl_method_CreateSession,
SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refused unless originating from the session to release itself."),
&vl_method_ReleaseSession,
SD_VARLINK_SYMBOL_COMMENT("Lists current sessions. If an ID or PID filter is provided, returns the single matching session; otherwise streams all current sessions (requires the 'more' flag)."),
SD_VARLINK_SYMBOL_COMMENT("Lists current sessions. If an Id or PID filter is provided, returns the single matching session; otherwise streams all current sessions (requires the 'more' flag)."),
&vl_method_ListSessions,
SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a user"),
&vl_type_UserContext,
SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a user"),
&vl_type_UserRuntime,
SD_VARLINK_SYMBOL_COMMENT("Lists current users. If a UID or PID filter is provided, returns the single matching user; otherwise streams all current users (requires the 'more' flag). If called with no parameters and no 'more' flag, resolves to the caller's user."),
SD_VARLINK_SYMBOL_COMMENT("Lists current users. If a UID or PID filter is provided, returns the single matching user; otherwise streams all current users (requires the 'more' flag)."),
&vl_method_ListUsers,
SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a seat"),
&vl_type_SeatContext,
SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a seat"),
&vl_type_SeatRuntime,
SD_VARLINK_SYMBOL_COMMENT("Lists current seats. If an Id filter is provided (or the caller has a session), returns the single matching seat; otherwise streams all current seats (requires the 'more' flag). If called with no parameters and no 'more' flag, resolves to the caller's seat."),
SD_VARLINK_SYMBOL_COMMENT("Lists current seats. If an Id filter is provided, returns the single matching seat; otherwise streams all current seats (requires the 'more' flag)."),
&vl_method_ListSeats,
SD_VARLINK_SYMBOL_COMMENT("Inhibition mode"),
&vl_type_InhibitMode,
SD_VARLINK_SYMBOL_COMMENT("Operations that can be inhibited"),
&vl_type_InhibitWhat,
SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of an inhibitor"),
&vl_type_InhibitorContext,
SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of an inhibitor"),
&vl_type_InhibitorRuntime,
SD_VARLINK_SYMBOL_COMMENT("Lists all current inhibitors."),
&vl_method_ListInhibitors,
SD_VARLINK_SYMBOL_COMMENT("No session by this name found"),
&vl_error_NoSuchSession,
SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"),
&vl_error_NoSuchSeat,
SD_VARLINK_SYMBOL_COMMENT("No user by this UID found"),
&vl_error_NoSuchUser,
SD_VARLINK_SYMBOL_COMMENT("No inhibitor found"),
&vl_error_NoSuchInhibitor,
SD_VARLINK_SYMBOL_COMMENT("Process already member of a session"),
&vl_error_AlreadySessionMember,
SD_VARLINK_SYMBOL_COMMENT("The specified virtual terminal (VT) is already taken by another session"),
+2
View File
@@ -5,5 +5,7 @@
extern const sd_varlink_symbol vl_type_SessionType;
extern const sd_varlink_symbol vl_type_SessionClass;
extern const sd_varlink_symbol vl_type_InhibitMode;
extern const sd_varlink_symbol vl_type_InhibitWhat;
extern const sd_varlink_interface vl_interface_io_systemd_Login;
+6
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "logind-inhibit.h"
#include "logind-session.h"
#include "tests.h"
#include "test-varlink-idl-util.h"
@@ -10,6 +11,11 @@ TEST(login_enums_idl) {
/* SessionClass has SESSION_NONE ("none") as an internal sentinel that is intentionally not exposed
* via Varlink. Only validate the IDL→C direction. */
TEST_IDL_ENUM_FROM_STRING(SessionClass, session_class, vl_type_SessionClass);
TEST_IDL_ENUM(InhibitMode, inhibit_mode, vl_type_InhibitMode);
/* InhibitWhat is a bit-flag enum: inhibit_what_to_string() accepts a bitmask and
* the index does not correspond to a sequential integer, so the TEST_IDL_ENUM
* round-trip macro doesn't apply. The flag→string mapping is exercised through
* ListInhibitors integration tests in TEST-35-LOGIN.sh. */
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+30 -1
View File
@@ -800,13 +800,14 @@ EOF
teardown_varlink() (
set +ex
systemctl stop test-varlink-inhibit.service 2>/dev/null
cleanup_session
return 0
)
testcase_varlink() {
local session uid session_out user_out seat_out self_err
local session uid session_out user_out seat_out self_err inhibitor_out
if [[ ! -c /dev/tty2 ]]; then
echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
@@ -822,6 +823,7 @@ testcase_varlink() {
varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListSessions" >/dev/null
varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListUsers" >/dev/null
varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListSeats" >/dev/null
varlinkctl introspect "$VARLINK_SOCKET" | grep "method ListInhibitors" >/dev/null
: "--- Setup test session ---"
create_session
@@ -956,6 +958,33 @@ testcase_varlink() {
varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' | grep "seat0" >/dev/null
test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' | wc -l)" -ge 1
: "--- ListInhibitors ---"
systemd-run --unit=test-varlink-inhibit.service --service-type=exec \
systemd-inhibit --what=shutdown --who="varlink-test" --why="testing varlink" --mode=block \
sleep infinity
timeout 10 bash -c "until varlinkctl call --more '$VARLINK_SOCKET' io.systemd.Login.ListInhibitors '{}' 2>/dev/null | grep varlink-test >/dev/null; do sleep 0.5; done"
inhibitor_out=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}')
# What is now an array of strings; pick our test record and validate fields with jq.
echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.What | type == "array" and contains(["shutdown"])' >/dev/null
echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.Mode == "block"' >/dev/null
echo "$inhibitor_out" | jq --seq -e 'select(.context.Who == "varlink-test") | .context.Why == "testing varlink"' >/dev/null
# without --more should fail (ListInhibitors has no filter, --more required)
(! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}')
systemctl stop test-varlink-inhibit.service
# Best-effort empty-list check: stopping the test inhibitor doesn't guarantee
# zero entries (other system inhibitors may be registered), but the contract
# we verify holds regardless — ListInhibitors has no single-lookup path, so
# it must never emit a NoSuchInhibitor sentinel. An empty result is just an
# empty parameters reply as the stream terminator; the IDL-validation skip
# in the sentinel handler ensures this passes validation.
local empty_inhibitor_err
empty_inhibitor_err=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListInhibitors '{}' 2>&1 || true)
(! echo "$empty_inhibitor_err" | grep NoSuchInhibitor >/dev/null)
: "--- ReleaseSession: NULL ID resolves to caller's session ---"
# A caller with a logind session calling ReleaseSession '{}' (no ID) must
# release its own session. We spawn the call inside a transient unit with