logind: migrate to Varlink (phase1) (#41561)

This implements phase 1 of #41560.

Add List Varlink methods for all logind object types (sessions, users,
seats, inhibitors).
This commit is contained in:
Yu Watanabe
2026-05-26 03:15:55 +09:00
committed by GitHub
24 changed files with 1236 additions and 82 deletions
+19 -5
View File
@@ -73,6 +73,7 @@ static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState);
static void varlink_server_test_exit_on_idle(sd_varlink_server *s);
static int varlink_reply_terminator(sd_varlink *v);
static void varlink_set_state(sd_varlink *v, VarlinkState state) {
assert(v);
@@ -989,7 +990,9 @@ static int varlink_dispatch_sentinel(sd_varlink *v) {
/* Propagate the sentinel to the client if one was configured and no replies were enqueued by
* the callback. */
if (sentinel == POINTER_MAX)
r = sd_varlink_reply(v, NULL);
/* Synthetic empty terminator. Skip IDL validation since the empty parameters wouldn't
* satisfy any mandatory output fields the method declares. */
r = varlink_reply_terminator(v);
else {
r = sd_varlink_error(v, sentinel, NULL);
/* sd_varlink_error() deliberately returns a negative
@@ -2298,10 +2301,10 @@ _public_ int sd_varlink_collectb(
return sd_varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
}
_public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
static int varlink_reply_internal(sd_varlink *v, sd_json_variant *parameters, bool skip_validation) {
int r;
assert_return(v, -EINVAL);
assert(v);
if (v->state == VARLINK_DISCONNECTED)
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
@@ -2313,8 +2316,9 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
bool more = IN_SET(v->state, VARLINK_PROCESSING_METHOD_MORE, VARLINK_PENDING_METHOD_MORE);
/* Validate parameters BEFORE sanitization */
if (v->current_method) {
/* Validate parameters BEFORE sanitization. Validation should be skipped for the synthetic
* empty terminator. */
if (!skip_validation && v->current_method) {
const char *bad_field = NULL;
r = varlink_idl_validate_method_reply(v->current_method, parameters, more && v->sentinel ? SD_VARLINK_REPLY_CONTINUES : 0, &bad_field);
@@ -2373,6 +2377,16 @@ _public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
return 1;
}
static int varlink_reply_terminator(sd_varlink *v) {
return varlink_reply_internal(v, /* parameters= */ NULL, /* skip_validation= */ true);
}
_public_ int sd_varlink_reply(sd_varlink *v, sd_json_variant *parameters) {
assert_return(v, -EINVAL);
return varlink_reply_internal(v, parameters, /* skip_validation= */ false);
}
_public_ int sd_varlink_replyb(sd_varlink *v, ...) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL;
va_list ap;
+5 -8
View File
@@ -447,7 +447,7 @@ int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret) {
return !!u;
}
int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
bool manager_get_idle_hint(Manager *m, dual_timestamp *ret_timestamp) {
Session *s;
bool idle_hint;
dual_timestamp ts;
@@ -458,19 +458,16 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
* unreasonable large idle periods starting with the Unix epoch. */
ts = m->init_ts;
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, t, /* flags= */ 0, UID_INVALID, NULL);
idle_hint = !manager_is_inhibited(m, INHIBIT_IDLE, /* since= */ NULL, /* flags= */ 0, UID_INVALID, NULL);
HASHMAP_FOREACH(s, m->sessions) {
dual_timestamp k;
int ih;
bool ih;
if (!SESSION_CLASS_CAN_IDLE(s->class))
continue;
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic < ts.monotonic)
@@ -486,8 +483,8 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) {
}
}
if (t)
*t = ts;
if (ret_timestamp)
*ret_timestamp = ts;
return idle_hint;
}
+3 -6
View File
@@ -282,7 +282,7 @@ static int property_get_idle_hint(
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "b", manager_get_idle_hint(m, NULL) > 0);
return sd_bus_message_append(reply, "b", manager_get_idle_hint(m, /* ret_timestamp= */ NULL));
}
static int property_get_idle_since_hint(
@@ -295,7 +295,7 @@ static int property_get_idle_since_hint(
sd_bus_error *error) {
Manager *m = ASSERT_PTR(userdata);
dual_timestamp t = DUAL_TIMESTAMP_NULL;
dual_timestamp t;
assert(bus);
assert(reply);
@@ -701,10 +701,7 @@ static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_b
if (!path)
return -ENOMEM;
r = session_get_idle_hint(s, &idle_ts);
if (r < 0)
return r;
idle = r > 0;
idle = session_get_idle_hint(s, &idle_ts);
r = sd_bus_message_append(reply, "(sussussbto)",
s->id,
+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;
}
+2 -5
View File
@@ -100,7 +100,7 @@ static int property_get_idle_hint(
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "b", seat_get_idle_hint(s, NULL) > 0);
return sd_bus_message_append(reply, "b", seat_get_idle_hint(s, /* ret_timestamp= */ NULL));
}
static int property_get_idle_since_hint(
@@ -115,14 +115,11 @@ static int property_get_idle_since_hint(
Seat *s = ASSERT_PTR(userdata);
dual_timestamp t;
uint64_t u;
int r;
assert(bus);
assert(reply);
r = seat_get_idle_hint(s, &t);
if (r < 0)
return r;
seat_get_idle_hint(s, &t);
u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+60 -8
View File
@@ -16,6 +16,7 @@
#include "fs-util.h"
#include "hashmap.h"
#include "id128-util.h"
#include "json-util.h"
#include "log.h"
#include "logind.h"
#include "logind-device.h"
@@ -773,7 +774,7 @@ bool seat_can_graphical(Seat *s) {
return seat_has_master_device(s);
}
int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
bool seat_get_idle_hint(Seat *s, dual_timestamp *ret_timestamp) {
bool idle_hint = true;
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
@@ -781,12 +782,9 @@ int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
LIST_FOREACH(sessions_by_seat, session, s->sessions) {
dual_timestamp k;
int ih;
bool ih;
ih = session_get_idle_hint(session, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic > ts.monotonic)
@@ -796,14 +794,13 @@ int seat_get_idle_hint(Seat *s, dual_timestamp *t) {
ts = k;
}
} else if (idle_hint) {
if (k.monotonic > ts.monotonic)
ts = k;
}
}
if (t)
*t = ts;
if (ret_timestamp)
*ret_timestamp = ts;
return idle_hint;
}
@@ -830,6 +827,61 @@ void seat_add_to_gc_queue(Seat *s) {
s->in_gc_queue = true;
}
static int seat_sessions_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Seat *s = ASSERT_PTR(userdata);
int r;
assert(ret);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
LIST_FOREACH(sessions_by_seat, session, s->sessions) {
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(session->id));
if (r < 0)
return r;
}
*ret = TAKE_PTR(v);
return 0;
}
static int seat_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Seat *s = ASSERT_PTR(userdata);
assert(ret);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("Id", s->id));
}
static int seat_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Seat *s = ASSERT_PTR(userdata);
assert(ret);
dual_timestamp idle_ts;
bool idle = seat_get_idle_hint(s, &idle_ts);
return sd_json_buildo(
ret,
JSON_BUILD_PAIR_STRING_NON_EMPTY("ActiveSession", s->active ? s->active->id : NULL),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Sessions", seat_sessions_build_json, s),
SD_JSON_BUILD_PAIR_BOOLEAN("CanTTY", seat_can_tty(s)),
SD_JSON_BUILD_PAIR_BOOLEAN("CanGraphical", seat_can_graphical(s)),
SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle),
SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts)));
}
int seat_build_json(Seat *s, sd_json_variant **ret) {
assert(s);
assert(ret);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_CALLBACK("context", seat_context_build_json, s),
SD_JSON_BUILD_PAIR_CALLBACK("runtime", seat_runtime_build_json, s));
}
static bool seat_name_valid_char(char c) {
return
ascii_isalpha(c) ||
+3 -1
View File
@@ -55,7 +55,7 @@ bool seat_can_tty(Seat *s);
bool seat_has_master_device(Seat *s);
bool seat_can_graphical(Seat *s);
int seat_get_idle_hint(Seat *s, dual_timestamp *t);
bool seat_get_idle_hint(Seat *s, dual_timestamp *ret_timestamp);
int seat_start(Seat *s);
int seat_stop(Seat *s, bool force);
@@ -64,6 +64,8 @@ int seat_stop_sessions(Seat *s, bool force);
bool seat_may_gc(Seat *s, bool drop_not_started);
void seat_add_to_gc_queue(Seat *s);
int seat_build_json(Seat *s, sd_json_variant **ret);
bool seat_name_is_valid(const char *name);
bool seat_is_self(const char *name);
bool seat_is_auto(const char *name);
+3 -6
View File
@@ -117,7 +117,7 @@ static int property_get_idle_hint(
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "b", session_get_idle_hint(s, NULL) > 0);
return sd_bus_message_append(reply, "b", session_get_idle_hint(s, /* ret_timestamp= */ NULL));
}
static int property_get_can_idle(
@@ -164,16 +164,13 @@ static int property_get_idle_since_hint(
sd_bus_error *error) {
Session *s = ASSERT_PTR(userdata);
dual_timestamp t = DUAL_TIMESTAMP_NULL;
dual_timestamp t;
uint64_t u;
int r;
assert(bus);
assert(reply);
r = session_get_idle_hint(s, &t);
if (r < 0)
return r;
session_get_idle_hint(s, &t);
u = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
+72 -8
View File
@@ -28,6 +28,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "hashmap.h"
#include "json-util.h"
#include "login-util.h"
#include "logind.h"
#include "logind-dbus.h"
@@ -1153,19 +1154,23 @@ static int get_process_ctty_atime(pid_t pid, usec_t *atime) {
return get_tty_atime(p, atime);
}
int session_get_idle_hint(Session *s, dual_timestamp *t) {
bool session_get_idle_hint(Session *s, dual_timestamp *ret_timestamp) {
usec_t atime = 0, dtime = 0;
int r;
assert(s);
if (!SESSION_CLASS_CAN_IDLE(s->class))
if (!SESSION_CLASS_CAN_IDLE(s->class)) {
if (ret_timestamp)
*ret_timestamp = DUAL_TIMESTAMP_NULL;
return false;
}
/* Graphical sessions have an explicit idle hint */
if (SESSION_TYPE_IS_GRAPHICAL(s->type)) {
if (t)
*t = s->idle_hint_timestamp;
if (ret_timestamp)
*ret_timestamp = s->idle_hint_timestamp;
return s->idle_hint;
}
@@ -1187,14 +1192,14 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) {
}
}
if (t)
*t = DUAL_TIMESTAMP_NULL;
if (ret_timestamp)
*ret_timestamp = DUAL_TIMESTAMP_NULL;
return false;
found_atime:
if (t)
dual_timestamp_from_realtime(t, atime);
if (ret_timestamp)
dual_timestamp_from_realtime(ret_timestamp, atime);
if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY)
dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec);
@@ -1680,6 +1685,65 @@ bool session_is_auto(const char *name) {
return streq_ptr(name, "auto");
}
static int session_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Session *s = ASSERT_PTR(userdata);
assert(ret);
assert(s->user);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid),
JSON_BUILD_PAIR_PIDREF_NON_NULL("PID", &s->leader),
JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type)),
JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", s->service),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", s->desktop),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", s->seat ? s->seat->id : NULL),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", s->vtnr),
JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", s->tty),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", s->display),
SD_JSON_BUILD_PAIR_BOOLEAN("Remote", s->remote),
JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteHost", s->remote_host),
JSON_BUILD_PAIR_STRING_NON_EMPTY("RemoteUser", s->remote_user),
JSON_BUILD_PAIR_STRV_NON_EMPTY("ExtraDeviceAccess", s->extra_device_access));
}
static int session_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
Session *s = ASSERT_PTR(userdata);
assert(ret);
dual_timestamp idle_ts;
bool idle = session_get_idle_hint(s, &idle_ts);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("Id", s->id),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Scope", s->scope),
SD_JSON_BUILD_PAIR_CONDITION(audit_session_is_valid(s->audit_id),
"Audit", SD_JSON_BUILD_UNSIGNED(s->audit_id)),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Timestamp", &s->timestamp),
JSON_BUILD_PAIR_ENUM("State", session_state_to_string(session_get_state(s))),
SD_JSON_BUILD_PAIR_BOOLEAN("Active", session_is_active(s)),
SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle),
SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts)),
SD_JSON_BUILD_PAIR_BOOLEAN("LockedHint", s->locked_hint),
SD_JSON_BUILD_PAIR_BOOLEAN("CanIdle", SESSION_CLASS_CAN_IDLE(s->class)),
SD_JSON_BUILD_PAIR_BOOLEAN("CanLock", SESSION_CLASS_CAN_LOCK(s->class)));
}
int session_build_json(Session *s, sd_json_variant **ret) {
assert(s);
assert(s->user);
assert(ret);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_CALLBACK("context", session_context_build_json, s),
SD_JSON_BUILD_PAIR_CALLBACK("runtime", session_runtime_build_json, s));
}
static const char* const session_state_table[_SESSION_STATE_MAX] = {
[SESSION_OPENING] = "opening",
[SESSION_ONLINE] = "online",
+3 -1
View File
@@ -179,7 +179,7 @@ bool session_may_gc(Session *s, bool drop_not_started);
void session_add_to_gc_queue(Session *s);
int session_activate(Session *s);
bool session_is_active(Session *s);
int session_get_idle_hint(Session *s, dual_timestamp *t);
bool session_get_idle_hint(Session *s, dual_timestamp *ret_timestamp);
int session_set_idle_hint(Session *s, bool b);
int session_get_locked_hint(Session *s);
int session_set_locked_hint(Session *s, bool b);
@@ -219,3 +219,5 @@ int session_send_create_reply(Session *s, const sd_bus_error *error);
bool session_is_self(const char *name);
bool session_is_auto(const char *name);
int session_build_json(Session *s, sd_json_variant **ret);
+2 -2
View File
@@ -144,7 +144,7 @@ static int property_get_idle_hint(
assert(bus);
assert(reply);
return sd_bus_message_append(reply, "b", user_get_idle_hint(u, NULL) > 0);
return sd_bus_message_append(reply, "b", user_get_idle_hint(u, /* ret_timestamp= */ NULL));
}
static int property_get_idle_since_hint(
@@ -163,7 +163,7 @@ static int property_get_idle_since_hint(
assert(bus);
assert(reply);
(void) user_get_idle_hint(u, &t);
user_get_idle_hint(u, &t);
k = streq(property, "IdleSinceHint") ? t.realtime : t.monotonic;
return sd_bus_message_append(reply, "t", k);
+76 -8
View File
@@ -18,6 +18,7 @@
#include "format-util.h"
#include "fs-util.h"
#include "hashmap.h"
#include "json-util.h"
#include "limits-util.h"
#include "logind-session.h"
#include "logind.h"
@@ -679,7 +680,7 @@ int user_finalize(User *u) {
return r;
}
int user_get_idle_hint(User *u, dual_timestamp *t) {
bool user_get_idle_hint(User *u, dual_timestamp *ret_timestamp) {
bool idle_hint = true;
dual_timestamp ts = DUAL_TIMESTAMP_NULL;
@@ -687,15 +688,12 @@ int user_get_idle_hint(User *u, dual_timestamp *t) {
LIST_FOREACH(sessions_by_user, s, u->sessions) {
dual_timestamp k;
int ih;
bool ih;
if (!SESSION_CLASS_CAN_IDLE(s->class))
continue;
ih = session_get_idle_hint(s, &k);
if (ih < 0)
return ih;
if (!ih) {
if (!idle_hint) {
if (k.monotonic < ts.monotonic)
@@ -705,14 +703,13 @@ int user_get_idle_hint(User *u, dual_timestamp *t) {
ts = k;
}
} else if (idle_hint) {
if (k.monotonic > ts.monotonic)
ts = k;
}
}
if (t)
*t = ts;
if (ret_timestamp)
*ret_timestamp = ts;
return idle_hint;
}
@@ -1023,6 +1020,77 @@ void user_update_last_session_timer(User *u) {
FORMAT_TIMESPAN(user_stop_delay, USEC_PER_MSEC));
}
static int user_sessions_build_json(sd_json_variant **ret, const char *name, void *userdata) {
User *u = ASSERT_PTR(userdata);
int r;
assert(ret);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
LIST_FOREACH(sessions_by_user, session, u->sessions) {
r = sd_json_variant_append_arrayb(&v, SD_JSON_BUILD_STRING(session->id));
if (r < 0)
return r;
}
*ret = TAKE_PTR(v);
return 0;
}
static int user_context_build_json(sd_json_variant **ret, const char *name, void *userdata) {
User *u = ASSERT_PTR(userdata);
assert(ret);
assert(u->user_record);
int linger = user_check_linger_file(u);
if (linger == -ENOMEM)
return linger;
if (linger < 0)
log_warning_errno(linger,
"Failed to check linger file for user '%s', assuming disabled: %m",
u->user_record->user_name);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_UNSIGNED("UID", u->user_record->uid),
SD_JSON_BUILD_PAIR_UNSIGNED("GID", u->user_record->gid),
SD_JSON_BUILD_PAIR_STRING("Name", u->user_record->user_name),
SD_JSON_BUILD_PAIR_BOOLEAN("Linger", linger > 0),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Service", u->service_manager_unit),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Slice", u->slice),
JSON_BUILD_PAIR_STRING_NON_EMPTY("RuntimePath", u->runtime_path));
}
static int user_runtime_build_json(sd_json_variant **ret, const char *name, void *userdata) {
User *u = ASSERT_PTR(userdata);
assert(ret);
dual_timestamp idle_ts;
bool idle = user_get_idle_hint(u, &idle_ts);
return sd_json_buildo(
ret,
JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", u->display ? u->display->id : NULL),
JSON_BUILD_PAIR_ENUM("State", user_state_to_string(user_get_state(u))),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("Sessions", user_sessions_build_json, u),
JSON_BUILD_PAIR_DUAL_TIMESTAMP_NON_NULL("Timestamp", &u->timestamp),
SD_JSON_BUILD_PAIR_BOOLEAN("IdleHint", idle),
SD_JSON_BUILD_PAIR_CONDITION(idle, "IdleSinceHint", JSON_BUILD_DUAL_TIMESTAMP(&idle_ts)));
}
int user_build_json(User *u, sd_json_variant **ret) {
assert(u);
assert(u->user_record);
assert(ret);
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_CALLBACK("context", user_context_build_json, u),
SD_JSON_BUILD_PAIR_CALLBACK("runtime", user_runtime_build_json, u));
}
static const char* const user_state_table[_USER_STATE_MAX] = {
[USER_OFFLINE] = "offline",
[USER_OPENING] = "opening",
+3 -1
View File
@@ -82,7 +82,7 @@ void user_add_to_gc_queue(User *u);
int user_stop(User *u, bool force);
int user_finalize(User *u);
UserState user_get_state(User *u);
int user_get_idle_hint(User *u, dual_timestamp *t);
bool user_get_idle_hint(User *u, dual_timestamp *ret_timestamp);
int user_save(User *u);
int user_load(User *u);
int user_kill(User *u, int signo);
@@ -90,6 +90,8 @@ int user_check_linger_file(const User *u);
void user_elect_display(User *u);
void user_update_last_session_timer(User *u);
int user_build_json(User *u, sd_json_variant **ret);
DECLARE_STRING_TABLE_LOOKUP(user_state, UserState);
DECLARE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode);
+443 -14
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"
@@ -41,7 +42,8 @@ static int manager_varlink_get_session_by_peer(
assert(ret);
/* Determines the session of the peer. If the peer is not part of a session, but consult_display is
* true, then will return the display session of the peer's owning user */
* true, then will return the display session of the peer's owning user. Returns 0 with *ret set to
* NULL if no session could be determined; the caller decides which error to report to the client. */
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
r = varlink_get_peer_pidref(link, &pidref);
@@ -70,35 +72,114 @@ static int manager_varlink_get_session_by_peer(
} else
session = hashmap_get(m->sessions, name);
*ret = session;
return 0;
}
static int lookup_session_by_name(Manager *m, sd_varlink *link, const char *name, Session **ret) {
int r;
assert(m);
assert(link);
assert(name);
assert(ret);
if (session_is_self(name) || session_is_auto(name)) {
Session *peer;
r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ session_is_auto(name), &peer);
if (r < 0)
return r;
if (!peer)
return -ESRCH;
*ret = peer;
return 0;
}
if (!session_id_valid(name))
return -EINVAL;
Session *session = hashmap_get(m->sessions, name);
if (!session)
return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL);
return -ESRCH;
*ret = session;
return 0;
}
static int manager_varlink_get_session_by_name(
static int lookup_session_by_pidref(Manager *m, const PidRef *pidref, Session **ret) {
int r;
assert(m);
assert(pidref);
assert(ret);
Session *session;
r = manager_get_session_by_pidref(m, pidref, &session);
if (r < 0)
return log_debug_errno(r, "Failed to look up session by PID " PID_FMT ": %m", pidref->pid);
if (!session)
return -ESRCH;
*ret = session;
return 0;
}
static int manager_varlink_get_session_by_name_or_pidref(
Manager *m,
sd_varlink *link,
const char *name,
const PidRef *pidref,
Session **ret) {
Session *by_name = NULL, *by_pid = NULL;
int r;
assert(m);
assert(link);
assert(ret);
/* Resolves a session name to a session object. Supports resolving the special names "self" and "auto". */
/* Resolves a session by name and/or PID. Supports the special names "self" and "auto" for the name
* argument. If both name and pidref are unset, resolves to the caller's session. If both name and
* pidref are set they must refer to the same session, otherwise -ESRCH is returned. Returns -ESRCH
* on "not found". Caller is expected to turn that into a varlink error, typically via
* sd_varlink_set_sentinel(). Returns negative errno on other failures. */
if (session_is_self(name))
return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, ret);
if (session_is_auto(name))
return manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, ret);
if (name) {
r = lookup_session_by_name(m, link, name, &by_name);
if (r == -EINVAL)
return sd_varlink_error_invalid_parameter_name(link, "Id");
if (r < 0)
return r;
}
Session *session = hashmap_get(m->sessions, name);
if (!session)
return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL);
if (pidref && pidref_is_set(pidref)) {
r = lookup_session_by_pidref(m, pidref, &by_pid);
if (r == -EINVAL)
return sd_varlink_error_invalid_parameter_name(link, "PID");
if (r < 0)
return r;
}
*ret = session;
if (by_name && by_pid && by_name != by_pid)
return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
"Search by session id '%s' and PID " PID_FMT " resulted in two different sessions",
name, pidref->pid);
if (by_name || by_pid) {
*ret = by_name ?: by_pid;
return 0;
}
/* Neither filter set — fall back to caller's session. */
Session *by_peer;
r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ true, &by_peer);
if (r < 0)
return r;
if (!by_peer)
return -ESRCH;
*ret = by_peer;
return 0;
}
@@ -129,7 +210,7 @@ int session_send_create_reply_varlink(Session *s, const sd_bus_error *error) {
SD_JSON_BUILD_PAIR_STRING("RuntimePath", s->user->runtime_path),
SD_JSON_BUILD_PAIR_UNSIGNED("UID", s->user->user_record->uid),
SD_JSON_BUILD_PAIR_CONDITION(!!s->seat, "Seat", SD_JSON_BUILD_STRING(s->seat ? s->seat->id : NULL)),
SD_JSON_BUILD_PAIR_CONDITION(s->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(s->vtnr)),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", s->vtnr),
JSON_BUILD_PAIR_ENUM("Class", session_class_to_string(s->class)),
JSON_BUILD_PAIR_ENUM("Type", session_type_to_string(s->type)));
}
@@ -304,6 +385,346 @@ fail:
return r;
}
static int emit_session_reply(sd_varlink *link, Session *session) {
assert(link);
assert(session);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
int r = session_build_json(session, &v);
if (r < 0)
return r;
return sd_varlink_reply(link, v);
}
typedef struct ListSessionsParameters {
const char *id;
PidRef pidref;
} ListSessionsParameters;
static void list_sessions_parameters_done(ListSessionsParameters *p) {
assert(p);
pidref_done(&p->pidref);
}
static int vl_method_list_sessions(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(ListSessionsParameters, id), 0 },
{ "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(ListSessionsParameters, pidref), 0 },
{}
};
_cleanup_(list_sessions_parameters_done) ListSessionsParameters p = {
.pidref = PIDREF_NULL,
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
/* Unique-key path: Id and/or PID provided. Single reply or NoSuchSession. */
if (p.id || pidref_is_set(&p.pidref)) {
r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchSession");
if (r < 0)
return r;
Session *session;
r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, &p.pidref, &session);
if (r == -ESRCH)
return 0; /* triggers NoSuchSession sentinel */
if (r < 0)
return r;
return emit_session_reply(link, session);
}
/* Streaming path: no filter. Full list, requires 'more' flag. */
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL);
/* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */
r = sd_varlink_set_sentinel(link, /* error_id= */ NULL);
if (r < 0)
return r;
Session *session;
HASHMAP_FOREACH(session, m->sessions) {
r = emit_session_reply(link, session);
if (r < 0)
return r;
}
return 0;
}
static int manager_varlink_get_user_by_uid_or_pidref(
Manager *m,
uid_t uid,
const PidRef *pidref,
User **ret) {
int r;
assert(m);
assert(ret);
/* Resolves a user by UID and/or PID. At least one filter must be set. If both are set they must
* reference the same user, otherwise -ESRCH is returned. Returns -ESRCH on "not found". Returns
* negative errno on other failures. */
User *by_uid = NULL;
if (uid_is_valid(uid)) {
by_uid = hashmap_get(m->users, UID_TO_PTR(uid));
if (!by_uid)
return -ESRCH;
}
User *by_pid = NULL;
if (pidref && pidref_is_set(pidref)) {
uid_t pid_uid;
r = cg_pidref_get_owner_uid(pidref, &pid_uid);
if (r < 0) {
if (!IN_SET(r, -ENXIO, -ENOENT))
return log_debug_errno(r, "Failed to acquire owning UID of PID " PID_FMT ": %m", pidref->pid);
/* -ENXIO: PID not in a user-N.slice cgroup (e.g. PID 1).
* -ENOENT: cgroup gone (process terminated).
* Translate to -ESRCH so the caller maps to the NoSuchUser sentinel. */
return -ESRCH;
}
by_pid = hashmap_get(m->users, UID_TO_PTR(pid_uid));
if (!by_pid)
return -ESRCH;
}
if (by_uid && by_pid && by_uid != by_pid)
return log_debug_errno(SYNTHETIC_ERRNO(ESRCH),
"Search by UID " UID_FMT " and PID " PID_FMT " resulted in two different users",
uid, pidref->pid);
assert(by_uid || by_pid);
*ret = by_uid ?: by_pid;
return 0;
}
static int emit_user_reply(sd_varlink *link, User *user) {
assert(link);
assert(user);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
int r = user_build_json(user, &v);
if (r < 0)
return r;
return sd_varlink_reply(link, v);
}
typedef struct ListUsersParameters {
uid_t uid;
PidRef pidref;
} ListUsersParameters;
static void list_users_parameters_done(ListUsersParameters *p) {
assert(p);
pidref_done(&p->pidref);
}
static int vl_method_list_users(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "UID", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(ListUsersParameters, uid), 0 },
{ "PID", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_pidref, offsetof(ListUsersParameters, pidref), 0 },
{}
};
_cleanup_(list_users_parameters_done) ListUsersParameters p = {
.uid = UID_INVALID,
.pidref = PIDREF_NULL,
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
/* Unique-key path: UID and/or PID provided. Single reply or NoSuchUser. */
if (uid_is_valid(p.uid) || pidref_is_set(&p.pidref)) {
r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchUser");
if (r < 0)
return r;
User *user;
r = manager_varlink_get_user_by_uid_or_pidref(m, p.uid, &p.pidref, &user);
if (r == -ESRCH)
return 0; /* triggers NoSuchUser sentinel */
if (r < 0)
return r;
return emit_user_reply(link, user);
}
/* Streaming path: no filter. Full list, requires 'more' flag. */
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL);
/* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */
r = sd_varlink_set_sentinel(link, /* error_id= */ NULL);
if (r < 0)
return r;
User *user;
HASHMAP_FOREACH(user, m->users) {
r = emit_user_reply(link, user);
if (r < 0)
return r;
}
return 0;
}
static int manager_varlink_get_seat_by_name(
Manager *m,
sd_varlink *link,
const char *name,
Seat **ret) {
int r;
assert(m);
assert(link);
assert(name);
assert(ret);
/* Resolves a seat name to a seat object. Supports the special names "self" and "auto" — these
* resolve to the seat of the caller's session. Returns -EINVAL on invalid seat name. Returns
* -ESRCH on "not found". Caller is expected to turn that into a varlink error. */
if (seat_is_self(name) || seat_is_auto(name)) {
Session *session;
r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ seat_is_auto(name), &session);
if (r < 0)
return r;
if (!session || !session->seat)
return -ESRCH;
*ret = session->seat;
return 0;
}
if (!seat_name_is_valid(name))
return -EINVAL;
Seat *seat = hashmap_get(m->seats, name);
if (!seat)
return -ESRCH;
*ret = seat;
return 0;
}
static int emit_seat_reply(sd_varlink *link, Seat *seat) {
assert(link);
assert(seat);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
int r = seat_build_json(seat, &v);
if (r < 0)
return r;
return sd_varlink_reply(link, v);
}
static int vl_method_list_seats(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
int r;
const char *id = NULL;
static const sd_json_dispatch_field dispatch_table[] = {
{ "Id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
{}
};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &id);
if (r != 0)
return r;
/* Unique-key path: Id provided. Single reply or NoSuchSeat. */
if (id) {
r = sd_varlink_set_sentinel(link, "io.systemd.Login.NoSuchSeat");
if (r < 0)
return r;
Seat *seat;
r = manager_varlink_get_seat_by_name(m, link, id, &seat);
if (r == -EINVAL)
return sd_varlink_error_invalid_parameter_name(link, "Id");
if (r == -ESRCH)
return 0; /* triggers NoSuchSeat sentinel */
if (r < 0)
return r;
return emit_seat_reply(link, seat);
}
/* Streaming path: no filter. Full list, requires 'more' flag. */
if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, /* parameters= */ NULL);
/* Empty hashmap is a valid empty stream, not "not found" — see vl_method_list_inhibitors. */
r = sd_varlink_set_sentinel(link, /* error_id= */ NULL);
if (r < 0)
return r;
Seat *seat;
HASHMAP_FOREACH(seat, m->seats) {
r = emit_seat_reply(link, seat);
if (r < 0)
return r;
}
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;
@@ -322,7 +743,9 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete
return r;
Session *session;
r = manager_varlink_get_session_by_name(m, link, p.id, &session);
r = manager_varlink_get_session_by_name_or_pidref(m, link, p.id, /* pidref= */ NULL, &session);
if (r == -ESRCH)
return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL);
if (r < 0)
return r;
@@ -330,6 +753,8 @@ static int vl_method_release_session(sd_varlink *link, sd_json_variant *paramete
r = manager_varlink_get_session_by_peer(m, link, /* consult_display= */ false, &peer_session);
if (r < 0)
return r;
if (!peer_session)
return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL);
if (session != peer_session)
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, /* parameters= */ NULL);
@@ -467,6 +892,10 @@ int manager_varlink_init(Manager *m, int fd) {
s,
"io.systemd.Login.CreateSession", vl_method_create_session,
"io.systemd.Login.ReleaseSession", vl_method_release_session,
"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,
+3 -2
View File
@@ -1116,6 +1116,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us
Manager *m = ASSERT_PTR(userdata);
struct dual_timestamp since;
usec_t n, elapse;
bool idle;
int r;
if (m->idle_action == HANDLE_IGNORE ||
@@ -1124,8 +1125,8 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us
n = now(CLOCK_MONOTONIC);
r = manager_get_idle_hint(m, &since);
if (r <= 0) {
idle = manager_get_idle_hint(m, &since);
if (!idle) {
/* Not idle. Let's check if after a timeout it might be idle then. */
elapse = n + m->idle_action_usec;
m->was_idle = false;
+1 -1
View File
@@ -159,7 +159,7 @@ int manager_spawn_autovt(Manager *m, unsigned vtnr);
bool manager_shall_kill(Manager *m, const char *user);
int manager_get_idle_hint(Manager *m, dual_timestamp *t);
bool manager_get_idle_hint(Manager *m, dual_timestamp *ret_timestamp);
int manager_get_user_by_pid(Manager *m, pid_t pid, User **ret);
int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret);
+1 -1
View File
@@ -1147,7 +1147,7 @@ static int register_session(
JSON_BUILD_PAIR_ENUM("Class", c->class),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat),
SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", c->vtnr),
JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", c->tty),
JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display),
SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote),
-1
View File
@@ -5406,7 +5406,6 @@ static int vl_method_read_event_log(sd_varlink *link, sd_json_variant *parameter
if (r < 0)
return r;
// FIXME: We can't use a NULL sentinel here because the output fields in the IDL are non-nullable.
r = sd_varlink_set_sentinel(link, NULL);
if (r < 0)
return r;
+231 -3
View File
@@ -3,7 +3,7 @@
#include "varlink-idl-common.h"
#include "varlink-io.systemd.Login.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_TYPE(
SessionType,
SD_VARLINK_DEFINE_ENUM_VALUE(unspecified),
SD_VARLINK_DEFINE_ENUM_VALUE(tty),
@@ -12,7 +12,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_VALUE(mir),
SD_VARLINK_DEFINE_ENUM_VALUE(web));
static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_DEFINE_ENUM_TYPE(
SessionClass,
SD_VARLINK_FIELD_COMMENT("Regular user sessions"),
SD_VARLINK_DEFINE_ENUM_VALUE(user),
@@ -37,6 +37,62 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
SD_VARLINK_FIELD_COMMENT("The special session of the service manager for the root user"),
SD_VARLINK_DEFINE_ENUM_VALUE(manager_early));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SessionContext,
SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user owning this session"),
SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("PID of the session leader"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The type of the session"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, SessionType, 0),
SD_VARLINK_FIELD_COMMENT("The class of the session"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Class, SessionClass, 0),
SD_VARLINK_FIELD_COMMENT("PAM service name"),
SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Desktop identifier"),
SD_VARLINK_DEFINE_FIELD(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Seat this session is assigned to"),
SD_VARLINK_DEFINE_FIELD(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Virtual terminal number of the session, if applicable"),
SD_VARLINK_DEFINE_FIELD(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("TTY device of the session"),
SD_VARLINK_DEFINE_FIELD(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("X11 display of the session"),
SD_VARLINK_DEFINE_FIELD(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether this is a remote session"),
SD_VARLINK_DEFINE_FIELD(Remote, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Remote host, if this is a remote session"),
SD_VARLINK_DEFINE_FIELD(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Remote user, if this is a remote session"),
SD_VARLINK_DEFINE_FIELD(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Device paths this session has special access to"),
SD_VARLINK_DEFINE_FIELD(ExtraDeviceAccess, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SessionRuntime,
SD_VARLINK_FIELD_COMMENT("The session identifier"),
SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("systemd scope unit name"),
SD_VARLINK_DEFINE_FIELD(Scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Audit session ID"),
SD_VARLINK_DEFINE_FIELD(Audit, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The session timestamps"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Current state of the session"),
SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Whether the session is active (i.e. in the foreground)"),
SD_VARLINK_DEFINE_FIELD(Active, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether the session is idle"),
SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Timestamp when the session went idle, only present when IdleHint is true"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether the session is currently locked"),
SD_VARLINK_DEFINE_FIELD(LockedHint, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether this session class supports idle tracking"),
SD_VARLINK_DEFINE_FIELD(CanIdle, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether this session class supports locking"),
SD_VARLINK_DEFINE_FIELD(CanLock, SD_VARLINK_BOOL, 0));
static SD_VARLINK_DEFINE_METHOD(
CreateSession,
SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user this session shall be owned by"),
@@ -83,13 +139,151 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("The assigned session class"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, SessionClass, 0));
static SD_VARLINK_DEFINE_METHOD_FULL(
ListSessions,
SD_VARLINK_SUPPORTS_MORE,
SD_VARLINK_FIELD_COMMENT("If non-null, the identifier string of a session to look up. Supports the special names 'self' and 'auto' which resolve to the caller's session."),
SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If non-null, return the session containing the process with this PID. If both Id and PID are specified they must reference the same session, otherwise NoSuchSession is returned."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Configuration of the session"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, SessionContext, 0),
SD_VARLINK_FIELD_COMMENT("Runtime information of the session"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, SessionRuntime, 0));
static SD_VARLINK_DEFINE_METHOD(
ReleaseSession,
SD_VARLINK_FIELD_COMMENT("The identifier string of the session to release. If unspecified or 'self', will return the callers session."),
SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
UserContext,
SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID"),
SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("Numeric UNIX GID"),
SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, 0),
SD_VARLINK_FIELD_COMMENT("User name"),
SD_VARLINK_DEFINE_FIELD(Name, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("Whether lingering is enabled for this user"),
SD_VARLINK_DEFINE_FIELD(Linger, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Service manager unit name"),
SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("User slice unit name"),
SD_VARLINK_DEFINE_FIELD(Slice, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Path to the user's runtime directory"),
SD_VARLINK_DEFINE_FIELD(RuntimePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
UserRuntime,
SD_VARLINK_FIELD_COMMENT("Display session identifier"),
SD_VARLINK_DEFINE_FIELD(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Current state of the user"),
SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Identifiers of sessions belonging to this user"),
SD_VARLINK_DEFINE_FIELD(Sessions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Timestamp when the user was 'started' for the first time"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Whether the user is idle"),
SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Timestamp when the user went idle, only present when IdleHint is true"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
ListUsers,
SD_VARLINK_SUPPORTS_MORE,
SD_VARLINK_FIELD_COMMENT("If non-null, the UNIX UID of a user to look up."),
SD_VARLINK_DEFINE_INPUT(UID, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If non-null, return the user owning the cgroup containing the process with this PID. If both UID and PID are specified they must reference the same user, otherwise NoSuchUser is returned."),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Configuration of the user"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, UserContext, 0),
SD_VARLINK_FIELD_COMMENT("Runtime information of the user"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(runtime, UserRuntime, 0));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SeatContext,
SD_VARLINK_FIELD_COMMENT("The seat identifier"),
SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0));
static SD_VARLINK_DEFINE_STRUCT_TYPE(
SeatRuntime,
SD_VARLINK_FIELD_COMMENT("The currently active session on this seat, if any"),
SD_VARLINK_DEFINE_FIELD(ActiveSession, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Identifiers of sessions assigned to this seat"),
SD_VARLINK_DEFINE_FIELD(Sessions, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Whether this seat supports text terminal sessions"),
SD_VARLINK_DEFINE_FIELD(CanTTY, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether this seat supports graphical sessions"),
SD_VARLINK_DEFINE_FIELD(CanGraphical, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Whether the seat is idle"),
SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0),
SD_VARLINK_FIELD_COMMENT("Timestamp when the seat went idle, only present when IdleHint is true"),
SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD_FULL(
ListSeats,
SD_VARLINK_SUPPORTS_MORE,
SD_VARLINK_FIELD_COMMENT("If non-null, the identifier string of a seat to look up."),
SD_VARLINK_DEFINE_INPUT(Id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Configuration of the seat"),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(context, SeatContext, 0),
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);
@@ -102,18 +296,52 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_INTERFACE_COMMENT("APIs for managing login sessions."),
SD_VARLINK_SYMBOL_COMMENT("Process identifier"),
&vl_type_ProcessId,
SD_VARLINK_SYMBOL_COMMENT("Dual timestamp"),
&vl_type_Timestamp,
SD_VARLINK_SYMBOL_COMMENT("Various types of sessions"),
&vl_type_SessionType,
SD_VARLINK_SYMBOL_COMMENT("Various classes of sessions"),
&vl_type_SessionClass,
SD_VARLINK_SYMBOL_COMMENT("Configuration aspects of a session (suitable for reuse as creation input)"),
&vl_type_SessionContext,
SD_VARLINK_SYMBOL_COMMENT("Runtime state and dynamic information of a session"),
&vl_type_SessionRuntime,
SD_VARLINK_SYMBOL_COMMENT("Allocates a new session."),
&vl_method_CreateSession,
SD_VARLINK_SYMBOL_COMMENT("Releases an existing session. Currently, will be refuses unless originating from the session to release itself."),
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)."),
&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)."),
&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, 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
View File
@@ -3,4 +3,9 @@
#include "sd-varlink-idl.h"
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;
+4
View File
@@ -495,6 +495,10 @@ executables += [
'objects' : ['systemd-machined'],
'conditions' : ['ENABLE_MACHINED'],
},
test_template + {
'sources' : files('test-varlink-idl-login.c'),
'objects' : ['systemd-logind'],
},
test_template + {
'sources' : files('test-watchdog.c'),
'type' : 'unsafe',
+21
View File
@@ -0,0 +1,21 @@
/* 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"
#include "varlink-io.systemd.Login.h"
TEST(login_enums_idl) {
TEST_IDL_ENUM(SessionType, session_type, vl_type_SessionType);
/* 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);
+212 -1
View File
@@ -797,8 +797,219 @@ EOF
systemctl stop "$RUN0UNIT3"
}
teardown_varlink() (
set +ex
systemctl stop test-varlink-inhibit.service 2>/dev/null
cleanup_session
return 0
)
testcase_varlink() {
varlinkctl introspect /run/systemd/io.systemd.Login
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]}."
return
fi
trap teardown_varlink RETURN
local VARLINK_SOCKET="/run/systemd/io.systemd.Login"
: "--- Introspect ---"
varlinkctl introspect "$VARLINK_SOCKET"
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
session=$(loginctl --no-legend | grep -v manager | awk '$3 == "logind-test-user" { print $1 }')
uid=$(id -ru logind-test-user)
local leader_pid
leader_pid=$(loginctl show-session "$session" -p Leader --value)
loginctl activate "$session"
: "--- ListSessions: Id filter (single reply) ---"
session_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"Id\":\"$session\"}")
echo "$session_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null
echo "$session_out" | jq -e ".context.UID == $uid" >/dev/null
echo "$session_out" | jq -e ".context.PID.pid == $leader_pid" >/dev/null
echo "$session_out" | jq -e '.context.TTY == "tty2"' >/dev/null
echo "$session_out" | jq -e '.context.Remote == false' >/dev/null
echo "$session_out" | jq -e '.context.Type == "tty"' >/dev/null
echo "$session_out" | jq -e '.context.Class == "user"' >/dev/null
echo "$session_out" | jq -e '.runtime.CanIdle == true' >/dev/null
echo "$session_out" | jq -e '.runtime.CanLock == true' >/dev/null
echo "$session_out" | jq -e '.runtime.State == "active"' >/dev/null
echo "$session_out" | jq -e '.runtime.Active == true' >/dev/null
# ExtraDeviceAccess may be absent (empty) but must be an array if present.
echo "$session_out" | jq -e '(.context | has("ExtraDeviceAccess") | not) or (.context.ExtraDeviceAccess | type == "array")' >/dev/null
# nonexistent session id
(! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{"Id":"nonexistent"}')
: "--- ListSessions: PID filter (single reply) ---"
# Look up the session containing the session leader's PID.
local pid_out
pid_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions "{\"PID\":{\"pid\":$leader_pid}}")
echo "$pid_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null
: "--- ListSessions: Id+PID consistency check ---"
# Same session referenced two ways: must succeed.
local ok_out mismatch_err
ok_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \
"{\"Id\":\"$session\",\"PID\":{\"pid\":$leader_pid}}")
echo "$ok_out" | jq -e ".runtime.Id == \"$session\"" >/dev/null
# Mismatched Id and PID (PID 1 is not in $session): must fail with NoSuchSession.
mismatch_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions \
"{\"Id\":\"$session\",\"PID\":{\"pid\":1}}" 2>&1 || true)
echo "$mismatch_err" | grep NoSuchSession >/dev/null
: "--- ListSessions: streaming path ---"
# varlinkctl --more emits RFC 7464 JSON-seq (each record prefixed with RS/0x1e), so jq --seq is required.
varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' \
| jq --seq -e --arg s "$session" 'select(.runtime.Id == $s)' >/dev/null
test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' | wc -l)" -ge 2
# without --more and no filter: must fail with ExpectedMore (not assert()) so logind stays running.
local list_err
list_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSessions '{}' 2>&1 || true)
# varlinkctl rewrites SD_VARLINK_ERROR_EXPECTED_MORE to the friendly description
# below (see src/varlinkctl/varlinkctl.c), so match on that substring rather than
# the raw error id.
echo "$list_err" | grep "'more' flag" >/dev/null
systemctl is-active systemd-logind.service >/dev/null
: "--- ListUsers: UID filter (single reply) ---"
user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers "{\"UID\":$uid}")
echo "$user_out" | jq -e ".context.UID == $uid" >/dev/null
echo "$user_out" | jq -e '.context.Name == "logind-test-user"' >/dev/null
echo "$user_out" | jq -e '.runtime.State' >/dev/null
echo "$user_out" | jq -e '.context.Linger == false' >/dev/null
# Sessions is now a flat array of session id strings (no wrapper object).
echo "$user_out" | jq -e ".runtime.Sessions[] | select(. == \"$session\")" >/dev/null
: "--- ListUsers: PID filter (single reply) ---"
# PID of a process in the test user's session should map back to the user.
local pid_user_out
pid_user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \
"{\"PID\":{\"pid\":$leader_pid}}")
echo "$pid_user_out" | jq -e --argjson u "$uid" '.context.UID == $u' >/dev/null
: "--- ListUsers: UID+PID consistency check ---"
# Same user referenced two ways: must succeed.
local ok_user_out mismatch_user_err
ok_user_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \
"{\"UID\":$uid,\"PID\":{\"pid\":$leader_pid}}")
echo "$ok_user_out" | jq -e --argjson u "$uid" '.context.UID == $u' >/dev/null
# Mismatched UID and PID (PID 1 is systemd init, UID 0): must fail with NoSuchUser.
mismatch_user_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers \
"{\"UID\":$uid,\"PID\":{\"pid\":1}}" 2>&1 || true)
echo "$mismatch_user_err" | grep NoSuchUser >/dev/null
: "--- ListUsers: empty input without --more must require --more ---"
# The DescribeUser-style caller-UID fallback was removed; a no-filter call without
# --more must fail with the EXPECTED_MORE error.
local empty_users_err
empty_users_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' 2>&1 || true)
echo "$empty_users_err" | grep "'more' flag" >/dev/null
systemctl is-active systemd-logind.service >/dev/null
# nonexistent UID
(! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{"UID":4294967294}')
: "--- ListUsers: streaming path ---"
varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' \
| jq --seq -e 'select(.context.Name == "logind-test-user")' >/dev/null
test "$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.Login.ListUsers '{}' | wc -l)" -ge 2
: "--- ListSeats: Id filter (single reply) ---"
seat_out=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{"Id":"seat0"}')
echo "$seat_out" | jq -e '.context.Id == "seat0"' >/dev/null
echo "$seat_out" | jq -e '.runtime.CanTTY == true' >/dev/null
# Sessions is a flat array of session id strings.
echo "$seat_out" | jq -e ".runtime.Sessions[] | select(. == \"$session\")" >/dev/null
# nonexistent seat
(! varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{"Id":"seat-nonexistent"}')
: "--- ListSeats: empty input without --more must require --more ---"
# The caller-seat fallback was removed; no filter + no --more = EXPECTED_MORE.
local empty_seats_err
empty_seats_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats '{}' 2>&1 || true)
echo "$empty_seats_err" | grep "'more' flag" >/dev/null
systemctl is-active systemd-logind.service >/dev/null
: "--- ListSeats: self/auto from session-less context yields NoSuchSeat ---"
# self/auto still resolves via peer session; running as root outside any session
# has no peer session, so we must get NoSuchSeat (not NoSuchSession leaked from
# the lookup helper).
local self_err
for id_arg in '{"Id":"self"}' '{"Id":"auto"}'; do
self_err=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ListSeats "$id_arg" 2>&1 || true)
echo "$self_err" | grep NoSuchSeat >/dev/null
(! echo "$self_err" | grep NoSuchSession >/dev/null)
done
: "--- ListSeats: streaming path ---"
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
# PAM so pam_systemd creates a real session for the caller.
local PAMSERVICE_REL="pamserv-rel$RANDOM"
cat >/etc/pam.d/"$PAMSERVICE_REL" <<EOF
auth sufficient pam_unix.so
auth required pam_deny.so
account sufficient pam_unix.so
account required pam_permit.so
session optional pam_systemd.so debug
session required pam_unix.so
EOF
systemd-run --wait --pipe \
-p PAMName="$PAMSERVICE_REL" \
-p Type=exec \
-p User=logind-test-user \
varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ReleaseSession '{}'
rm -f /etc/pam.d/"$PAMSERVICE_REL"
# With no caller session (running as root outside any session), the helper
# must return NoSuchSession, not PermissionDenied.
local no_sess_rel
no_sess_rel=$(varlinkctl call "$VARLINK_SOCKET" io.systemd.Login.ReleaseSession '{}' 2>&1 || true)
echo "$no_sess_rel" | grep NoSuchSession >/dev/null
}
testcase_restart() {