mirror of
https://github.com/systemd/systemd
synced 2024-07-21 18:24:38 +00:00
Merge pull request #30884 from poettering/logind-background-light
logind: add "background-light" + "manager" session classes
This commit is contained in:
commit
25f8d3856a
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue