<varlistentry>
<term><varname>class=</varname></term>
- <listitem><para>Takes a string argument which sets the session class. The <varname>XDG_SESSION_CLASS</varname>
- environment variable (see below) takes precedence. One of <literal>user</literal>, <literal>greeter</literal>,
- <literal>lock-screen</literal> or <literal>background</literal>. See
- <citerefentry><refentrytitle>sd_session_get_class</refentrytitle><manvolnum>3</manvolnum></citerefentry> for
- details about the session class.</para>
+ <listitem><para>Takes a string argument which sets the session class. The
+ <varname>XDG_SESSION_CLASS</varname> environment variable (see below) takes precedence. See
+ <citerefentry><refentrytitle>sd_session_get_class</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+ for a way to query the class of a session. The following session classes are defined:</para>
+
+ <table>
+ <title>Session Classes</title>
+ <tgroup cols='2' align='left' colsep='1' rowsep='1'>
+ <colspec colname="name" />
+ <colspec colname="explanation" />
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Explanation</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><constant>user</constant></entry>
+ <entry>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.</entry>
+ </row>
+ <row>
+ <entry><constant>user-early</constant></entry>
+ <entry>Similar to <literal>user</literal> but sessions of this class are not ordered after <filename>systemd-user-sessions.service</filename>, 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 <constant>user</constant> class, see above. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>greeter</constant></entry>
+ <entry>Similar to <literal>user</literal> but for sessions that are spawned by a display manager ephemerally and which prompt the user for login credentials.</entry>
+ </row>
+ <row>
+ <entry><constant>lock-screen</constant></entry>
+ <entry>Similar to <literal>user</literal> 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.</entry>
+ </row>
+ <row>
+ <entry><constant>background</constant></entry>
+ <entry>Used for background sessions, such as those invoked by <command>cron</command> and similar tools. This is the default class for sessions for which no TTY or X display is known at session registration time.</entry>
+ </row>
+ <row>
+ <entry><constant>background-light</constant></entry>
+ <entry>Similar to <constant>background</constant>, but sessions of this class will not pull in the <filename>user@.service</filename> of the user, and thus possibly have no services of the user running. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>manager</constant></entry>
+ <entry>The <filename>user@.service</filename> service of the user is registered under this session class. (Added in v256.)</entry>
+ </row>
+ <row>
+ <entry><constant>manager-early</constant></entry>
+ <entry>Similar to <constant>manager</constant>, but for the root user. Compare with the <constant>user</constant> vs. <constant>user-early</constant> situation. (Added in v256.)</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
<xi:include href="version-info.xml" xpointer="v197"/></listitem>
</varlistentry>
<citerefentry project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
call after use.</para>
- <para><function>sd_session_get_class()</function> may be used to
- determine the class of the session identified by the specified
- session identifier. The returned string is one of
- <literal>user</literal>, <literal>greeter</literal>,
- <literal>lock-screen</literal>, or <literal>background</literal>
- and needs to be freed with the libc
- <citerefentry project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry>
- call after use.</para>
+ <para><function>sd_session_get_class()</function> may be used to determine the class of the session
+ identified by the specified session identifier. The returned string is one of <literal>user</literal>,
+ <literal>user-early</literal>, <literal>greeter</literal>, <literal>lock-screen</literal>,
+ <literal>background</literal>, <literal>background-light</literal>, <literal>manager</literal> or
+ <literal>manager-early</literal> and needs to be freed with the libc <citerefentry
+ project='man-pages'><refentrytitle>free</refentrytitle><manvolnum>3</manvolnum></citerefentry> call after
+ use.</para>
<para><function>sd_session_get_desktop()</function> may be used to
determine the brand of the desktop running on the session
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;
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) &&
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);
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;
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;
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;
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);
}
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");
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");
assert(s);
+ if (!SESSION_CLASS_CAN_LOCK(s->class))
+ return -ENOTTY;
+
p = session_bus_path(s);
if (!p)
return -ENOMEM;
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;
/* 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) {
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;
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
&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,
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(
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)
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) {
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);
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,
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);
.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;
}
-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;
* 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);
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. */
(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))
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;
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;
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) {
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;
/* 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) {
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);
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. */
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);
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);
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))
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
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
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() {
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
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
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
}
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?}"
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
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
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() {
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" <<EOF
+auth sufficient pam_unix.so
+auth required pam_deny.so
+account sufficient pam_unix.so
+account required pam_permit.so
+session optional pam_systemd.so debug
+session required pam_unix.so
+EOF
+
+ systemd-run -u "$TRANSIENTUNIT1" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_CLASS=background-light" -p Type=exec -p User=logind-test-user sleep infinity
+
+ # This was a 'light' background service, hence the service manager should not be running
+ (! systemctl is-active user@"$uid".service )
+
+ systemctl stop "$TRANSIENTUNIT1"
+
+ systemd-run -u "$TRANSIENTUNIT2" -p PAMName="$PAMSERVICE" -p "Environment=XDG_SESSION_CLASS=background" -p Type=exec -p User=logind-test-user sleep infinity
+
+ # This was a regular background service, hence the service manager should be running
+ systemctl is-active user@"$uid".service
+
+ systemctl stop "$TRANSIENTUNIT2"
+
+ systemctl stop user@"$uid".service
+}
+
setup_test_user
test_write_dropin
run_testcases