Merge pull request #30884 from poettering/logind-background-light

logind: add "background-light" + "manager" session classes
This commit is contained in:
Lennart Poettering 2024-01-11 21:20:01 +01:00 committed by GitHub
commit 25f8d3856a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 341 additions and 110 deletions

View file

@ -90,11 +90,58 @@
<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>

View file

@ -214,14 +214,13 @@
<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

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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);

View file

@ -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 users 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,

View file

@ -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);

View file

@ -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))

View file

@ -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" <<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