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