diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
commit | 78e9bb837c258ac0ec7712b3d612cc2f407e731e (patch) | |
tree | f515d16b6efd858a9aeb5b0ef5d6f90bf288283d /src/login/logind-user.c | |
parent | Adding debian version 255.5-1. (diff) | |
download | systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.tar.xz systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.zip |
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/login/logind-user.c')
-rw-r--r-- | src/login/logind-user.c | 345 |
1 files changed, 232 insertions, 113 deletions
diff --git a/src/login/logind-user.c b/src/login/logind-user.c index c613307..8066b3e 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -33,21 +33,18 @@ #include "string-table.h" #include "strv.h" #include "tmpfile-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "unit-name.h" #include "user-util.h" -int user_new(User **ret, - Manager *m, - UserRecord *ur) { - +int user_new(Manager *m, UserRecord *ur, User **ret) { _cleanup_(user_freep) User *u = NULL; char lu[DECIMAL_STR_MAX(uid_t) + 1]; int r; - assert(ret); assert(m); assert(ur); + assert(ret); if (!ur->user_name) return -EINVAL; @@ -63,6 +60,7 @@ int user_new(User **ret, .manager = m, .user_record = user_record_ref(ur), .last_session_timestamp = USEC_INFINITY, + .gc_mode = USER_GC_BY_ANY, }; if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0) @@ -76,11 +74,11 @@ int user_new(User **ret, if (r < 0) return r; - r = unit_name_build("user", lu, ".service", &u->service); + r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_unit); if (r < 0) return r; - r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service); + r = unit_name_build("user", lu, ".service", &u->service_manager_unit); if (r < 0) return r; @@ -92,11 +90,11 @@ int user_new(User **ret, if (r < 0) return r; - r = hashmap_put(m->user_units, u->service, u); + r = hashmap_put(m->user_units, u->runtime_dir_unit, u); if (r < 0) return r; - r = hashmap_put(m->user_units, u->runtime_dir_service, u); + r = hashmap_put(m->user_units, u->service_manager_unit, u); if (r < 0) return r; @@ -114,26 +112,29 @@ User *user_free(User *u) { while (u->sessions) session_free(u->sessions); - if (u->service) - hashmap_remove_value(u->manager->user_units, u->service, u); - - if (u->runtime_dir_service) - hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u); + sd_event_source_unref(u->timer_event_source); - if (u->slice) - hashmap_remove_value(u->manager->user_units, u->slice, u); + if (u->service_manager_unit) { + (void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u); + free(u->service_manager_job); + free(u->service_manager_unit); + } - hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); + if (u->runtime_dir_unit) { + (void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u); + free(u->runtime_dir_job); + free(u->runtime_dir_unit); + } - sd_event_source_unref(u->timer_event_source); + if (u->slice) { + (void) hashmap_remove_value(u->manager->user_units, u->slice, u); + free(u->slice); + } - u->service_job = mfree(u->service_job); + (void) hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); - u->service = mfree(u->service); - u->runtime_dir_service = mfree(u->runtime_dir_service); - u->slice = mfree(u->slice); - u->runtime_path = mfree(u->runtime_path); - u->state_file = mfree(u->state_file); + free(u->runtime_path); + free(u->state_file); user_record_unref(u->user_record); @@ -162,17 +163,22 @@ static int user_save_internal(User *u) { "# This is private data. Do not parse.\n" "NAME=%s\n" "STATE=%s\n" /* friendly user-facing state */ - "STOPPING=%s\n", /* low-level state */ + "STOPPING=%s\n" /* low-level state */ + "GC_MODE=%s\n", u->user_record->user_name, user_state_to_string(user_get_state(u)), - yes_no(u->stopping)); + yes_no(u->stopping), + user_gc_mode_to_string(u->gc_mode)); /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */ if (u->runtime_path) fprintf(f, "RUNTIME=%s\n", u->runtime_path); - if (u->service_job) - fprintf(f, "SERVICE_JOB=%s\n", u->service_job); + if (u->runtime_dir_job) + fprintf(f, "RUNTIME_DIR_JOB=%s\n", u->runtime_dir_job); + + if (u->service_manager_job) + fprintf(f, "SERVICE_JOB=%s\n", u->service_manager_job); if (u->display) fprintf(f, "DISPLAY=%s\n", u->display->id); @@ -302,17 +308,19 @@ int user_save(User *u) { } int user_load(User *u) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL; int r; assert(u); r = parse_env_file(NULL, u->state_file, - "SERVICE_JOB", &u->service_job, + "RUNTIME_DIR_JOB", &u->runtime_dir_job, + "SERVICE_JOB", &u->service_manager_job, "STOPPING", &stopping, "REALTIME", &realtime, "MONOTONIC", &monotonic, - "LAST_SESSION_TIMESTAMP", &last_session_timestamp); + "LAST_SESSION_TIMESTAMP", &last_session_timestamp, + "GC_MODE", &gc_mode); if (r == -ENOENT) return 0; if (r < 0) @@ -322,8 +330,11 @@ int user_load(User *u) { r = parse_boolean(stopping); if (r < 0) log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping); - else + else { u->stopping = r; + if (u->stopping && !u->runtime_dir_job) + log_debug("User '%s' is stopping, but no job is being tracked.", u->user_record->user_name); + } } if (realtime) @@ -333,25 +344,74 @@ int user_load(User *u) { if (last_session_timestamp) (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp); + u->gc_mode = user_gc_mode_from_string(gc_mode); + if (u->gc_mode < 0) + u->gc_mode = USER_GC_BY_PIN; + return 0; } -static void user_start_service(User *u) { +static int user_start_runtime_dir(User *u) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(u); + assert(!u->stopping); + assert(u->manager); + assert(u->runtime_dir_unit); - /* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly - * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by - * user@.service and the session scopes as dependencies. */ + u->runtime_dir_job = mfree(u->runtime_dir_job); - u->service_job = mfree(u->service_job); - - r = manager_start_unit(u->manager, u->service, &error, &u->service_job); + r = manager_start_unit(u->manager, u->runtime_dir_unit, &error, &u->runtime_dir_job); if (r < 0) - log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r)); + return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_ERR, + r, "Failed to start user service '%s': %s", + u->runtime_dir_unit, bus_error_message(&error, r)); + + return 0; +} + +static bool user_wants_service_manager(const User *u) { + assert(u); + + LIST_FOREACH(sessions_by_user, s, u->sessions) + if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class)) + return true; + + if (user_check_linger_file(u) > 0) + return true; + + return false; +} + +int user_start_service_manager(User *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(u); + assert(!u->stopping); + assert(u->manager); + assert(u->service_manager_unit); + + if (u->service_manager_started) + return 1; + + /* Only start user service manager if there's at least one session which wants it */ + if (!user_wants_service_manager(u)) + return 0; + + u->service_manager_job = mfree(u->service_manager_job); + + r = manager_start_unit(u->manager, u->service_manager_unit, &error, &u->service_manager_job); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED)) + return 0; + + return log_error_errno(r, "Failed to start user service '%s': %s", + u->service_manager_unit, bus_error_message(&error, r)); + } + + return (u->service_manager_started = true); } static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { @@ -412,12 +472,14 @@ static int user_update_slice(User *u) { { "IOWeight", u->user_record->io_weight }, }; - for (size_t i = 0; i < ELEMENTSOF(settings); i++) - if (settings[i].value != UINT64_MAX) { - r = sd_bus_message_append(m, "(sv)", settings[i].name, "t", settings[i].value); - if (r < 0) - return bus_log_create_error(r); - } + FOREACH_ELEMENT(st, settings) { + if (st->value == UINT64_MAX) + continue; + + r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value); + if (r < 0) + return bus_log_create_error(r); + } r = sd_bus_message_close_container(m); if (r < 0) @@ -434,33 +496,47 @@ static int user_update_slice(User *u) { } int user_start(User *u) { + int r; + assert(u); - if (u->started && !u->stopping) + if (u->service_manager_started) { + /* Everything is up. No action needed. */ + assert(u->started && !u->stopping); return 0; + } - /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear - * that flag before queueing the start-jobs again. If they succeed, the user object can be re-used just fine - * (pid1 takes care of job-ordering and proper restart), but if they fail, we want to force another user_stop() - * so possibly pending units are stopped. */ - u->stopping = false; + if (!u->started || u->stopping) { + /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. + * We have to clear that flag before queueing the start-jobs again. If they succeed, the + * user object can be reused just fine (pid1 takes care of job-ordering and proper restart), + * but if they fail, we want to force another user_stop() so possibly pending units are + * stopped. */ + u->stopping = false; - if (!u->started) - log_debug("Starting services for new user %s.", u->user_record->user_name); + if (!u->started) + log_debug("Tracking new user %s.", u->user_record->user_name); - /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up - * systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */ - user_save_internal(u); + /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it + * while starting up systemd --user. We need to do user_save_internal() because we have not + * "officially" started yet. */ + user_save_internal(u); - /* Set slice parameters */ - (void) user_update_slice(u); + /* Set slice parameters */ + (void) user_update_slice(u); - /* Start user@UID.service */ - user_start_service(u); + (void) user_start_runtime_dir(u); + } + + /* Start user@UID.service if needed. */ + r = user_start_service_manager(u); + if (r < 0) + return r; if (!u->started) { if (!dual_timestamp_is_set(&u->timestamp)) dual_timestamp_now(&u->timestamp); + user_send_signal(u, true); u->started = true; } @@ -476,16 +552,22 @@ static void user_stop_service(User *u, bool force) { int r; assert(u); - assert(u->service); + assert(u->manager); + assert(u->runtime_dir_unit); + + /* Note that we only stop user-runtime-dir@.service here, and let BindsTo= deal with the user@.service + * instance. However, we still need to clear service_manager_job here, so that if the stop is + * interrupted, the new sessions won't be confused by leftovers. */ - /* The reverse of user_start_service(). Note that we only stop user@UID.service here, and let StopWhenUnneeded= - * deal with the slice and the user-runtime-dir@.service instance. */ + u->service_manager_job = mfree(u->service_manager_job); + u->service_manager_started = false; - u->service_job = mfree(u->service_job); + u->runtime_dir_job = mfree(u->runtime_dir_job); - r = manager_stop_unit(u->manager, u->service, force ? "replace" : "fail", &error, &u->service_job); + r = manager_stop_unit(u->manager, u->runtime_dir_unit, force ? "replace" : "fail", &error, &u->runtime_dir_job); if (r < 0) - log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r)); + log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", + u->runtime_dir_unit, bus_error_message(&error, r)); } int user_stop(User *u, bool force) { @@ -506,13 +588,8 @@ int user_stop(User *u, bool force) { return 0; } - LIST_FOREACH(sessions_by_user, s, u->sessions) { - int k; - - k = session_stop(s, force); - if (k < 0) - r = k; - } + LIST_FOREACH(sessions_by_user, s, u->sessions) + RET_GATHER(r, session_stop(s, force)); user_stop_service(u, force); @@ -524,7 +601,7 @@ int user_stop(User *u, bool force) { } int user_finalize(User *u) { - int r = 0, k; + int r = 0; assert(u); @@ -532,13 +609,10 @@ int user_finalize(User *u) { * done. This is called as a result of an earlier user_done() when all jobs are completed. */ if (u->started) - log_debug("User %s logged out.", u->user_record->user_name); + log_debug("User %s exited.", u->user_record->user_name); - LIST_FOREACH(sessions_by_user, s, u->sessions) { - k = session_finalize(s); - if (k < 0) - r = k; - } + LIST_FOREACH(sessions_by_user, s, u->sessions) + RET_GATHER(r, session_finalize(s)); /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to @@ -546,11 +620,8 @@ int user_finalize(User *u) { * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up, * and do it only for normal users. */ - if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) { - k = clean_ipc_by_uid(u->user_record->uid); - if (k < 0) - r = k; - } + if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) + RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid)); (void) unlink(u->state_file); user_add_to_gc_queue(u); @@ -573,6 +644,9 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { dual_timestamp k; int ih; + if (!SESSION_CLASS_CAN_IDLE(s->class)) + continue; + ih = session_get_idle_hint(s, &k); if (ih < 0) return ih; @@ -598,9 +672,12 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { return idle_hint; } -int user_check_linger_file(User *u) { +int user_check_linger_file(const User *u) { _cleanup_free_ char *cc = NULL; - char *p = NULL; + const char *p; + + assert(u); + assert(u->user_record); cc = cescape(u->user_record->user_name); if (!cc) @@ -620,11 +697,11 @@ int user_check_linger_file(User *u) { static bool user_unit_active(User *u) { int r; - assert(u->service); - assert(u->runtime_dir_service); assert(u->slice); + assert(u->runtime_dir_unit); + assert(u->service_manager_unit); - FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) { + FOREACH_STRING(i, u->slice, u->runtime_dir_unit, u->service_manager_unit) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; r = manager_unit_is_active(u->manager, i, &error); @@ -649,6 +726,29 @@ static usec_t user_get_stop_delay(User *u) { return u->manager->user_stop_delay; } +static bool user_pinned_by_sessions(User *u) { + assert(u); + + /* Returns true if at least one session exists that shall keep the user tracking alive. That + * generally means one session that isn't the service manager still exists. */ + + switch (u->gc_mode) { + + case USER_GC_BY_ANY: + return u->sessions; + + case USER_GC_BY_PIN: + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (SESSION_CLASS_PIN_USER(i->class)) + return true; + + return false; + + default: + assert_not_reached(); + } +} + bool user_may_gc(User *u, bool drop_not_started) { int r; @@ -657,7 +757,7 @@ bool user_may_gc(User *u, bool drop_not_started) { if (drop_not_started && !u->started) return true; - if (u->sessions) + if (user_pinned_by_sessions(u)) return false; if (u->last_session_timestamp != USEC_INFINITY) { @@ -681,18 +781,23 @@ bool user_may_gc(User *u, bool drop_not_started) { return false; /* Check if our job is still pending */ - if (u->service_job) { + const char *j; + FOREACH_ARGUMENT(j, u->runtime_dir_job, u->service_manager_job) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = manager_job_is_active(u->manager, u->service_job, &error); + if (!j) + continue; + + r = manager_job_is_active(u->manager, j, &error); if (r < 0) - log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r)); + log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", + j, bus_error_message(&error, r)); if (r != 0) return false; } - /* Note that we don't care if the three units we manage for each user object are up or not, as we are managing - * their state rather than tracking it. */ + /* Note that we don't care if the three units we manage for each user object are up or not, as we are + * managing their state rather than tracking it. */ return true; } @@ -713,25 +818,29 @@ UserState user_get_state(User *u) { if (u->stopping) return USER_CLOSING; - if (!u->started || u->service_job) + if (!u->started || u->runtime_dir_job) return USER_OPENING; - if (u->sessions) { - bool all_closing = true; + bool any = false, all_closing = true; + LIST_FOREACH(sessions_by_user, i, u->sessions) { + SessionState state; - LIST_FOREACH(sessions_by_user, i, u->sessions) { - SessionState state; + /* Ignore sessions that don't pin the user, i.e. are not supposed to have an effect on user state */ + if (!SESSION_CLASS_PIN_USER(i->class)) + continue; - state = session_get_state(i); - if (state == SESSION_ACTIVE) - return USER_ACTIVE; - if (state != SESSION_CLOSING) - all_closing = false; - } + state = session_get_state(i); + if (state == SESSION_ACTIVE) + return USER_ACTIVE; + if (state != SESSION_CLOSING) + all_closing = false; - return all_closing ? USER_CLOSING : USER_ONLINE; + any = true; } + if (any) + return all_closing ? USER_CLOSING : USER_ONLINE; + if (user_check_linger_file(u) > 0 && user_unit_active(u)) return USER_LINGERING; @@ -748,7 +857,7 @@ static bool elect_display_filter(Session *s) { /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */ assert(s); - return IN_SET(s->class, SESSION_USER, SESSION_GREETER) && s->started && !s->stopping; + return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping; } static int elect_display_compare(Session *s1, Session *s2) { @@ -780,6 +889,9 @@ static int elect_display_compare(Session *s1, Session *s2) { if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER)) return (s1->class != SESSION_USER) - (s2->class != SESSION_USER); + if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY)) + return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY); + if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID)) return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID); @@ -823,7 +935,7 @@ void user_update_last_session_timer(User *u) { assert(u); - if (u->sessions) { + if (user_pinned_by_sessions(u)) { /* There are sessions, turn off the timer */ u->last_session_timestamp = USEC_INFINITY; u->timer_event_source = sd_event_source_unref(u->timer_event_source); @@ -871,6 +983,13 @@ static const char* const user_state_table[_USER_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); +static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = { + [USER_GC_BY_PIN] = "pin", + [USER_GC_BY_ANY] = "any", +}; + +DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode); + int config_parse_tmpfs_size( const char* unit, const char *filename, |