]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #13423 from pwithnall/12035-session-time-limits
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 28 Oct 2019 13:57:00 +0000 (14:57 +0100)
committerGitHub <noreply@github.com>
Mon, 28 Oct 2019 13:57:00 +0000 (14:57 +0100)
Add `RuntimeMaxSec=` support to scope units (time-limited login sessions)

docs/TRANSIENT-SETTINGS.md
man/pam_systemd.xml
man/systemd.scope.xml
src/core/dbus-scope.c
src/core/load-fragment-gperf.gperf.m4
src/core/scope.c
src/core/scope.h
src/login/pam_systemd.c
src/shared/bus-unit-util.c
test/TEST-03-JOBS/test-jobs.sh
test/fuzz/fuzz-unit-file/directives.scope [new file with mode: 0644]

index 08d317ca41f86f4aeac08491eaeeb314b789f01b..05d6d4c0682d2359ba789921cb2584315c9fa336 100644 (file)
@@ -370,6 +370,7 @@ Scope units are fully supported as transient units (in fact they only exist as
 such).
 
 ```
+✓ RuntimeMaxSec=
 ✓ TimeoutStopSec=
 ```
 
index fd8e4fb773b41032989d39716e3f99c7f23aad88..d5be98e4c0daa8e77d9aec9f8a0e8d4cd1adb0a4 100644 (file)
 
         <listitem><para>Sets unit <varname>IOWeight=</varname>.</para></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>systemd.runtime_max_sec</varname></term>
+
+        <listitem><para>Sets unit <varname>RuntimeMaxSec=</varname>.</para></listitem>
+      </varlistentry>
     </variablelist>
 
     <para>Example data as can be provided from an another PAM module:
@@ -271,6 +277,7 @@ pam_set_data(handle, "systemd.memory_max", (void *)"200M", cleanup);
 pam_set_data(handle, "systemd.tasks_max",  (void *)"50",   cleanup);
 pam_set_data(handle, "systemd.cpu_weight", (void *)"100",  cleanup);
 pam_set_data(handle, "systemd.io_weight",  (void *)"340",  cleanup);
+pam_set_data(handle, "systemd.runtime_max_sec", (void *)"3600", cleanup);
       </programlisting>
     </para>
 
index 503a480dd08e093db7182ef98f3ec0c52eb2e8e8..daf3554db2b663b457e93acd576c2188d2eb3417 100644 (file)
     </refsect2>
   </refsect1>
 
+  <refsect1>
+    <title>Options</title>
+
+    <para>Scope files may include a <literal>[Scope]</literal>
+    section, which carries information about the scope and the
+    units it contains. A number of options that may be used in
+    this section are shared with other unit types. These options are
+    documented in
+    <citerefentry><refentrytitle>systemd.kill</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+    and
+    <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+    The options specific to the <literal>[Scope]</literal> section
+    of scope units are the following:</para>
+
+    <variablelist class='unit-directives'>
+      <varlistentry>
+        <term><varname>RuntimeMaxSec=</varname></term>
+
+        <listitem><para>Configures a maximum time for the scope to run. If this is used and the scope has been
+        active for longer than the specified time it is terminated and put into a failure state. Pass
+        <literal>infinity</literal> (the default) to configure no runtime limit.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
   <refsect1>
     <title>See Also</title>
     <para>
index 8eb915e5084b9ba3c816142ddd1b5fafa2c71875..84d91dcfa3538c14692c0f3de3ccfe4e4534f12e 100644 (file)
@@ -47,6 +47,7 @@ const sd_bus_vtable bus_scope_vtable[] = {
         SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Scope, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_SIGNAL("RequestStop", NULL, 0),
         SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_method_abandon, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_VTABLE_END
@@ -59,6 +60,7 @@ static int bus_scope_set_transient_property(
                 UnitWriteFlags flags,
                 sd_bus_error *error) {
 
+        Unit *u = UNIT(s);
         int r;
 
         assert(s);
@@ -68,7 +70,10 @@ static int bus_scope_set_transient_property(
         flags |= UNIT_PRIVATE;
 
         if (streq(name, "TimeoutStopUSec"))
-                return bus_set_transient_usec(UNIT(s), name, &s->timeout_stop_usec, message, flags, error);
+                return bus_set_transient_usec(u, name, &s->timeout_stop_usec, message, flags, error);
+
+        if (streq(name, "RuntimeMaxUSec"))
+                return bus_set_transient_usec(u, name, &s->runtime_max_usec, message, flags, error);
 
         if (streq(name, "PIDs")) {
                 _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
@@ -101,12 +106,12 @@ static int bus_scope_set_transient_property(
                         } else
                                 pid = (uid_t) upid;
 
-                        r = unit_pid_attachable(UNIT(s), pid, error);
+                        r = unit_pid_attachable(u, pid, error);
                         if (r < 0)
                                 return r;
 
                         if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
-                                r = unit_watch_pid(UNIT(s), pid, false);
+                                r = unit_watch_pid(u, pid, false);
                                 if (r < 0 && r != -EEXIST)
                                         return r;
                         }
@@ -128,7 +133,7 @@ static int bus_scope_set_transient_property(
 
                 /* We can't support direct connections with this, as direct connections know no service or unique name
                  * concept, but the Controller field stores exactly that. */
-                if (sd_bus_message_get_bus(message) != UNIT(s)->manager->api_bus)
+                if (sd_bus_message_get_bus(message) != u->manager->api_bus)
                         return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Sorry, Controller= logic only supported via the bus.");
 
                 r = sd_bus_message_read(message, "s", &controller);
index d057c0d18b67b4d93ede581e0074cd952c9460fd..66e1a17183c3464c8ec25a51edb2d6107ae54a5e 100644 (file)
@@ -471,6 +471,7 @@ CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl
 m4_dnl
 CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl
 KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl
+Scope.RuntimeMaxSec,             config_parse_sec,                   0,                             offsetof(Scope, runtime_max_usec)
 Scope.TimeoutStopSec,            config_parse_sec,                   0,                             offsetof(Scope, timeout_stop_usec)
 m4_dnl The [Install] section is ignored here.
 Install.Alias,                   NULL,                               0,                             0
index 094c8979a8ea879791094457092106b251236f21..e8cfedb0d49930eab599ca35907ceae8e7528f3c 100644 (file)
@@ -34,6 +34,7 @@ static void scope_init(Unit *u) {
         assert(u);
         assert(u->load_state == UNIT_STUB);
 
+        s->runtime_max_usec = USEC_INFINITY;
         s->timeout_stop_usec = u->manager->default_timeout_stop_usec;
         u->ignore_on_isolate = true;
 }
@@ -203,6 +204,23 @@ static int scope_load(Unit *u) {
         return scope_verify(s);
 }
 
+static usec_t scope_coldplug_timeout(Scope *s) {
+        assert(s);
+
+        switch (s->deserialized_state) {
+
+        case SCOPE_RUNNING:
+                return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec);
+
+        case SCOPE_STOP_SIGKILL:
+        case SCOPE_STOP_SIGTERM:
+                return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec);
+
+        default:
+                return USEC_INFINITY;
+        }
+}
+
 static int scope_coldplug(Unit *u) {
         Scope *s = SCOPE(u);
         int r;
@@ -213,11 +231,9 @@ static int scope_coldplug(Unit *u) {
         if (s->deserialized_state == s->state)
                 return 0;
 
-        if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) {
-                r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec));
-                if (r < 0)
-                        return r;
-        }
+        r = scope_arm_timer(s, scope_coldplug_timeout(s));
+        if (r < 0)
+                return r;
 
         if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED))
                 (void) unit_enqueue_rewatch_pids(u);
@@ -230,15 +246,18 @@ static int scope_coldplug(Unit *u) {
 
 static void scope_dump(Unit *u, FILE *f, const char *prefix) {
         Scope *s = SCOPE(u);
+        char buf_runtime[FORMAT_TIMESPAN_MAX];
 
         assert(s);
         assert(f);
 
         fprintf(f,
                 "%sScope State: %s\n"
-                "%sResult: %s\n",
+                "%sResult: %s\n"
+                "%sRuntimeMaxSec: %s\n",
                 prefix, scope_state_to_string(s->state),
-                prefix, scope_result_to_string(s->result));
+                prefix, scope_result_to_string(s->result),
+                prefix, format_timespan(buf_runtime, sizeof(buf_runtime), s->runtime_max_usec, USEC_PER_SEC));
 
         cgroup_context_dump(UNIT(s), f, prefix);
         kill_context_dump(&s->kill_context, f, prefix);
@@ -351,6 +370,9 @@ static int scope_start(Unit *u) {
 
         scope_set_state(s, SCOPE_RUNNING);
 
+        /* Set the maximum runtime timeout. */
+        scope_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec));
+
         /* Start watching the PIDs currently in the scope */
         (void) unit_enqueue_rewatch_pids(u);
         return 1;
@@ -485,6 +507,11 @@ static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *user
 
         switch (s->state) {
 
+        case SCOPE_RUNNING:
+                log_unit_warning(UNIT(s), "Scope reached runtime time limit. Stopping.");
+                scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_FAILURE_TIMEOUT);
+                break;
+
         case SCOPE_STOP_SIGTERM:
                 if (s->kill_context.send_sigkill) {
                         log_unit_warning(UNIT(s), "Stopping timed out. Killing.");
index c38afb5e5d54fd815ff7d61cd9c855a201935306..ae2bb80e55679e4f9bca22378f920d90c520c73d 100644 (file)
@@ -24,6 +24,7 @@ struct Scope {
         ScopeState state, deserialized_state;
         ScopeResult result;
 
+        usec_t runtime_max_usec;
         usec_t timeout_stop_usec;
 
         char *controller;
index 766d651c3fb11a9c65ec7cdd7e72f4ec803882af..1e38186e7776b6fbb602228ba77bd114e32088c2 100644 (file)
@@ -279,6 +279,27 @@ static int append_session_memory_max(pam_handle_t *handle, sd_bus_message *m, co
         return 0;
 }
 
+static int append_session_runtime_max_sec(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
+        usec_t val;
+        int r;
+
+        /* No need to parse "infinity" here, it will be set by default later in scope_init() */
+        if (isempty(limit) || streq(limit, "infinity"))
+                return 0;
+
+        r = parse_sec(limit, &val);
+        if (r >= 0) {
+                r = sd_bus_message_append(m, "(sv)", "RuntimeMaxUSec", "t", (uint64_t) val);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to append to bus message: %s", strerror_safe(r));
+                        return r;
+                }
+        } else
+                pam_syslog(handle, LOG_WARNING, "Failed to parse systemd.runtime_max_sec: %s, ignoring.", limit);
+
+        return 0;
+}
+
 static int append_session_tasks_max(pam_handle_t *handle, sd_bus_message *m, const char *limit) {
         uint64_t val;
         int r;
@@ -412,7 +433,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                 *seat = NULL,
                 *type = NULL, *class = NULL,
                 *class_pam = NULL, *type_pam = NULL, *cvtnr = NULL, *desktop = NULL, *desktop_pam = NULL,
-                *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL;
+                *memory_max = NULL, *tasks_max = NULL, *cpu_weight = NULL, *io_weight = NULL, *runtime_max_sec = NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
         int session_fd = -1, existing, r;
         bool debug = false, remote;
@@ -545,6 +566,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         (void) pam_get_data(handle, "systemd.tasks_max",  (const void **)&tasks_max);
         (void) pam_get_data(handle, "systemd.cpu_weight", (const void **)&cpu_weight);
         (void) pam_get_data(handle, "systemd.io_weight",  (const void **)&io_weight);
+        (void) pam_get_data(handle, "systemd.runtime_max_sec", (const void **)&runtime_max_sec);
 
         /* Talk to logind over the message bus */
 
@@ -563,8 +585,8 @@ _public_ PAM_EXTERN int pam_sm_open_session(
                            strempty(seat), vtnr, strempty(tty), strempty(display),
                            yes_no(remote), strempty(remote_user), strempty(remote_host));
                 pam_syslog(handle, LOG_DEBUG, "Session limits: "
-                           "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s",
-                           strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight));
+                           "memory_max=%s tasks_max=%s cpu_weight=%s io_weight=%s runtime_max_sec=%s",
+                           strna(memory_max), strna(tasks_max), strna(cpu_weight), strna(io_weight), strna(runtime_max_sec));
         }
 
         r = sd_bus_message_new_method_call(
@@ -608,6 +630,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
         if (r < 0)
                 return PAM_SESSION_ERR;
 
+        r = append_session_runtime_max_sec(handle, m, runtime_max_sec);
+        if (r < 0)
+                return PAM_SESSION_ERR;
+
         r = append_session_tasks_max(handle, m, tasks_max);
         if (r < 0)
                 return PAM_SESSION_ERR;
index cdd30e1e2d3fe9a659cb3ff7066b7c1eca456805..c9f352f79605ece9faa9ed81f13954e9a3fe15f1 100644 (file)
@@ -1381,6 +1381,18 @@ static int bus_append_path_property(sd_bus_message *m, const char *field, const
         return 0;
 }
 
+static int bus_append_scope_property(sd_bus_message *m, const char *field, const char *eq) {
+        if (streq(field, "RuntimeMaxSec"))
+
+                return bus_append_parse_sec_rename(m, field, eq);
+
+        if (streq(field, "TimeoutStopSec"))
+
+                return bus_append_parse_sec_rename(m, field, eq);
+
+        return 0;
+}
+
 static int bus_append_service_property(sd_bus_message *m, const char *field, const char *eq) {
         int r;
 
@@ -1747,15 +1759,15 @@ int bus_append_unit_property_assignment(sd_bus_message *m, UnitType t, const cha
                 break;
 
         case UNIT_SCOPE:
-
-                if (streq(field, "TimeoutStopSec"))
-                        return bus_append_parse_sec_rename(m, field, eq);
-
                 r = bus_append_cgroup_property(m, field, eq);
                 if (r != 0)
                         return r;
 
                 r = bus_append_kill_property(m, field, eq);
+                if (r != 0)
+                        return r;
+
+                r = bus_append_scope_property(m, field, eq);
                 if (r != 0)
                         return r;
                 break;
index 42190cf47800fafca06fa3095c4772493153e5e6..fca6cccb4fb1820944f9f640090fe606b474e00d 100755 (executable)
@@ -84,4 +84,14 @@ END_SEC=$(date -u '+%s')
 ELAPSED=$(($END_SEC-$START_SEC))
 [[ "$ELAPSED" -ge 5 ]] && [[ "$ELAPSED" -le 7 ]] || exit 1
 
+# Test time-limited scopes
+START_SEC=$(date -u '+%s')
+set +e
+systemd-run --scope --property=RuntimeMaxSec=3s sleep 10
+RESULT=$?
+END_SEC=$(date -u '+%s')
+ELAPSED=$(($END_SEC-$START_SEC))
+[[ "$ELAPSED" -ge 3 ]] && [[ "$ELAPSED" -le 5 ]] || exit 1
+[[ "$RESULT" -ne 0 ]] || exit 1
+
 touch /testok
diff --git a/test/fuzz/fuzz-unit-file/directives.scope b/test/fuzz/fuzz-unit-file/directives.scope
new file mode 100644 (file)
index 0000000..d0e194c
--- /dev/null
@@ -0,0 +1,2 @@
+scope
+RuntimeMaxSec=