goto fail;
session->original_type = session->type = t;
- session->class = c;
session->remote = remote;
session->vtnr = vtnr;
+ session->class = c;
+
+ /* Once the first session that is of a pinning class shows up we'll change the GC mode for the user
+ * from USER_GC_BY_ANY to USER_GC_BY_PIN, so that the user goes away once the last pinning session
+ * goes away. Background: we want that user@.service – when started manually – remains around (which
+ * itself is a non-pinning session), but gets stopped when the last pinning session goes away. */
+
+ if (SESSION_CLASS_PIN_USER(c))
+ user->gc_mode = USER_GC_BY_PIN;
if (!isempty(tty)) {
session->tty = strdup(tty);
.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)
"# 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)
}
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);
"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)
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;
}
/* 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. */
- LIST_FOREACH(sessions_by_user, i, u->sessions)
- if (SESSION_CLASS_PIN_USER(i->class))
- return true;
+ switch (u->gc_mode) {
- return false;
+ 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) {
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,
_USER_STATE_INVALID = -EINVAL,
} UserState;
+typedef enum UserGCMode {
+ USER_GC_BY_ANY, /* any session pins this user */
+ USER_GC_BY_PIN, /* only sessions with an explicitly pinning class pin this user */
+ _USER_GC_MODE_MAX,
+ _USER_GC_MODE_INVALID = -EINVAL,
+} UserGCMode;
+
struct User {
Manager *manager;
/* Set up when the last session of the user logs out */
sd_event_source *timer_event_source;
+ UserGCMode gc_mode;
bool in_gc_queue:1;
bool started:1; /* Whenever the user being started, has been started or is being stopped again. */
const char* user_state_to_string(UserState s) _const_;
UserState user_state_from_string(const char *s) _pure_;
+const char* user_gc_mode_to_string(UserGCMode m) _const_;
+UserGCMode user_gc_mode_from_string(const char *s) _pure_;
+
CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max);