diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index af9dd30973..1b0efa3992 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -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; diff --git a/src/login/logind-core.c b/src/login/logind-core.c index c8846d592b..29ca81eec9 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -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; } diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index b773dbfa42..4afc4952b0 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -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, diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index 30b43404b1..99ec0a3c99 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -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", diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h index 1cb4ed4605..c606582df2 100644 --- a/src/login/logind-inhibit.h +++ b/src/login/logind-inhibit.h @@ -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; } diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c index a07a0e49ee..3e236e8bb7 100644 --- a/src/login/logind-seat-dbus.c +++ b/src/login/logind-seat-dbus.c @@ -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; diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 66c27d3d73..1fe1eb41f4 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -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) || diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h index 323cbe546e..3f6d2aaf3c 100644 --- a/src/login/logind-seat.h +++ b/src/login/logind-seat.h @@ -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); diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index 4da9bb2425..90cc576fec 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -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; diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 420ec81008..e62a5768da 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -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", diff --git a/src/login/logind-session.h b/src/login/logind-session.h index f51eed24fa..b0749134af 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -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); diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c index b67e21e052..ca32e8b056 100644 --- a/src/login/logind-user-dbus.c +++ b/src/login/logind-user-dbus.c @@ -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); diff --git a/src/login/logind-user.c b/src/login/logind-user.c index c0342dfe16..9bac65cc81 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -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", diff --git a/src/login/logind-user.h b/src/login/logind-user.h index 2ae8faf375..3119e7ed21 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -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); diff --git a/src/login/logind-varlink.c b/src/login/logind-varlink.c index 71a445e900..382f8fea22 100644 --- a/src/login/logind-varlink.c +++ b/src/login/logind-varlink.c @@ -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, diff --git a/src/login/logind.c b/src/login/logind.c index c19c1cb791..973506a892 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -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; diff --git a/src/login/logind.h b/src/login/logind.h index 4e3c15de52..d96bb753dd 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -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); diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index 1b0adfae9e..b6f7ddd38c 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -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), diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index a0d415209c..247c36cca1 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -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; diff --git a/src/shared/varlink-io.systemd.Login.c b/src/shared/varlink-io.systemd.Login.c index 9a07155ef2..7d7f5f2a4a 100644 --- a/src/shared/varlink-io.systemd.Login.c +++ b/src/shared/varlink-io.systemd.Login.c @@ -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"), diff --git a/src/shared/varlink-io.systemd.Login.h b/src/shared/varlink-io.systemd.Login.h index 5453cdfb25..2d751eebdf 100644 --- a/src/shared/varlink-io.systemd.Login.h +++ b/src/shared/varlink-io.systemd.Login.h @@ -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; diff --git a/src/test/meson.build b/src/test/meson.build index 404e366861..490b9b7d0d 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -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', diff --git a/src/test/test-varlink-idl-login.c b/src/test/test-varlink-idl-login.c new file mode 100644 index 0000000000..fb8ef81363 --- /dev/null +++ b/src/test/test-varlink-idl-login.c @@ -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); diff --git a/test/units/TEST-35-LOGIN.sh b/test/units/TEST-35-LOGIN.sh index 58b3bcca07..e4f9621c8d 100755 --- a/test/units/TEST-35-LOGIN.sh +++ b/test/units/TEST-35-LOGIN.sh @@ -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" <&1 || true) + echo "$no_sess_rel" | grep NoSuchSession >/dev/null } testcase_restart() {