diff --git a/man/pam_systemd.xml b/man/pam_systemd.xml
index c9a3ccbb5f..2c3bbec5d8 100644
--- a/man/pam_systemd.xml
+++ b/man/pam_systemd.xml
@@ -90,11 +90,58 @@
class=
- Takes a string argument which sets the session class. The XDG_SESSION_CLASS
- environment variable (see below) takes precedence. One of user, greeter,
- lock-screen or background. See
- sd_session_get_class3 for
- details about the session class.
+ Takes a string argument which sets the session class. The
+ XDG_SESSION_CLASS environment variable (see below) takes precedence. See
+ sd_session_get_class3
+ for a way to query the class of a session. The following session classes are defined:
+
+
+ Session Classes
+
+
+
+
+
+ Name
+ Explanation
+
+
+
+
+ user
+ A regular interactive user session. This is the default class for sessions for which a TTY or X display is known at session registration time.
+
+
+ user-early
+ Similar to user but sessions of this class are not ordered after systemd-user-sessions.service, i.e. may be started before regular sessions are allowed to be established. This session class is the default for sessions of the root user that would otherwise qualify for the user class, see above. (Added in v256.)
+
+
+ greeter
+ Similar to user but for sessions that are spawned by a display manager ephemerally and which prompt the user for login credentials.
+
+
+ lock-screen
+ Similar to user but for sessions that are spawned by a display manager ephemerally and which show a lock screen that can be used to unlock locked user accounts or sessions.
+
+
+ background
+ Used for background sessions, such as those invoked by cron and similar tools. This is the default class for sessions for which no TTY or X display is known at session registration time.
+
+
+ background-light
+ Similar to background, but sessions of this class will not pull in the user@.service of the user, and thus possibly have no services of the user running. (Added in v256.)
+
+
+ manager
+ The user@.service service of the user is registered under this session class. (Added in v256.)
+
+
+ manager-early
+ Similar to manager, but for the root user. Compare with the user vs. user-early situation. (Added in v256.)
+
+
+
+
diff --git a/man/sd_session_is_active.xml b/man/sd_session_is_active.xml
index e69ef59fd0..747fab4c68 100644
--- a/man/sd_session_is_active.xml
+++ b/man/sd_session_is_active.xml
@@ -214,14 +214,13 @@
free3
call after use.
- sd_session_get_class() may be used to
- determine the class of the session identified by the specified
- session identifier. The returned string is one of
- user, greeter,
- lock-screen, or background
- and needs to be freed with the libc
- free3
- call after use.
+ sd_session_get_class() may be used to determine the class of the session
+ identified by the specified session identifier. The returned string is one of user,
+ user-early, greeter, lock-screen,
+ background, background-light, manager or
+ manager-early and needs to be freed with the libc free3 call after
+ use.
sd_session_get_desktop() may be used to
determine the brand of the desktop running on the session
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index 26133ee6ef..582cbcbea8 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -413,6 +413,9 @@ int manager_get_idle_hint(Manager *m, 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;
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index 4f8525b46a..99b9da6ba1 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -865,25 +865,19 @@ static int create_session(
c = SESSION_USER;
}
- /* Check if we are already in a logind session. Or if we are in user@.service
- * which is a special PAM session that avoids creating a logind session. */
- r = manager_get_user_by_pid(m, leader.pid, NULL);
+ /* Check if we are already in a logind session, and if so refuse. */
+ r = manager_get_session_by_pidref(m, &leader, /* ret_session= */ NULL);
if (r < 0)
return r;
if (r > 0)
return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY,
"Already running in a session or user slice");
- /*
- * Old gdm and lightdm start the user-session on the same VT as
- * the greeter session. But they destroy the greeter session
- * after the user-session and want the user-session to take
- * over the VT. We need to support this for
- * backwards-compatibility, so make sure we allow new sessions
- * on a VT that a greeter is running on. Furthermore, to allow
- * re-logins, we have to allow a greeter to take over a used VT for
- * the exact same reasons.
- */
+ /* Old gdm and lightdm start the user-session on the same VT as the greeter session. But they destroy
+ * the greeter session after the user-session and want the user-session to take over the VT. We need
+ * to support this for backwards-compatibility, so make sure we allow new sessions on a VT that a
+ * greeter is running on. Furthermore, to allow re-logins, we have to allow a greeter to take over a
+ * used VT for the exact same reasons. */
if (c != SESSION_GREETER &&
vtnr > 0 &&
vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) &&
@@ -943,9 +937,17 @@ static int create_session(
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);
@@ -1017,8 +1019,14 @@ static int create_session(
session->create_message = sd_bus_message_ref(message);
- /* Now, let's wait until the slice unit and stuff got created. We send the reply back from
- * session_send_create_reply(). */
+ /* Now call into session_send_create_reply(), which will reply to this method call for us. Or it
+ * won't – in case we just spawned a session scope and/or user service manager, and they aren't ready
+ * yet. We'll call session_create_reply() again once the session scope or the user service manager is
+ * ready, where the function will check again if a reply is then ready to be sent, and then do so if
+ * all is complete - or wait again. */
+ r = session_send_create_reply(session, /* error= */ NULL);
+ if (r < 0)
+ return r;
return 1;
diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c
index 15f04c4a66..7217b81476 100644
--- a/src/login/logind-session-dbus.c
+++ b/src/login/logind-session-dbus.c
@@ -216,7 +216,9 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro
if (r == 0)
return 1; /* Will call us back */
- r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock"));
+ r = session_send_lock(s, /* lock= */ strstr(sd_bus_message_get_member(message), "Lock"));
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
if (r < 0)
return r;
@@ -248,7 +250,7 @@ static int method_set_idle_hint(sd_bus_message *message, void *userdata, sd_bus_
r = session_set_idle_hint(s, b);
if (r == -ENOTTY)
- return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical sessions.");
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical and non-user sessions.");
if (r < 0)
return r;
@@ -278,7 +280,11 @@ static int method_set_locked_hint(sd_bus_message *message, void *userdata, sd_bu
if (uid != 0 && uid != s->user->user_record->uid)
return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint");
- session_set_locked_hint(s, b);
+ r = session_set_locked_hint(s, b);
+ if (r == -ENOTTY)
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen.");
+ if (r < 0)
+ return r;
return sd_bus_reply_method_return(message, NULL);
}
@@ -387,6 +393,9 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid session type '%s'", t);
+ if (!SESSION_CLASS_CAN_CHANGE_TYPE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support changing type.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You must be in control of this session to set type");
@@ -470,6 +479,9 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er
if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor))
return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid.");
+ if (!SESSION_CLASS_CAN_TAKE_DEVICE(s->class))
+ return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support taking device control.");
+
if (!session_is_controller(s, sd_bus_message_get_sender(message)))
return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session");
@@ -765,6 +777,9 @@ int session_send_lock(Session *s, bool lock) {
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
p = session_bus_path(s);
if (!p)
return -ENOMEM;
@@ -786,6 +801,9 @@ int session_send_lock_all(Manager *m, bool lock) {
HASHMAP_FOREACH(session, m->sessions) {
int k;
+ if (!SESSION_CLASS_CAN_LOCK(session->class))
+ continue;
+
k = session_send_lock(session, lock);
if (k < 0)
r = k;
@@ -800,7 +818,7 @@ static bool session_ready(Session *s) {
/* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */
return !s->scope_job &&
- !s->user->service_job;
+ (!SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) || !s->user->service_job);
}
int session_send_create_reply(Session *s, sd_bus_error *error) {
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index c60811f72e..69dc52ad66 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -722,8 +722,11 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
assert(s);
assert(s->user);
+ if (!SESSION_CLASS_WANTS_SCOPE(s->class))
+ return 0;
+
if (!s->scope) {
- _cleanup_strv_free_ char **after = NULL;
+ _cleanup_strv_free_ char **wants = NULL, **after = NULL;
_cleanup_free_ char *scope = NULL;
const char *description;
@@ -735,6 +738,12 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name);
+ /* These two have StopWhenUnneeded= set, hence add a dep towards them */
+ wants = strv_new(s->user->runtime_dir_service,
+ SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service : STRV_IGNORE);
+ if (!wants)
+ return log_oom();
+
/* We usually want to order session scopes after systemd-user-sessions.service since the
* latter unit is used as login session barrier for unprivileged users. However the barrier
* doesn't apply for root as sysadmin should always be able to log in (and without waiting
@@ -754,9 +763,7 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
&s->leader,
s->user->slice,
description,
- /* These two have StopWhenUnneeded= set, hence add a dep towards them */
- STRV_MAKE(s->user->runtime_dir_service,
- s->user->service),
+ wants,
after,
user_record_home_directory(s->user->user_record),
properties,
@@ -810,7 +817,7 @@ static int session_setup_stop_on_idle_timer(Session *s) {
assert(s);
- if (s->manager->stop_idle_session_usec == USEC_INFINITY || IN_SET(s->class, SESSION_GREETER, SESSION_LOCK_SCREEN))
+ if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class))
return 0;
r = sd_event_add_time_relative(
@@ -1148,7 +1155,9 @@ found_atime:
int session_set_idle_hint(Session *s, bool b) {
assert(s);
- if (!SESSION_TYPE_IS_GRAPHICAL(s->type))
+ if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */
+ return -ENOTTY;
+ if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */
return -ENOTTY;
if (s->idle_hint == b)
@@ -1174,15 +1183,20 @@ int session_get_locked_hint(Session *s) {
return s->locked_hint;
}
-void session_set_locked_hint(Session *s, bool b) {
+int session_set_locked_hint(Session *s, bool b) {
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
if (s->locked_hint == b)
- return;
+ return 0;
s->locked_hint = b;
+ (void) session_save(s);
+ (void) session_send_changed(s, "LockedHint", NULL);
- session_send_changed(s, "LockedHint", NULL);
+ return 1;
}
void session_set_type(Session *s, SessionType t) {
@@ -1632,11 +1646,14 @@ static const char* const session_type_table[_SESSION_TYPE_MAX] = {
DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType);
static const char* const session_class_table[_SESSION_CLASS_MAX] = {
- [SESSION_USER] = "user",
- [SESSION_USER_EARLY] = "user-early",
- [SESSION_GREETER] = "greeter",
- [SESSION_LOCK_SCREEN] = "lock-screen",
- [SESSION_BACKGROUND] = "background",
+ [SESSION_USER] = "user",
+ [SESSION_USER_EARLY] = "user-early",
+ [SESSION_GREETER] = "greeter",
+ [SESSION_LOCK_SCREEN] = "lock-screen",
+ [SESSION_BACKGROUND] = "background",
+ [SESSION_BACKGROUND_LIGHT] = "background-light",
+ [SESSION_MANAGER] = "manager",
+ [SESSION_MANAGER_EARLY] = "manager-early",
};
DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass);
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
index a9c44b465f..6a0fb54303 100644
--- a/src/login/logind-session.h
+++ b/src/login/logind-session.h
@@ -25,13 +25,43 @@ typedef enum SessionClass {
SESSION_GREETER, /* A login greeter pseudo-session */
SESSION_LOCK_SCREEN, /* A lock screen */
SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */
+ SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */
+ SESSION_MANAGER, /* The service manager */
+ SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */
_SESSION_CLASS_MAX,
_SESSION_CLASS_INVALID = -EINVAL,
} SessionClass;
-/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. For now,
- * there's only one class we allow this for. It's generally set for root sessions, but no one else. */
-#define SESSION_CLASS_IS_EARLY(class) ((class) == SESSION_USER_EARLY)
+/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's
+ * generally set for root sessions, but no one else. */
+#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY)
+
+/* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */
+#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT)
+
+/* Which session classes want their own per-user service manager? */
+#define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND)
+
+/* Which session classes can pin our user tracking? */
+#define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY))
+
+/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/
+#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which session classes have a lock screen concept? */
+#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which sessions are candidates to become "display" sessions */
+#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER))
+
+/* Which sessions classes should be subject to stop-in-idle */
+#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY))
+
+/* Which session classes can take control of devices */
+#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
+
+/* Which session classes allow changing session types */
+#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN))
typedef enum SessionType {
SESSION_UNSPECIFIED,
@@ -146,7 +176,7 @@ bool session_is_active(Session *s);
int session_get_idle_hint(Session *s, dual_timestamp *t);
int session_set_idle_hint(Session *s, bool b);
int session_get_locked_hint(Session *s);
-void session_set_locked_hint(Session *s, bool b);
+int session_set_locked_hint(Session *s, bool b);
void session_set_type(Session *s, SessionType t);
int session_set_display(Session *s, const char *display);
int session_set_tty(Session *s, const char *tty);
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 4868eb48fb..e6e57ad79e 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -63,6 +63,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)
@@ -162,10 +163,12 @@ 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)
@@ -302,7 +305,7 @@ 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);
@@ -312,7 +315,8 @@ int user_load(User *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)
@@ -333,10 +337,24 @@ 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 bool user_wants_service_manager(User *u) {
+ assert(u);
+
+ LIST_FOREACH(sessions_by_user, s, u->sessions)
+ if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class))
+ return true;
+
+ return false;
+}
+
+void user_start_service_manager(User *u) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
@@ -346,6 +364,12 @@ static void user_start_service(User *u) {
* 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. */
+ if (u->stopping) /* Don't try to start this if the user is going down */
+ return;
+
+ if (!user_wants_service_manager(u)) /* Only start user service manager if there's at least one session which wants it */
+ return;
+
u->service_job = mfree(u->service_job);
r = manager_start_unit(u->manager, u->service, &error, &u->service_job);
@@ -448,7 +472,7 @@ int user_start(User *u) {
u->stopping = false;
if (!u->started)
- log_debug("Starting services for new user %s.", u->user_record->user_name);
+ 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. */
@@ -458,7 +482,7 @@ int user_start(User *u) {
(void) user_update_slice(u);
/* Start user@UID.service */
- user_start_service(u);
+ user_start_service_manager(u);
if (!u->started) {
if (!dual_timestamp_is_set(&u->timestamp))
@@ -575,6 +599,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;
@@ -651,6 +678,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;
@@ -659,7 +709,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) {
@@ -718,22 +768,26 @@ UserState user_get_state(User *u) {
if (!u->started || u->service_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;
@@ -750,7 +804,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) {
@@ -828,7 +882,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);
@@ -876,6 +930,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,
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
index 21b9f8f348..9bda5dde42 100644
--- a/src/login/logind-user.h
+++ b/src/login/logind-user.h
@@ -19,6 +19,13 @@ typedef enum UserState {
_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;
@@ -41,6 +48,7 @@ struct User {
/* 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. */
@@ -57,6 +65,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free);
bool user_may_gc(User *u, bool drop_not_started);
void user_add_to_gc_queue(User *u);
+void user_start_service_manager(User *u);
int user_start(User *u);
int user_stop(User *u, bool force);
int user_finalize(User *u);
@@ -72,4 +81,7 @@ void user_update_last_session_timer(User *u);
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);
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index 297689203b..d7814a7275 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -936,30 +936,21 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (r != PAM_SUCCESS)
return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
- /* Make sure we don't enter a loop by talking to systemd-logind when it is actually waiting for the
- * background to finish start-up. If the service is "systemd-user" we simply set XDG_RUNTIME_DIR and
- * leave. */
-
- if (streq_ptr(service, "systemd-user")) {
- char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
-
- xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
- r = configure_runtime_directory(handle, ur, rt);
- if (r != PAM_SUCCESS)
- return r;
-
- goto success;
- }
-
- /* Otherwise, we ask logind to create a session for us */
-
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
- if (tty && strchr(tty, ':')) {
+ if (streq_ptr(service, "systemd-user")) {
+ /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
+ * 'manager' if not set, simply for robustness reasons. */
+ type = "unspecified";
+ class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
+ "manager-early" : "manager";
+ tty = NULL;
+
+ } else if (tty && strchr(tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
diff --git a/test/units/testsuite-35.sh b/test/units/testsuite-35.sh
index 8ea801ee96..5c230c0a45 100755
--- a/test/units/testsuite-35.sh
+++ b/test/units/testsuite-35.sh
@@ -258,7 +258,7 @@ cleanup_session() (
systemctl stop getty@tty2.service
- for s in $(loginctl --no-legend list-sessions | awk '$3 == "logind-test-user" { print $1 }'); do
+ for s in $(loginctl --no-legend list-sessions | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do
echo "INFO: stopping session $s"
loginctl terminate-session "$s"
done
@@ -308,18 +308,18 @@ check_session() (
local seat session leader_pid
- if [[ $(loginctl --no-legend | grep -c "logind-test-user") != 1 ]]; then
+ if [[ $(loginctl --no-legend | grep tty | grep -c "logind-test-user") != 1 ]]; then
echo "no session or multiple sessions for logind-test-user." >&2
return 1
fi
- seat=$(loginctl --no-legend | grep 'logind-test-user *seat' | awk '{ print $4 }')
+ seat=$(loginctl --no-legend | grep tty | grep 'logind-test-user *seat' | awk '{ print $4 }')
if [[ -z "$seat" ]]; then
echo "no seat found for user logind-test-user" >&2
return 1
fi
- session=$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ session=$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
if [[ -z "$session" ]]; then
echo "no session found for user logind-test-user" >&2
return 1
@@ -364,7 +364,7 @@ EOF
check_session && break
done
check_session
- assert_eq "$(loginctl --no-legend | awk '$3=="logind-test-user" { print $5 }')" "tty2"
+ assert_eq "$(loginctl --no-legend | grep tty | awk '$3=="logind-test-user" { print $5 }')" "tty2"
}
testcase_sanity_check() {
@@ -455,7 +455,7 @@ EOF
udevadm info "$dev"
# trigger logind and activate session
- loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')"
+ loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')"
# check ACL
sleep 1
@@ -496,7 +496,7 @@ testcase_lock_idle_action() {
return
fi
- if loginctl --no-legend | grep -q logind-test-user; then
+ if loginctl --no-legend | grep tty | grep -q logind-test-user; then
echo >&2 "Session of the 'logind-test-user' is already present."
exit 1
fi
@@ -545,7 +545,7 @@ testcase_session_properties() {
trap cleanup_session RETURN
create_session
- s=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ s=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
/usr/lib/systemd/tests/unit-tests/manual/test-session-properties "/org/freedesktop/login1/session/_3${s?}" /dev/tty2
}
@@ -561,17 +561,17 @@ testcase_list_users_sessions_seats() {
create_session
# Activate the session
- loginctl activate "$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1 }')"
+ loginctl activate "$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')"
- session=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }')
+ session=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }')
: check that we got a valid session id
busctl get-property org.freedesktop.login1 "/org/freedesktop/login1/session/_3${session?}" org.freedesktop.login1.Session Id
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
- seat=$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $4 }')
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $5 }')" tty2
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $6 }')" active
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $7 }')" no
- assert_eq "$(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $8 }')" '-'
+ assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $2 }')" "$(id -ru logind-test-user)"
+ seat=$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $4 }')
+ assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $5 }')" tty2
+ assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $6 }')" active
+ assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $7 }')" no
+ assert_eq "$(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $8 }')" '-'
loginctl list-seats --no-legend | grep -Fwq "${seat?}"
@@ -582,10 +582,10 @@ testcase_list_users_sessions_seats() {
loginctl enable-linger logind-test-user
assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes
- for s in $(loginctl list-sessions --no-legend | awk '$3 == "logind-test-user" { print $1 }'); do
+ for s in $(loginctl list-sessions --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1 }'); do
loginctl terminate-session "$s"
done
- if ! timeout 30 bash -c "while loginctl --no-legend | grep -q logind-test-user; do sleep 1; done"; then
+ if ! timeout 30 bash -c "while loginctl --no-legend | grep tty | grep -q logind-test-user; do sleep 1; done"; then
echo "WARNING: session for logind-test-user still active, ignoring."
return
fi
@@ -613,7 +613,7 @@ testcase_stop_idle_session() {
create_session
trap teardown_stop_idle_session RETURN
- id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')"
+ id="$(loginctl --no-legend | grep tty | awk '$3 == "logind-test-user" { print $1; }')"
ts="$(date '+%H:%M:%S')"
mkdir -p /run/systemd/logind.conf.d
@@ -625,7 +625,7 @@ EOF
sleep 5
assert_eq "$(journalctl -b -u systemd-logind.service --since="$ts" --grep "Session \"$id\" of user \"logind-test-user\" is idle, stopping." | wc -l)" 1
- assert_eq "$(loginctl --no-legend | grep -c "logind-test-user")" 0
+ assert_eq "$(loginctl --no-legend | grep tty | grep -c "logind-test-user")" 0
}
testcase_ambient_caps() {
@@ -680,6 +680,51 @@ EOF
rm -f "$SCRIPT" "$PAMSERVICE"
}
+background_at_return() {
+ rm -f /etc/pam.d/"$PAMSERVICE"
+ unset PAMSERVICE
+}
+
+testcase_background() {
+
+ local uid TRANSIENTUNIT1 TRANSIENTUNIT2
+
+ uid=$(id -u logind-test-user)
+
+ systemctl stop user@"$uid".service
+
+ PAMSERVICE="pamserv$RANDOM"
+ TRANSIENTUNIT1="bg$RANDOM.service"
+ TRANSIENTUNIT2="bgg$RANDOM.service"
+
+ trap background_at_return RETURN
+
+ cat > /etc/pam.d/"$PAMSERVICE" <