]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
logind: add ListSessions Varlink method
authorYaping Li <202858510+YapingLi04@users.noreply.github.com>
Sun, 10 May 2026 14:50:12 +0000 (14:50 +0000)
committerYaping Li <202858510+YapingLi04@users.noreply.github.com>
Fri, 22 May 2026 02:56:24 +0000 (02:56 +0000)
The Varlink ListSessions method accepts optional Id and PID filters,
folding in the D-Bus GetSession(s) and GetSessionByPID(u) lookups.

Passing a unique-key filter (Id and/or PID) yields a single reply on
match, or NoSuchSession on miss. Passing no filter streams the full
list (requires the 'more' flag). Specifying both Id and PID acts as a
consistency check: both must refer to the same session, otherwise
NoSuchSession is returned.

The Id filter supports the special names "self" and "auto" which
resolve to the caller's session. The SessionInfo type in the
io.systemd.Login Varlink IDL carries all session properties matching
the D-Bus org.freedesktop.login1.Session interface.

src/login/logind-session.c
src/login/logind-session.h
src/login/logind-varlink.c
src/login/pam_systemd.c
src/shared/varlink-io.systemd.Login.c
src/shared/varlink-io.systemd.Login.h
src/test/meson.build
src/test/test-varlink-idl-login.c [new file with mode: 0644]
test/units/TEST-35-LOGIN.sh

index 1b8731e2c64937689ba86f7429eb04e87af8e404..e62a5768da69325668b52969da48566bf8805c2e 100644 (file)
@@ -28,6 +28,7 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "hashmap.h"
+#include "json-util.h"
 #include "login-util.h"
 #include "logind.h"
 #include "logind-dbus.h"
@@ -1684,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",
index da8c52510aea04f11d9dc5ea6826794330469620..b0749134af620835294d3afeb127a5fa453e7093 100644 (file)
@@ -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);
index 71a445e90032efb14fe0db32f00dfe5440fb7041..9b58791991671b4504d1c326726907303bcf73d7 100644 (file)
@@ -41,7 +41,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 +71,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 (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;
 }
 
@@ -129,7 +209,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 +384,81 @@ 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 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 +477,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 +487,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 +626,7 @@ 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.Shutdown.PowerOff",      vl_method_power_off,
                         "io.systemd.Shutdown.Reboot",        vl_method_reboot,
                         "io.systemd.Shutdown.Halt",          vl_method_halt,
index 1b0adfae9ea823d6de3094994ebd8d39c25cea2f..b6f7ddd38c214107eb11b0ce1e514c832838c9fd 100644 (file)
@@ -1147,7 +1147,7 @@ static int register_session(
                                         JSON_BUILD_PAIR_ENUM("Class", c->class),
                                         JSON_BUILD_PAIR_STRING_NON_EMPTY("Desktop", c->desktop),
                                         JSON_BUILD_PAIR_STRING_NON_EMPTY("Seat", c->seat),
-                                        SD_JSON_BUILD_PAIR_CONDITION(c->vtnr > 0, "VTNr", SD_JSON_BUILD_UNSIGNED(c->vtnr)),
+                                        JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("VTNr", c->vtnr),
                                         JSON_BUILD_PAIR_STRING_NON_EMPTY("TTY", c->tty),
                                         JSON_BUILD_PAIR_STRING_NON_EMPTY("Display", c->display),
                                         SD_JSON_BUILD_PAIR_BOOLEAN("Remote", c->remote),
index 9a07155ef20cab0fbcd04b79ced13af54fe760fc..42cd0aa95a082a2a41d2b3a63b7e6ffd4b82d499 100644 (file)
@@ -3,7 +3,7 @@
 #include "varlink-idl-common.h"
 #include "varlink-io.systemd.Login.h"
 
-static SD_VARLINK_DEFINE_ENUM_TYPE(
+SD_VARLINK_DEFINE_ENUM_TYPE(
                 SessionType,
                 SD_VARLINK_DEFINE_ENUM_VALUE(unspecified),
                 SD_VARLINK_DEFINE_ENUM_VALUE(tty),
@@ -12,7 +12,7 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
                 SD_VARLINK_DEFINE_ENUM_VALUE(mir),
                 SD_VARLINK_DEFINE_ENUM_VALUE(web));
 
-static SD_VARLINK_DEFINE_ENUM_TYPE(
+SD_VARLINK_DEFINE_ENUM_TYPE(
                 SessionClass,
                 SD_VARLINK_FIELD_COMMENT("Regular user sessions"),
                 SD_VARLINK_DEFINE_ENUM_VALUE(user),
@@ -37,6 +37,62 @@ static SD_VARLINK_DEFINE_ENUM_TYPE(
                 SD_VARLINK_FIELD_COMMENT("The special session of the service manager for the root user"),
                 SD_VARLINK_DEFINE_ENUM_VALUE(manager_early));
 
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+                SessionContext,
+                SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user owning this session"),
+                SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0),
+                SD_VARLINK_FIELD_COMMENT("PID of the session leader"),
+                SD_VARLINK_DEFINE_FIELD_BY_TYPE(PID, ProcessId, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The type of the session"),
+                SD_VARLINK_DEFINE_FIELD_BY_TYPE(Type, SessionType, 0),
+                SD_VARLINK_FIELD_COMMENT("The class of the session"),
+                SD_VARLINK_DEFINE_FIELD_BY_TYPE(Class, SessionClass, 0),
+                SD_VARLINK_FIELD_COMMENT("PAM service name"),
+                SD_VARLINK_DEFINE_FIELD(Service, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Desktop identifier"),
+                SD_VARLINK_DEFINE_FIELD(Desktop, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Seat this session is assigned to"),
+                SD_VARLINK_DEFINE_FIELD(Seat, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Virtual terminal number of the session, if applicable"),
+                SD_VARLINK_DEFINE_FIELD(VTNr, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("TTY device of the session"),
+                SD_VARLINK_DEFINE_FIELD(TTY, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("X11 display of the session"),
+                SD_VARLINK_DEFINE_FIELD(Display, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Whether this is a remote session"),
+                SD_VARLINK_DEFINE_FIELD(Remote, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("Remote host, if this is a remote session"),
+                SD_VARLINK_DEFINE_FIELD(RemoteHost, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Remote user, if this is a remote session"),
+                SD_VARLINK_DEFINE_FIELD(RemoteUser, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Device paths this session has special access to"),
+                SD_VARLINK_DEFINE_FIELD(ExtraDeviceAccess, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY));
+
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+                SessionRuntime,
+                SD_VARLINK_FIELD_COMMENT("The session identifier"),
+                SD_VARLINK_DEFINE_FIELD(Id, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("systemd scope unit name"),
+                SD_VARLINK_DEFINE_FIELD(Scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Audit session ID"),
+                SD_VARLINK_DEFINE_FIELD(Audit, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("The session timestamps"),
+                SD_VARLINK_DEFINE_FIELD_BY_TYPE(Timestamp, Timestamp, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Current state of the session"),
+                SD_VARLINK_DEFINE_FIELD(State, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("Whether the session is active (i.e. in the foreground)"),
+                SD_VARLINK_DEFINE_FIELD(Active, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("Whether the session is idle"),
+                SD_VARLINK_DEFINE_FIELD(IdleHint, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("Timestamp when the session went idle, only present when IdleHint is true"),
+                SD_VARLINK_DEFINE_FIELD_BY_TYPE(IdleSinceHint, Timestamp, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Whether the session is currently locked"),
+                SD_VARLINK_DEFINE_FIELD(LockedHint, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("Whether this session class supports idle tracking"),
+                SD_VARLINK_DEFINE_FIELD(CanIdle, SD_VARLINK_BOOL, 0),
+                SD_VARLINK_FIELD_COMMENT("Whether this session class supports locking"),
+                SD_VARLINK_DEFINE_FIELD(CanLock, SD_VARLINK_BOOL, 0));
+
 static SD_VARLINK_DEFINE_METHOD(
                 CreateSession,
                 SD_VARLINK_FIELD_COMMENT("Numeric UNIX UID of the user this session shall be owned by"),
@@ -83,6 +139,18 @@ 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."),
@@ -102,14 +170,22 @@ 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("No session by this name found"),
                 &vl_error_NoSuchSession,
                 SD_VARLINK_SYMBOL_COMMENT("No seat by this name found"),
index 5453cdfb25f886507554d4528caa69f8ac12a2cf..52a2ddd97491fe314be6816064f0ddb825558fb7 100644 (file)
@@ -3,4 +3,7 @@
 
 #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;
index 0c8dba64d5678992795d5ccd5fecc9c36fe616ee..8a25cc03dc35db269f6bb73c5c9af30da797f649 100644 (file)
@@ -494,6 +494,10 @@ executables += [
                 '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',
diff --git a/src/test/test-varlink-idl-login.c b/src/test/test-varlink-idl-login.c
new file mode 100644 (file)
index 0000000..7dff801
--- /dev/null
@@ -0,0 +1,15 @@
+/* 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);
index 58b3bcca07888b090ab74ceda128585daa1ed9f3..74e83c79d4cf25689d7004ab41509a07d800019f 100755 (executable)
@@ -797,8 +797,113 @@ EOF
     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() {