]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #24242 from msekletar/terminate-idle-sessions
authorLennart Poettering <lennart@poettering.net>
Thu, 25 Aug 2022 09:39:42 +0000 (11:39 +0200)
committerGitHub <noreply@github.com>
Thu, 25 Aug 2022 09:39:42 +0000 (11:39 +0200)
Add option to stop idle sessions after specified timeout

14 files changed:
man/logind.conf.xml
man/org.freedesktop.login1.xml
src/core/load-fragment.c
src/core/load-fragment.h
src/login/logind-core.c
src/login/logind-dbus.c
src/login/logind-gperf.gperf
src/login/logind-session.c
src/login/logind-session.h
src/login/logind.conf.in
src/login/logind.h
src/shared/conf-parser.c
src/shared/conf-parser.h
test/units/testsuite-35.sh

index fc838abee26219c4615a725529404a0748e30756..9682add08c1270cefe27799889af1fc4dd5bf936 100644 (file)
         are excluded from the effect of this setting. Defaults to <literal>yes</literal>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>StopIdleSessionSec=</varname></term>
+
+        <listitem><para>Specifies a timeout in seconds, or a time span value after which
+        <filename>systemd-logind</filename> checks the idle state of all sessions. Every session that is idle for
+        longer then the timeout will be stopped. Defaults to <literal>infinity</literal>
+        (<filename>systemd-logind</filename> is not checking the idle state of sessions). For details about the syntax
+        of time spans, see
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
+        </para></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index dc67e0100904adef982b71525f0ebde025449326..6781e1dcaaec50e59a5e8615d6a9a869a20e8c30 100644 (file)
@@ -245,6 +245,8 @@ node /org/freedesktop/login1 {
       readonly t SessionsMax = ...;
       @org.freedesktop.DBus.Property.EmitsChangedSignal("false")
       readonly t NCurrentSessions = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly t StopIdleSessionUSec = ...;
   };
   interface org.freedesktop.DBus.Peer { ... };
   interface org.freedesktop.DBus.Introspectable { ... };
@@ -262,6 +264,8 @@ node /org/freedesktop/login1 {
 
     <!--property HandleHibernateKeyLongPress is not documented!-->
 
+    <!--property StopIdleSessionUSec is not documented!-->
+
     <!--Autogenerated cross-references for systemd.directives, do not edit-->
 
     <variablelist class="dbus-interface" generated="True" extra-ref="org.freedesktop.login1.Manager"/>
@@ -490,6 +494,8 @@ node /org/freedesktop/login1 {
 
     <variablelist class="dbus-property" generated="True" extra-ref="NCurrentSessions"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="StopIdleSessionUSec"/>
+
     <!--End of Autogenerated section-->
 
     <refsect2>
index 6a7390a6f09abe6aec63f1f7f8260fb5cdc8079e..30d885e7a1d74819a3df6e1eca2464a71466d3d3 100644 (file)
@@ -2544,37 +2544,6 @@ int config_parse_service_timeout_abort(
         return 0;
 }
 
-int config_parse_sec_fix_0(
-                const char *unit,
-                const char *filename,
-                unsigned line,
-                const char *section,
-                unsigned section_line,
-                const char *lvalue,
-                int ltype,
-                const char *rvalue,
-                void *data,
-                void *userdata) {
-
-        usec_t *usec = data;
-        int r;
-
-        assert(filename);
-        assert(lvalue);
-        assert(rvalue);
-        assert(usec);
-
-        /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for
-         * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a
-         * timeout. */
-
-        r = parse_sec_fix_0(rvalue, usec);
-        if (r < 0)
-                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue);
-
-        return 0;
-}
-
 int config_parse_user_group_compat(
                 const char *unit,
                 const char *filename,
index 8842d7ddc8acdad3576d0085d256b26aa06d8d12..c57a6b227760c8023db80998d9e9b4d3affa9bc0 100644 (file)
@@ -115,7 +115,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_bus_name);
 CONFIG_PARSER_PROTOTYPE(config_parse_exec_utmp_mode);
 CONFIG_PARSER_PROTOTYPE(config_parse_working_directory);
 CONFIG_PARSER_PROTOTYPE(config_parse_fdname);
-CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
 CONFIG_PARSER_PROTOTYPE(config_parse_user_group_compat);
 CONFIG_PARSER_PROTOTYPE(config_parse_user_group_strv_compat);
 CONFIG_PARSER_PROTOTYPE(config_parse_restrict_namespaces);
index 254a1a69fb66d10f0b77b04ec03c2751bf178f02..1fba1980d7a035b2f6ff4139eb38b609880d7761 100644 (file)
@@ -71,6 +71,8 @@ void manager_reset_config(Manager *m) {
 
         m->kill_only_users = strv_free(m->kill_only_users);
         m->kill_exclude_users = strv_free(m->kill_exclude_users);
+
+        m->stop_idle_session_usec = USEC_INFINITY;
 }
 
 int manager_parse_config_file(Manager *m) {
index 65c001215d2f6439e020c98f13db9579a98c391a..4f72574318cdd5b35c7351678e63cafab9094833 100644 (file)
@@ -3402,6 +3402,7 @@ static const sd_bus_vtable manager_vtable[] = {
         SD_BUS_PROPERTY("SessionsMax", "t", NULL, offsetof(Manager, sessions_max), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("NCurrentSessions", "t", property_get_hashmap_size, offsetof(Manager, sessions), 0),
         SD_BUS_PROPERTY("UserTasksMax", "t", property_get_compat_user_tasks_max, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN),
+        SD_BUS_PROPERTY("StopIdleSessionUSec", "t", NULL, offsetof(Manager, stop_idle_session_usec), SD_BUS_VTABLE_PROPERTY_CONST),
 
         SD_BUS_METHOD_WITH_ARGS("GetSession",
                                 SD_BUS_ARGS("s", session_id),
index f11ab8ada5dd1f71cda6d166229ba3c1fd6cd985..c95a3b2dc3af48f3267a98b8c6157770647823f5 100644 (file)
@@ -50,3 +50,4 @@ Login.RemoveIPC,                    config_parse_bool,                  0, offse
 Login.InhibitorsMax,                config_parse_uint64,                0, offsetof(Manager, inhibitors_max)
 Login.SessionsMax,                  config_parse_uint64,                0, offsetof(Manager, sessions_max)
 Login.UserTasksMax,                 config_parse_compat_user_tasks_max, 0, 0
+Login.StopIdleSessionSec,           config_parse_sec_fix_0,             0, offsetof(Manager, stop_idle_session_usec)
index 267d8af852539293cb22d24e1bcb12e889cde939..443adf0c24d5f947b9db6020941fea8c3241e020 100644 (file)
@@ -152,6 +152,8 @@ Session* session_free(Session *s) {
         free(s->state_file);
         free(s->fifo_path);
 
+        sd_event_source_unref(s->stop_on_idle_event_source);
+
         return mfree(s);
 }
 
@@ -697,6 +699,55 @@ static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_er
         return 0;
 }
 
+static int session_dispatch_stop_on_idle(sd_event_source *source, uint64_t t, void *userdata) {
+        Session *s = userdata;
+        dual_timestamp ts;
+        int r, idle;
+
+        assert(s);
+
+        if (s->stopping)
+                return 0;
+
+        idle = session_get_idle_hint(s, &ts);
+        if (idle) {
+                log_debug("Session \"%s\" of user \"%s\" is idle, stopping.", s->id, s->user->user_record->user_name);
+
+                return session_stop(s, /* force */ true);
+        }
+
+        r = sd_event_source_set_time(source, usec_add(ts.monotonic, s->manager->stop_idle_session_usec));
+        if (r < 0)
+                return log_error_errno(r, "Failed to configure stop on idle session event source: %m");
+
+        r = sd_event_source_set_enabled(source, SD_EVENT_ONESHOT);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enable stop on idle session event source: %m");
+
+        return 1;
+}
+
+static int session_setup_stop_on_idle_timer(Session *s) {
+        int r;
+
+        assert(s);
+
+        if (s->manager->stop_idle_session_usec == USEC_INFINITY)
+                return 0;
+
+        r = sd_event_add_time_relative(
+                        s->manager->event,
+                        &s->stop_on_idle_event_source,
+                        CLOCK_MONOTONIC,
+                        s->manager->stop_idle_session_usec,
+                        0,
+                        session_dispatch_stop_on_idle, s);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add stop on idle session event source: %m");
+
+        return 0;
+}
+
 int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
         int r;
 
@@ -719,6 +770,10 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) {
         if (r < 0)
                 return r;
 
+        r = session_setup_stop_on_idle_timer(s);
+        if (r < 0)
+                return r;
+
         log_struct(s->class == SESSION_BACKGROUND ? LOG_DEBUG : LOG_INFO,
                    "MESSAGE_ID=" SD_MESSAGE_SESSION_START_STR,
                    "SESSION_ID=%s", s->id,
@@ -959,7 +1014,7 @@ static int get_process_ctty_atime(pid_t pid, usec_t *atime) {
 }
 
 int session_get_idle_hint(Session *s, dual_timestamp *t) {
-        usec_t atime = 0;
+        usec_t atime = 0, dtime = 0;
         int r;
 
         assert(s);
@@ -996,10 +1051,16 @@ found_atime:
         if (t)
                 dual_timestamp_from_realtime(t, atime);
 
-        if (s->manager->idle_action_usec <= 0)
+        if (s->manager->idle_action_usec > 0 && s->manager->stop_idle_session_usec != USEC_INFINITY)
+                dtime = MIN(s->manager->idle_action_usec, s->manager->stop_idle_session_usec);
+        else if (s->manager->idle_action_usec > 0)
+                dtime = s->manager->idle_action_usec;
+        else if (s->manager->stop_idle_session_usec != USEC_INFINITY)
+                dtime = s->manager->stop_idle_session_usec;
+        else
                 return false;
 
-        return usec_add(atime, s->manager->idle_action_usec) <= now(CLOCK_REALTIME);
+        return usec_add(atime, dtime) <= now(CLOCK_REALTIME);
 }
 
 int session_set_idle_hint(Session *s, bool b) {
index 6b6ac2d573f43ecc7fdd407d5b5f1d73cad39690..4c286079866a78ef7ed1ceca3f500a69db946ad8 100644 (file)
@@ -115,6 +115,8 @@ struct Session {
         Hashmap *devices;
         sd_bus_track *track;
 
+        sd_event_source *stop_on_idle_event_source;
+
         LIST_FIELDS(Session, sessions_by_user);
         LIST_FIELDS(Session, sessions_by_seat);
 
index 08a89c351cadffd410db676b50679352a50cebea..0b10df68399d4c96bae72c574e103dc38b7fbdd4 100644 (file)
@@ -46,3 +46,4 @@
 #RemoveIPC=yes
 #InhibitorsMax=8192
 #SessionsMax=8192
+#StopIdleSessionSec=infinity
index 58677c94918215d38655af4dee8de89d00831174..d0b1f9671e0538071780d041931fc1b7875af415 100644 (file)
@@ -96,6 +96,8 @@ struct Manager {
         HandleAction idle_action;
         bool was_idle;
 
+        usec_t stop_idle_session_usec;
+
         HandleAction handle_power_key;
         HandleAction handle_power_key_long_press;
         HandleAction handle_reboot_key;
index 887ae0dd61644621818d431a4e76a0814f51954a..b7ecf9d53254c14ee30699cd7dc7802c3346dafb 100644 (file)
@@ -1885,3 +1885,4 @@ int config_parse_in_addr_non_null(
 
 DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value");
 DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value");
+DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value");
index 32f2498b53e350576448db0fcbdcf3fe563cbef1..6d9d243492d792cb0488735312a5812200396445 100644 (file)
@@ -209,6 +209,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_in_addr_non_null);
 CONFIG_PARSER_PROTOTYPE(config_parse_percent);
 CONFIG_PARSER_PROTOTYPE(config_parse_permyriad);
 CONFIG_PARSER_PROTOTYPE(config_parse_pid);
+CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0);
 
 typedef enum Disabled {
         DISABLED_CONFIGURATION,
index eae2cb801d4c39fffa1029fde4169e15ec0ffc40..4ef0f0c11cc764cefd22c5b96b152e989f0df97a 100755 (executable)
@@ -493,6 +493,42 @@ test_list_users() {
     assert_eq "$(loginctl list-users --no-legend | awk '$2 == "logind-test-user" { print $3 }')" yes
 }
 
+
+teardown_stop_idle_session() (
+    set +eux
+
+    rm -f /run/systemd/logind.conf.d/stop-idle-session.conf
+    systemctl restart systemd-logind.service
+
+    cleanup_session
+)
+
+test_stop_idle_session() {
+    local id ts
+
+    if [[ ! -c /dev/tty2 ]]; then
+        echo "/dev/tty2 does not exist, skipping test ${FUNCNAME[0]}."
+        return
+    fi
+
+    create_session
+    trap teardown_stop_idle_session RETURN
+
+    id="$(loginctl --no-legend | awk '$3 == "logind-test-user" { print $1; }')"
+    ts="$(date '+%H:%M:%S')"
+
+    mkdir -p /run/systemd/logind.conf.d
+    cat >/run/systemd/logind.conf.d/stop-idle-session.conf <<EOF
+[Login]
+StopIdleSessionSec=2s
+EOF
+    systemctl restart systemd-logind.service
+    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
+}
+
 : >/failed
 
 setup_test_user
@@ -505,6 +541,7 @@ test_session
 test_lock_idle_action
 test_session_properties
 test_list_users
+test_stop_idle_session
 
 touch /testok
 rm /failed