#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"
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",
bool session_is_self(const char *name);
bool session_is_auto(const char *name);
+
+int session_build_json(Session *s, sd_json_variant **ret);
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);
} 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 (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;
+ }
- 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 (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;
+ }
- Session *session = hashmap_get(m->sessions, name);
- if (!session)
- return sd_varlink_error(link, "io.systemd.Login.NoSuchSession", /* parameters= */ NULL);
+ 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);
- *ret = session;
+ 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;
}
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)));
}
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 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;
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;
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);
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.Shutdown.PowerOff", vl_method_power_off,
"io.systemd.Shutdown.Reboot", vl_method_reboot,
"io.systemd.Shutdown.Halt", vl_method_halt,
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),
#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),
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),
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"),
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_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("No session by this name found"),
&vl_error_NoSuchSession,
SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"),
#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_interface vl_interface_io_systemd_Login;
'sources' : files('test-varlink-idl-machine.c'),
'objects' : ['systemd-machined'],
},
+ test_template + {
+ 'sources' : files('test-varlink-idl-login.c'),
+ 'objects' : ['systemd-logind'],
+ },
test_template + {
'sources' : files('test-watchdog.c'),
'type' : 'unsafe',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#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);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
systemctl stop "$RUN0UNIT3"
}
+teardown_varlink() (
+ set +ex
+
+ cleanup_session
+
+ return 0
+)
+
testcase_varlink() {
- varlinkctl introspect /run/systemd/io.systemd.Login
+ local session uid session_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
+
+ : "--- 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
+
+ : "--- 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() {