b11b5f
From 24439b08e3a3437b423553c385cde1d4cddf18f6 Mon Sep 17 00:00:00 2001
b11b5f
From: Michal Sekletar <msekleta@redhat.com>
b11b5f
Date: Mon, 8 Aug 2022 09:13:50 +0200
b11b5f
Subject: [PATCH] logind: add option to stop idle sessions after specified
b11b5f
 timeout
b11b5f
b11b5f
Thanks to Jan Pazdziora <jpazdziora@redhat.com> for providing a patch
b11b5f
which implemeted a PoC of this feature.
b11b5f
b11b5f
(cherry picked from commit 82325af3ae41bc7efb3d5cd8f56a4652fef498c2)
b11b5f
b11b5f
Resolves: #2122288
b11b5f
---
b11b5f
 man/logind.conf.xml          | 11 ++++++
b11b5f
 src/login/logind-core.c      |  2 +
b11b5f
 src/login/logind-dbus.c      |  1 +
b11b5f
 src/login/logind-gperf.gperf |  1 +
b11b5f
 src/login/logind-session.c   | 72 +++++++++++++++++++++++++++++++++---
b11b5f
 src/login/logind-session.h   |  2 +
b11b5f
 src/login/logind.conf.in     |  1 +
b11b5f
 src/login/logind.h           |  2 +
b11b5f
 8 files changed, 86 insertions(+), 6 deletions(-)
b11b5f
b11b5f
diff --git a/man/logind.conf.xml b/man/logind.conf.xml
b11b5f
index 0cf8a7d1f2..00b5b1f2e8 100644
b11b5f
--- a/man/logind.conf.xml
b11b5f
+++ b/man/logind.conf.xml
b11b5f
@@ -333,6 +333,17 @@
b11b5f
         are excluded from the effect of this setting. Defaults to <literal>no</literal>.</para></listitem>
b11b5f
       </varlistentry>
b11b5f
 
b11b5f
+      <varlistentry>
b11b5f
+        <term><varname>StopIdleSessionSec=</varname></term>
b11b5f
+
b11b5f
+        <listitem><para>Specifies a timeout in seconds, or a time span value after which
b11b5f
+        <filename>systemd-logind</filename> checks the idle state of all sessions. Every session that is idle for
b11b5f
+        longer then the timeout will be stopped. Defaults to <literal>infinity</literal>
b11b5f
+        (<filename>systemd-logind</filename> is not checking the idle state of sessions). For details about the syntax
b11b5f
+        of time spans, see
b11b5f
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
b11b5f
+        </para></listitem>
b11b5f
+      </varlistentry>
b11b5f
     </variablelist>
b11b5f
   </refsect1>
b11b5f
 
b11b5f
diff --git a/src/login/logind-core.c b/src/login/logind-core.c
b11b5f
index a1943b6f9d..abe6eecffb 100644
b11b5f
--- a/src/login/logind-core.c
b11b5f
+++ b/src/login/logind-core.c
b11b5f
@@ -58,6 +58,8 @@ void manager_reset_config(Manager *m) {
b11b5f
 
b11b5f
         m->kill_only_users = strv_free(m->kill_only_users);
b11b5f
         m->kill_exclude_users = strv_free(m->kill_exclude_users);
b11b5f
+
b11b5f
+        m->stop_idle_session_usec = USEC_INFINITY;
b11b5f
 }
b11b5f
 
b11b5f
 int manager_parse_config_file(Manager *m) {
b11b5f
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
b11b5f
index 01bfef4ff7..81aacb4eed 100644
b11b5f
--- a/src/login/logind-dbus.c
b11b5f
+++ b/src/login/logind-dbus.c
b11b5f
@@ -2720,6 +2720,7 @@ const sd_bus_vtable manager_vtable[] = {
b11b5f
         SD_BUS_PROPERTY("SessionsMax", "t", NULL, offsetof(Manager, sessions_max), SD_BUS_VTABLE_PROPERTY_CONST),
b11b5f
         SD_BUS_PROPERTY("NCurrentSessions", "t", property_get_hashmap_size, offsetof(Manager, sessions), 0),
b11b5f
         SD_BUS_PROPERTY("UserTasksMax", "t", property_get_compat_user_tasks_max, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
b11b5f
+        SD_BUS_PROPERTY("StopIdleSessionUSec", "t", NULL, offsetof(Manager, stop_idle_session_usec), SD_BUS_VTABLE_PROPERTY_CONST),
b11b5f
 
b11b5f
         SD_BUS_METHOD_WITH_NAMES("GetSession",
b11b5f
                                  "s",
b11b5f
diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf
b11b5f
index 8829ce7d85..214ac5c4a3 100644
b11b5f
--- a/src/login/logind-gperf.gperf
b11b5f
+++ b/src/login/logind-gperf.gperf
b11b5f
@@ -42,3 +42,4 @@ Login.RemoveIPC,                    config_parse_bool,                  0, offse
b11b5f
 Login.InhibitorsMax,                config_parse_uint64,                0, offsetof(Manager, inhibitors_max)
b11b5f
 Login.SessionsMax,                  config_parse_uint64,                0, offsetof(Manager, sessions_max)
b11b5f
 Login.UserTasksMax,                 config_parse_compat_user_tasks_max, 0, offsetof(Manager, user_tasks_max)
b11b5f
+Login.StopIdleSessionSec,           config_parse_sec_fix_0,             0, offsetof(Manager, stop_idle_session_usec)
b11b5f
diff --git a/src/login/logind-session.c b/src/login/logind-session.c
b11b5f
index cc838ca383..56f40fbec4 100644
b11b5f
--- a/src/login/logind-session.c
b11b5f
+++ b/src/login/logind-session.c
b11b5f
@@ -29,6 +29,7 @@
b11b5f
 #include "string-table.h"
b11b5f
 #include "strv.h"
b11b5f
 #include "terminal-util.h"
b11b5f
+#include "time-util.h"
b11b5f
 #include "user-util.h"
b11b5f
 #include "util.h"
b11b5f
 
b11b5f
@@ -139,6 +140,8 @@ Session* session_free(Session *s) {
b11b5f
 
b11b5f
         free(s->state_file);
b11b5f
 
b11b5f
+        sd_event_source_unref(s->stop_on_idle_event_source);
b11b5f
+
b11b5f
         return mfree(s);
b11b5f
 }
b11b5f
 
b11b5f
@@ -658,6 +661,55 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
b11b5f
         return 0;
b11b5f
 }
b11b5f
 
b11b5f
+static int session_dispatch_stop_on_idle(sd_event_source *source, uint64_t t, void *userdata) {
b11b5f
+        Session *s = userdata;
b11b5f
+        dual_timestamp ts;
b11b5f
+        int r, idle;
b11b5f
+
b11b5f
+        assert(s);
b11b5f
+
b11b5f
+        if (s->stopping)
b11b5f
+                return 0;
b11b5f
+
b11b5f
+        idle = session_get_idle_hint(s, &ts);
b11b5f
+        if (idle) {
b11b5f
+                log_debug("Session \"%s\" of user \"%s\" is idle, stopping.", s->id, s->user->name);
b11b5f
+
b11b5f
+                return session_stop(s, /* force */ true);
b11b5f
+        }
b11b5f
+
b11b5f
+        r = sd_event_source_set_time(source, usec_add(ts.monotonic, s->manager->stop_idle_session_usec));
b11b5f
+        if (r < 0)
b11b5f
+                return log_error_errno(r, "Failed to configure stop on idle session event source: %m");
b11b5f
+
b11b5f
+        r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
b11b5f
+        if (r < 0)
b11b5f
+                return log_error_errno(r, "Failed to enable stop on idle session event source: %m");
b11b5f
+
b11b5f
+        return 1;
b11b5f
+}
b11b5f
+
b11b5f
+static int session_setup_stop_on_idle_timer(Session *s) {
b11b5f
+        int r;
b11b5f
+
b11b5f
+        assert(s);
b11b5f
+
b11b5f
+        if (s->manager->stop_idle_session_usec == USEC_INFINITY)
b11b5f
+                return 0;
b11b5f
+
b11b5f
+        r = sd_event_add_time_relative(
b11b5f
+                        s->manager->event,
b11b5f
+                        &s->stop_on_idle_event_source,
b11b5f
+                        CLOCK_MONOTONIC,
b11b5f
+                        s->manager->stop_idle_session_usec,
b11b5f
+                        0,
b11b5f
+                        session_dispatch_stop_on_idle, s);
b11b5f
+        if (r < 0)
b11b5f
+                return log_error_errno(r, "Failed to add stop on idle session event source: %m");
b11b5f
+
b11b5f
+        return 0;
b11b5f
+}
b11b5f
+
b11b5f
 int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
b11b5f
         int r;
b11b5f
 
b11b5f
@@ -680,6 +732,10 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
b11b5f
         if (r < 0)
b11b5f
                 return r;
b11b5f
 
b11b5f
+        r = session_setup_stop_on_idle_timer(s);
b11b5f
+        if (r < 0)
b11b5f
+                return r;
b11b5f
+
b11b5f
         log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
b11b5f
                    "MESSAGE_ID=" SD_MESSAGE_SESSION_START_STR,
b11b5f
                    "SESSION_ID=%s", s->id,
b11b5f
@@ -917,7 +973,7 @@ static int get_process_ctty_atime(pid_t pid, usec_t *atime) {
b11b5f
 }
b11b5f
 
b11b5f
 int session_get_idle_hint(Session *s, dual_timestamp *t) {
b11b5f
-        usec_t atime = 0, n;
b11b5f
+        usec_t atime = 0, dtime = 0;
b11b5f
         int r;
b11b5f
 
b11b5f
         assert(s);
b11b5f
@@ -961,12 +1017,16 @@ found_atime:
b11b5f
         if (t)
b11b5f
                 dual_timestamp_from_realtime(t, atime);
b11b5f
 
b11b5f
-        n = now(CLOCK_REALTIME);
b11b5f
-
b11b5f
-        if (s->manager->idle_action_usec <= 0)
b11b5f
-                return 0;
b11b5f
+        if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY)
b11b5f
+                dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec);
b11b5f
+        else if (s->manager->idle_action_usec > 0)
b11b5f
+                dtime = s->manager->idle_action_usec;
b11b5f
+        else if (s->manager->stop_idle_session_usec != USEC_INFINITY)
b11b5f
+                dtime = s->manager->stop_idle_session_usec;
b11b5f
+        else
b11b5f
+                return false;
b11b5f
 
b11b5f
-        return atime + s->manager->idle_action_usec <= n;
b11b5f
+        return usec_add(atime, dtime) <= now(CLOCK_REALTIME);
b11b5f
 }
b11b5f
 
b11b5f
 void session_set_idle_hint(Session *s, bool b) {
b11b5f
diff --git a/src/login/logind-session.h b/src/login/logind-session.h
b11b5f
index 8c7d0301f2..6678441bb9 100644
b11b5f
--- a/src/login/logind-session.h
b11b5f
+++ b/src/login/logind-session.h
b11b5f
@@ -112,6 +112,8 @@ struct Session {
b11b5f
         Hashmap *devices;
b11b5f
         sd_bus_track *track;
b11b5f
 
b11b5f
+        sd_event_source *stop_on_idle_event_source;
b11b5f
+
b11b5f
         LIST_FIELDS(Session, sessions_by_user);
b11b5f
         LIST_FIELDS(Session, sessions_by_seat);
b11b5f
 
b11b5f
diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in
b11b5f
index c7346f9819..a62c2b0b57 100644
b11b5f
--- a/src/login/logind.conf.in
b11b5f
+++ b/src/login/logind.conf.in
b11b5f
@@ -35,3 +35,4 @@
b11b5f
 #RemoveIPC=no
b11b5f
 #InhibitorsMax=8192
b11b5f
 #SessionsMax=8192
b11b5f
+#StopIdleSessionSec=infinity
b11b5f
diff --git a/src/login/logind.h b/src/login/logind.h
b11b5f
index 7f94dea2f6..606adf4fe6 100644
b11b5f
--- a/src/login/logind.h
b11b5f
+++ b/src/login/logind.h
b11b5f
@@ -102,6 +102,8 @@ struct Manager {
b11b5f
         usec_t idle_action_not_before_usec;
b11b5f
         HandleAction idle_action;
b11b5f
 
b11b5f
+        usec_t stop_idle_session_usec;
b11b5f
+
b11b5f
         HandleAction handle_power_key;
b11b5f
         HandleAction handle_suspend_key;
b11b5f
         HandleAction handle_hibernate_key;