summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/logind.conf.xml11
-rw-r--r--src/login/logind-core.c2
-rw-r--r--src/login/logind-dbus.c1
-rw-r--r--src/login/logind-gperf.gperf1
-rw-r--r--src/login/logind-session.c4
-rw-r--r--src/login/logind-user.c90
-rw-r--r--src/login/logind-user.h7
-rw-r--r--src/login/logind.h1
8 files changed, 109 insertions, 8 deletions
diff --git a/man/logind.conf.xml b/man/logind.conf.xml
index abc97b356..f5ad3488a 100644
--- a/man/logind.conf.xml
+++ b/man/logind.conf.xml
@@ -226,6 +226,17 @@
</varlistentry>
<varlistentry>
+ <term><varname>UserStopDelaySec=</varname></term>
+
+ <listitem><para>Specifies how long to keep the user record and per-user service
+ <filename>user@.service</filename> around for a user after they logged out fully. If set to zero, the per-user
+ service is terminated immediately when the last session of the user has ended. If this option is configured to
+ non-zero rapid logout/login cycles are sped up, as the user's service manager is not constantly restarted. If
+ set to <literal>infinity</literal> the per-user service for a user is never terminated again after first login,
+ and continues to run until system shutdown. Defaults to 10s.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>HandlePowerKey=</varname></term>
<term><varname>HandleSuspendKey=</varname></term>
<term><varname>HandleHibernateKey=</varname></term>
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
index 7e096277b..c75c96c01 100644
--- a/src/login/logind-core.c
+++ b/src/login/logind-core.c
@@ -29,6 +29,8 @@ void manager_reset_config(Manager *m) {
#endif // 0
m->remove_ipc = true;
m->inhibit_delay_max = 5 * USEC_PER_SEC;
+ m->user_stop_delay = 10 * USEC_PER_SEC;
+
m->handle_power_key = HANDLE_POWEROFF;
m->handle_suspend_key = HANDLE_SUSPEND;
m->handle_hibernate_key = HANDLE_HIBERNATE;
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index 3d31ef252..333eb5a8c 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -2820,6 +2820,7 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("BlockInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandlePowerKey", "s", property_get_handle_action, offsetof(Manager, handle_power_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleSuspendKey", "s", property_get_handle_action, offsetof(Manager, handle_suspend_key), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HandleHibernateKey", "s", property_get_handle_action, offsetof(Manager, handle_hibernate_key), SD_BUS_VTABLE_PROPERTY_CONST),
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
index c8523fbdf..fe593d5b4 100644
--- a/src/login/logind-gperf.gperf
+++ b/src/login/logind-gperf.gperf
@@ -34,6 +34,7 @@ Login.KillUserProcesses, config_parse_bool, 0, offse
Login.KillOnlyUsers, config_parse_strv, 0, offsetof(Manager, kill_only_users)
Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users)
Login.InhibitDelayMaxSec, config_parse_sec, 0, offsetof(Manager, inhibit_delay_max)
+Login.UserStopDelaySec, config_parse_sec, 0, offsetof(Manager, user_stop_delay)
Login.HandlePowerKey, config_parse_handle_action, 0, offsetof(Manager, handle_power_key)
Login.HandleSuspendKey, config_parse_handle_action, 0, offsetof(Manager, handle_suspend_key)
Login.HandleHibernateKey, config_parse_handle_action, 0, offsetof(Manager, handle_hibernate_key)
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
index 16d69defa..8c478cb3e 100644
--- a/src/login/logind-session.c
+++ b/src/login/logind-session.c
@@ -105,6 +105,8 @@ Session* session_free(Session *s) {
if (s->user->display == s)
s->user->display = NULL;
+
+ user_update_last_session_timer(s->user);
}
if (s->seat) {
@@ -148,6 +150,8 @@ void session_set_user(Session *s, User *u) {
s->user = u;
LIST_PREPEND(sessions_by_user, u->sessions, s);
+
+ user_update_last_session_timer(u);
}
static void session_save_devices(Session *s, FILE *f) {
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
index 3d277d711..1e9048600 100644
--- a/src/login/logind-user.c
+++ b/src/login/logind-user.c
@@ -49,6 +49,7 @@ int user_new(User **ret, Manager *m, uid_t uid, gid_t gid, const char *name) {
.manager = m,
.uid = uid,
.gid = gid,
+ .last_session_timestamp = USEC_INFINITY,
};
u->name = strdup(name);
@@ -115,6 +116,8 @@ User *user_free(User *u) {
hashmap_remove_value(u->manager->users, UID_TO_PTR(u->uid), u);
+ (void) sd_event_source_unref(u->timer_event_source);
+
#if 0 /// elogind neither supports slice nor service jobs.
u->service_job = mfree(u->service_job);
#endif // 0
@@ -176,6 +179,10 @@ static int user_save_internal(User *u) {
u->timestamp.realtime,
u->timestamp.monotonic);
+ if (u->last_session_timestamp != USEC_INFINITY)
+ fprintf(f, "LAST_SESSION_TIMESTAMP=" USEC_FMT "\n",
+ u->last_session_timestamp);
+
if (u->sessions) {
Session *i;
bool first;
@@ -293,18 +300,19 @@ int user_save(User *u) {
}
int user_load(User *u) {
- _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL;
+ _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL;
int r;
assert(u);
r = parse_env_file(NULL, u->state_file, NEWLINE,
#if 0 /// elogind neither supports service nor slice jobs
- "SERVICE_JOB", &u->service_job,
#endif // 0
- "STOPPING", &stopping,
- "REALTIME", &realtime,
- "MONOTONIC", &monotonic,
+ "SERVICE_JOB", &u->service_job,
+ "STOPPING", &stopping,
+ "REALTIME", &realtime,
+ "MONOTONIC", &monotonic,
+ "LAST_SESSION_TIMESTAMP", &last_session_timestamp,
NULL);
if (r == -ENOENT)
return 0;
@@ -320,9 +328,11 @@ int user_load(User *u) {
}
if (realtime)
- timestamp_deserialize(realtime, &u->timestamp.realtime);
+ (void) timestamp_deserialize(realtime, &u->timestamp.realtime);
if (monotonic)
- timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ (void) timestamp_deserialize(monotonic, &u->timestamp.monotonic);
+ if (last_session_timestamp)
+ (void) timestamp_deserialize(last_session_timestamp, &u->last_session_timestamp);
return 0;
}
@@ -339,6 +349,7 @@ static void user_start_service(User *u) {
assert(u);
/* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly
* start the per-user slice or the elogind-runtime-dir@.service instance, as those are pulled in both by
+ * start the per-user slice or the elogind-runtime-dir@.service instance, as those are pulled in both by
* user@.service and the session scopes as dependencies. */
hashmap_put(u->manager->user_units, u->service, u);
@@ -378,6 +389,7 @@ int user_start(User *u) {
* "officially" started yet. */
/* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up
* elogind --user. We need to do user_save_internal() because we have not "officially" started yet. */
+ * elogind --user. We need to do user_save_internal() because we have not "officially" started yet. */
user_save_internal(u);
/* Spawn user systemd */
@@ -600,6 +612,17 @@ bool user_may_gc(User *u, bool drop_not_started) {
if (u->sessions)
return false;
+ if (u->last_session_timestamp != USEC_INFINITY) {
+ /* All sessions have been closed. Let's see if we shall leave the user record around for a bit */
+
+ if (u->manager->user_stop_delay == USEC_INFINITY)
+ return false; /* Leave it around forever! */
+ if (u->manager->user_stop_delay > 0 &&
+ now(CLOCK_MONOTONIC) < usec_add(u->last_session_timestamp, u->manager->user_stop_delay))
+ return false; /* Leave it around for a bit longer. */
+ }
+
+ /* Is this a user that shall stay around forever? */
if (user_check_linger_file(u) > 0)
return false;
@@ -757,6 +780,59 @@ void user_elect_display(User *u) {
}
}
+static int user_stop_timeout_callback(sd_event_source *es, uint64_t usec, void *userdata) {
+ User *u = userdata;
+
+ assert(u);
+ user_add_to_gc_queue(u);
+
+ return 0;
+}
+
+void user_update_last_session_timer(User *u) {
+ int r;
+
+ assert(u);
+
+ if (u->sessions) {
+ /* 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);
+ return;
+ }
+
+ if (u->last_session_timestamp != USEC_INFINITY)
+ return; /* Timer already started */
+
+ u->last_session_timestamp = now(CLOCK_MONOTONIC);
+
+ assert(!u->timer_event_source);
+
+ if (u->manager->user_stop_delay == 0 || u->manager->user_stop_delay == USEC_INFINITY)
+ return;
+
+ if (sd_event_get_state(u->manager->event) == SD_EVENT_FINISHED) {
+ log_debug("Not allocating user stop timeout, since we are already exiting.");
+ return;
+ }
+
+ r = sd_event_add_time(u->manager->event,
+ &u->timer_event_source,
+ CLOCK_MONOTONIC,
+ usec_add(u->last_session_timestamp, u->manager->user_stop_delay), 0,
+ user_stop_timeout_callback, u);
+ if (r < 0)
+ log_warning_errno(r, "Failed to enqueue user stop event source, ignoring: %m");
+
+ if (DEBUG_LOGGING) {
+ char s[FORMAT_TIMESPAN_MAX];
+
+ log_debug("Last session of user '%s' logged out, terminating user context in %s.",
+ u->name,
+ format_timespan(s, sizeof(s), u->manager->user_stop_delay, USEC_PER_MSEC));
+ }
+}
+
static const char* const user_state_table[_USER_STATE_MAX] = {
[USER_OFFLINE] = "offline",
[USER_OPENING] = "opening",
diff --git a/src/login/logind-user.h b/src/login/logind-user.h
index f8d40b409..a1a8c632d 100644
--- a/src/login/logind-user.h
+++ b/src/login/logind-user.h
@@ -36,7 +36,11 @@ struct User {
Session *display;
- dual_timestamp timestamp;
+ dual_timestamp timestamp; /* When this User object was 'started' the first time */
+ usec_t last_session_timestamp; /* When the number of sessions of this user went from 1 to 0 the last time */
+
+ /* Set up when the last session of the user logs out */
+ sd_event_source *timer_event_source;
bool in_gc_queue:1;
@@ -64,6 +68,7 @@ int user_load(User *u);
int user_kill(User *u, int signo);
int user_check_linger_file(User *u);
void user_elect_display(User *u);
+void user_update_last_session_timer(User *u);
extern const sd_bus_vtable user_vtable[];
int user_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
diff --git a/src/login/logind.h b/src/login/logind.h
index e11d3327d..5b9e22b9b 100644
--- a/src/login/logind.h
+++ b/src/login/logind.h
@@ -93,6 +93,7 @@ struct Manager {
Hashmap *user_units;
usec_t inhibit_delay_max;
+ usec_t user_stop_delay;
/* If an action is currently being executed or is delayed,
* this is != 0 and encodes what is being done */