#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"
}
}
+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",
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;
}
#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"
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;
"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,
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);
&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"),
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;
/* 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"
/* 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);
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]}."
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
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