]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: add ListInhibitors Varlink method 41561/head
authorYaping Li <202858510+YapingLi04@users.noreply.github.com>
Sun, 10 May 2026 14:50:13 +0000 (14:50 +0000)
committerYaping Li <202858510+YapingLi04@users.noreply.github.com>
Fri, 22 May 2026 02:56:24 +0000 (02:56 +0000)
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.

src/login/logind-inhibit.c
src/login/logind-inhibit.h
src/login/logind-varlink.c
src/shared/varlink-io.systemd.Login.c
src/shared/varlink-io.systemd.Login.h
src/test/test-varlink-idl-login.c
test/units/TEST-35-LOGIN.sh

index 30b43404b19e7acd138869e4747000de6579ee05..99ec0a3c99ed5f63ee3e3d8ff6cbe3cecf1016df 100644 (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",
index 1cb4ed460541e0e848353628de04615824967e37..c606582df2149e90174ae414f68eedadecaff11a 100644 (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;
 }
index 89dcbf87789936d6bb0d805137b2deba50535a2c..382f8fea221f3de73ae0e22883bda0e3a0e41e04 100644 (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,
index 5771c52c4c69b02aff4bb4388e7622f55fa9623f..7d7f5f2a4a8844a3a0053254fcbf614c80ab3637 100644 (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"),
index 52a2ddd97491fe314be6816064f0ddb825558fb7..2d751eebdf9ed0263236a9c0c451aeb131dc8a9c 100644 (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;
index 7dff80178f38e9db12541aa08ae89b4cd70d15b2..fb8ef813637489bd019d53c7b510c3548d740cc1 100644 (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);
index 491b754b295ea469d308bd2818d4d46e558a1459..e4f9621c8d9a42d574fea3c70bf14a9e7c902b96 100755 (executable)
@@ -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