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:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user