Brian Stinson 2593d8
From a26f2b2732733aa361fec0a3a8f0ba377f48e75c Mon Sep 17 00:00:00 2001
Brian Stinson 2593d8
From: Anita Zhang <anitzhang@gmail.com>
Brian Stinson 2593d8
Date: Sun, 7 Oct 2018 20:28:36 -0700
Brian Stinson 2593d8
Subject: [PATCH] core: implement per unit journal rate limiting
Brian Stinson 2593d8
Brian Stinson 2593d8
Add LogRateLimitIntervalSec= and LogRateLimitBurst= options for
Brian Stinson 2593d8
services. If provided, these values get passed to the journald
Brian Stinson 2593d8
client context, and those values are used in the rate limiting
Brian Stinson 2593d8
function in the journal over the the journald.conf values.
Brian Stinson 2593d8
Brian Stinson 2593d8
Part of #10230
Brian Stinson 2593d8
Brian Stinson 2593d8
(cherry picked from commit 90fc172e191f44979005a524521112f2bd1ff21b)
Brian Stinson 2593d8
Brian Stinson 2593d8
Resolves: #1719577
Brian Stinson 2593d8
---
Brian Stinson 2593d8
 catalog/systemd.catalog.in                  |  3 +-
Brian Stinson 2593d8
 doc/TRANSIENT-SETTINGS.md                   |  2 +
Brian Stinson 2593d8
 man/journald.conf.xml                       |  8 +-
Brian Stinson 2593d8
 man/systemd.exec.xml                        | 16 ++++
Brian Stinson 2593d8
 src/core/dbus-execute.c                     |  8 ++
Brian Stinson 2593d8
 src/core/execute.c                          | 14 ++++
Brian Stinson 2593d8
 src/core/execute.h                          |  3 +
Brian Stinson 2593d8
 src/core/load-fragment-gperf.gperf.m4       |  2 +
Brian Stinson 2593d8
 src/core/unit.c                             | 92 +++++++++++++++++++++
Brian Stinson 2593d8
 src/core/unit.h                             |  2 +
Brian Stinson 2593d8
 src/journal/journald-context.c              | 50 ++++++++++-
Brian Stinson 2593d8
 src/journal/journald-context.h              |  3 +
Brian Stinson 2593d8
 src/journal/journald-rate-limit.c           | 38 ++++-----
Brian Stinson 2593d8
 src/journal/journald-rate-limit.h           |  4 +-
Brian Stinson 2593d8
 src/journal/journald-server.c               |  4 +-
Brian Stinson 2593d8
 src/shared/bus-unit-util.c                  |  8 ++
Brian Stinson 2593d8
 test/fuzz/fuzz-unit-file/directives.service |  2 +
Brian Stinson 2593d8
 17 files changed, 231 insertions(+), 28 deletions(-)
Brian Stinson 2593d8
Brian Stinson 2593d8
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
Brian Stinson 2593d8
index f1bddc6f7d..8234e387cf 100644
Brian Stinson 2593d8
--- a/catalog/systemd.catalog.in
Brian Stinson 2593d8
+++ b/catalog/systemd.catalog.in
Brian Stinson 2593d8
@@ -52,7 +52,8 @@ dropped, other services' messages are unaffected.
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 The limits controlling when messages are dropped may be configured
Brian Stinson 2593d8
 with RateLimitIntervalSec= and RateLimitBurst= in
Brian Stinson 2593d8
-/etc/systemd/journald.conf. See journald.conf(5) for details.
Brian Stinson 2593d8
+/etc/systemd/journald.conf or LogRateLimitIntervalSec= and LogRateLimitBurst=
Brian Stinson 2593d8
+in the unit file. See journald.conf(5) and systemd.exec(5) for details.
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 -- e9bf28e6e834481bb6f48f548ad13606
Brian Stinson 2593d8
 Subject: Journal messages have been missed
Brian Stinson 2593d8
diff --git a/doc/TRANSIENT-SETTINGS.md b/doc/TRANSIENT-SETTINGS.md
Brian Stinson 2593d8
index ca9e8387b7..0ea444b133 100644
Brian Stinson 2593d8
--- a/doc/TRANSIENT-SETTINGS.md
Brian Stinson 2593d8
+++ b/doc/TRANSIENT-SETTINGS.md
Brian Stinson 2593d8
@@ -135,6 +135,8 @@ All execution-related settings are available for transient units.
Brian Stinson 2593d8
 ✓ SyslogLevelPrefix=
Brian Stinson 2593d8
 ✓ LogLevelMax=
Brian Stinson 2593d8
 ✓ LogExtraFields=
Brian Stinson 2593d8
+✓ LogRateLimitIntervalSec=
Brian Stinson 2593d8
+✓ LogRateLimitBurst=
Brian Stinson 2593d8
 ✓ SecureBits=
Brian Stinson 2593d8
 ✓ CapabilityBoundingSet=
Brian Stinson 2593d8
 ✓ AmbientCapabilities=
Brian Stinson 2593d8
diff --git a/man/journald.conf.xml b/man/journald.conf.xml
Brian Stinson 2593d8
index ee8e8b7faf..b57a244b22 100644
Brian Stinson 2593d8
--- a/man/journald.conf.xml
Brian Stinson 2593d8
+++ b/man/journald.conf.xml
Brian Stinson 2593d8
@@ -140,7 +140,13 @@
Brian Stinson 2593d8
         following units: <literal>s</literal>, <literal>min</literal>,
Brian Stinson 2593d8
         <literal>h</literal>, <literal>ms</literal>,
Brian Stinson 2593d8
         <literal>us</literal>. To turn off any kind of rate limiting,
Brian Stinson 2593d8
-        set either value to 0.</para></listitem>
Brian Stinson 2593d8
+        set either value to 0.</para>
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        <para>If a service provides rate limits for itself through
Brian Stinson 2593d8
+        <varname>LogRateLimitIntervalSec=</varname> and/or <varname>LogRateLimitBurst=</varname>
Brian Stinson 2593d8
+        in <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
Brian Stinson 2593d8
+        those values will override the settings specified here.</para>
Brian Stinson 2593d8
+        </listitem>
Brian Stinson 2593d8
       </varlistentry>
Brian Stinson 2593d8
 
Brian Stinson 2593d8
       <varlistentry>
Brian Stinson 2593d8
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
Brian Stinson 2593d8
index 3bd790b485..737c52bcc4 100644
Brian Stinson 2593d8
--- a/man/systemd.exec.xml
Brian Stinson 2593d8
+++ b/man/systemd.exec.xml
Brian Stinson 2593d8
@@ -1905,6 +1905,22 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
Brian Stinson 2593d8
         matching. Assign an empty string to reset the list.</para></listitem>
Brian Stinson 2593d8
       </varlistentry>
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+      <varlistentry>
Brian Stinson 2593d8
+        <term><varname>LogRateLimitIntervalSec=</varname></term>
Brian Stinson 2593d8
+        <term><varname>LogRateLimitBurst=</varname></term>
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        <listitem><para>Configures the rate limiting that is applied to messages generated by this unit. If, in the
Brian Stinson 2593d8
+        time interval defined by <varname>LogRateLimitIntervalSec=</varname>, more messages than specified in
Brian Stinson 2593d8
+        <varname>LogRateLimitBurst=</varname> are logged by a service, all further messages within the interval are
Brian Stinson 2593d8
+        dropped until the interval is over. A message about the number of dropped messages is generated. The time
Brian Stinson 2593d8
+        specification for <varname>LogRateLimitIntervalSec=</varname> may be specified in the following units: "s",
Brian Stinson 2593d8
+        "min", "h", "ms", "us" (see
Brian Stinson 2593d8
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details).
Brian Stinson 2593d8
+        The default settings are set by <varname>RateLimitIntervalSec=</varname> and <varname>RateLimitBurst=</varname>
Brian Stinson 2593d8
+        configured in <citerefentry><refentrytitle>journald.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Brian Stinson 2593d8
+        </para></listitem>
Brian Stinson 2593d8
+      </varlistentry>
Brian Stinson 2593d8
+
Brian Stinson 2593d8
       <varlistentry>
Brian Stinson 2593d8
         <term><varname>SyslogIdentifier=</varname></term>
Brian Stinson 2593d8
 
Brian Stinson 2593d8
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
Brian Stinson 2593d8
index c44970c10c..33a91c012e 100644
Brian Stinson 2593d8
--- a/src/core/dbus-execute.c
Brian Stinson 2593d8
+++ b/src/core/dbus-execute.c
Brian Stinson 2593d8
@@ -718,6 +718,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
Brian Stinson 2593d8
         SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
         SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
         SD_BUS_PROPERTY("LogLevelMax", "i", bus_property_get_int, offsetof(ExecContext, log_level_max), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
+        SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_rate_limit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
+        SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_rate_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
         SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST),
Brian Stinson 2593d8
@@ -1073,6 +1075,12 @@ int bus_exec_context_set_transient_property(
Brian Stinson 2593d8
         if (streq(name, "CPUSchedulingPriority"))
Brian Stinson 2593d8
                 return bus_set_transient_sched_priority(u, name, &c->cpu_sched_priority, message, flags, error);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        if (streq(name, "LogRateLimitIntervalUSec"))
Brian Stinson 2593d8
+                return bus_set_transient_usec(u, name, &c->log_rate_limit_interval_usec, message, flags, error);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (streq(name, "LogRateLimitBurst"))
Brian Stinson 2593d8
+                return bus_set_transient_unsigned(u, name, &c->log_rate_limit_burst, message, flags, error);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         if (streq(name, "Personality"))
Brian Stinson 2593d8
                 return bus_set_transient_personality(u, name, &c->personality, message, flags, error);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
diff --git a/src/core/execute.c b/src/core/execute.c
Brian Stinson 2593d8
index c62f3cf849..8293c522bc 100644
Brian Stinson 2593d8
--- a/src/core/execute.c
Brian Stinson 2593d8
+++ b/src/core/execute.c
Brian Stinson 2593d8
@@ -3693,6 +3693,9 @@ void exec_context_done(ExecContext *c) {
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         exec_context_free_log_extra_fields(c);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        c->log_rate_limit_interval_usec = 0;
Brian Stinson 2593d8
+        c->log_rate_limit_burst = 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         c->stdin_data = mfree(c->stdin_data);
Brian Stinson 2593d8
         c->stdin_data_size = 0;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
@@ -4153,6 +4156,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
Brian Stinson 2593d8
                 fprintf(f, "%sLogLevelMax: %s\n", prefix, strna(t));
Brian Stinson 2593d8
         }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        if (c->log_rate_limit_interval_usec > 0) {
Brian Stinson 2593d8
+                char buf_timespan[FORMAT_TIMESPAN_MAX];
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                fprintf(f,
Brian Stinson 2593d8
+                        "%sLogRateLimitIntervalSec: %s\n",
Brian Stinson 2593d8
+                        prefix, format_timespan(buf_timespan, sizeof(buf_timespan), c->log_rate_limit_interval_usec, USEC_PER_SEC));
Brian Stinson 2593d8
+        }
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (c->log_rate_limit_burst > 0)
Brian Stinson 2593d8
+                fprintf(f, "%sLogRateLimitBurst: %u\n", prefix, c->log_rate_limit_burst);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         if (c->n_log_extra_fields > 0) {
Brian Stinson 2593d8
                 size_t j;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
diff --git a/src/core/execute.h b/src/core/execute.h
Brian Stinson 2593d8
index bff1634b88..8c91636adc 100644
Brian Stinson 2593d8
--- a/src/core/execute.h
Brian Stinson 2593d8
+++ b/src/core/execute.h
Brian Stinson 2593d8
@@ -216,6 +216,9 @@ struct ExecContext {
Brian Stinson 2593d8
         struct iovec* log_extra_fields;
Brian Stinson 2593d8
         size_t n_log_extra_fields;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        usec_t log_rate_limit_interval_usec;
Brian Stinson 2593d8
+        unsigned log_rate_limit_burst;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         bool cpu_sched_reset_on_fork;
Brian Stinson 2593d8
         bool non_blocking;
Brian Stinson 2593d8
         bool private_tmp;
Brian Stinson 2593d8
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
Brian Stinson 2593d8
index 15fb47838c..1066bcfb8f 100644
Brian Stinson 2593d8
--- a/src/core/load-fragment-gperf.gperf.m4
Brian Stinson 2593d8
+++ b/src/core/load-fragment-gperf.gperf.m4
Brian Stinson 2593d8
@@ -57,6 +57,8 @@ $1.SyslogFacility,               config_parse_log_facility,          0,
Brian Stinson 2593d8
 $1.SyslogLevel,                  config_parse_log_level,             0,                             offsetof($1, exec_context.syslog_priority)
Brian Stinson 2593d8
 $1.SyslogLevelPrefix,            config_parse_bool,                  0,                             offsetof($1, exec_context.syslog_level_prefix)
Brian Stinson 2593d8
 $1.LogLevelMax,                  config_parse_log_level,             0,                             offsetof($1, exec_context.log_level_max)
Brian Stinson 2593d8
+$1.LogRateLimitIntervalSec,      config_parse_sec,                   0,                             offsetof($1, exec_context.log_rate_limit_interval_usec)
Brian Stinson 2593d8
+$1.LogRateLimitBurst,            config_parse_unsigned,              0,                             offsetof($1, exec_context.log_rate_limit_burst)
Brian Stinson 2593d8
 $1.LogExtraFields,               config_parse_log_extra_fields,      0,                             offsetof($1, exec_context)
Brian Stinson 2593d8
 $1.Capabilities,                 config_parse_warn_compat,           DISABLED_LEGACY,               offsetof($1, exec_context)
Brian Stinson 2593d8
 $1.SecureBits,                   config_parse_exec_secure_bits,      0,                             offsetof($1, exec_context.secure_bits)
Brian Stinson 2593d8
diff --git a/src/core/unit.c b/src/core/unit.c
Brian Stinson 2593d8
index b0b1c77ef7..115739f4c6 100644
Brian Stinson 2593d8
--- a/src/core/unit.c
Brian Stinson 2593d8
+++ b/src/core/unit.c
Brian Stinson 2593d8
@@ -3245,6 +3245,8 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
Brian Stinson 2593d8
         unit_serialize_item(u, f, "exported-invocation-id", yes_no(u->exported_invocation_id));
Brian Stinson 2593d8
         unit_serialize_item(u, f, "exported-log-level-max", yes_no(u->exported_log_level_max));
Brian Stinson 2593d8
         unit_serialize_item(u, f, "exported-log-extra-fields", yes_no(u->exported_log_extra_fields));
Brian Stinson 2593d8
+        unit_serialize_item(u, f, "exported-log-rate-limit-interval", yes_no(u->exported_log_rate_limit_interval));
Brian Stinson 2593d8
+        unit_serialize_item(u, f, "exported-log-rate-limit-burst", yes_no(u->exported_log_rate_limit_burst));
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base);
Brian Stinson 2593d8
         if (u->cpu_usage_last != NSEC_INFINITY)
Brian Stinson 2593d8
@@ -3508,6 +3510,26 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                         continue;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+                } else if (streq(l, "exported-log-rate-limit-interval")) {
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                        r = parse_boolean(v);
Brian Stinson 2593d8
+                        if (r < 0)
Brian Stinson 2593d8
+                                log_unit_debug(u, "Failed to parse exported log rate limit interval %s, ignoring.", v);
Brian Stinson 2593d8
+                        else
Brian Stinson 2593d8
+                                u->exported_log_rate_limit_interval = r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                        continue;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                } else if (streq(l, "exported-log-rate-limit-burst")) {
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                        r = parse_boolean(v);
Brian Stinson 2593d8
+                        if (r < 0)
Brian Stinson 2593d8
+                                log_unit_debug(u, "Failed to parse exported log rate limit burst %s, ignoring.", v);
Brian Stinson 2593d8
+                        else
Brian Stinson 2593d8
+                                u->exported_log_rate_limit_burst = r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                        continue;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
                 } else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) {
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                         r = safe_atou64(v, &u->cpu_usage_base);
Brian Stinson 2593d8
@@ -5241,6 +5263,60 @@ fail:
Brian Stinson 2593d8
         return r;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+static int unit_export_log_rate_limit_interval(Unit *u, const ExecContext *c) {
Brian Stinson 2593d8
+        _cleanup_free_ char *buf = NULL;
Brian Stinson 2593d8
+        const char *p;
Brian Stinson 2593d8
+        int r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        assert(u);
Brian Stinson 2593d8
+        assert(c);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (u->exported_log_rate_limit_interval)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (c->log_rate_limit_interval_usec == 0)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (asprintf(&buf, "%" PRIu64, c->log_rate_limit_interval_usec) < 0)
Brian Stinson 2593d8
+                return log_oom();
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        r = symlink_atomic(buf, p);
Brian Stinson 2593d8
+        if (r < 0)
Brian Stinson 2593d8
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit interval symlink %s: %m", p);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        u->exported_log_rate_limit_interval = true;
Brian Stinson 2593d8
+        return 0;
Brian Stinson 2593d8
+}
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+static int unit_export_log_rate_limit_burst(Unit *u, const ExecContext *c) {
Brian Stinson 2593d8
+        _cleanup_free_ char *buf = NULL;
Brian Stinson 2593d8
+        const char *p;
Brian Stinson 2593d8
+        int r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        assert(u);
Brian Stinson 2593d8
+        assert(c);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (u->exported_log_rate_limit_burst)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (c->log_rate_limit_burst == 0)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (asprintf(&buf, "%u", c->log_rate_limit_burst) < 0)
Brian Stinson 2593d8
+                return log_oom();
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        r = symlink_atomic(buf, p);
Brian Stinson 2593d8
+        if (r < 0)
Brian Stinson 2593d8
+                return log_unit_debug_errno(u, r, "Failed to create log rate limit burst symlink %s: %m", p);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        u->exported_log_rate_limit_burst = true;
Brian Stinson 2593d8
+        return 0;
Brian Stinson 2593d8
+}
Brian Stinson 2593d8
+
Brian Stinson 2593d8
 void unit_export_state_files(Unit *u) {
Brian Stinson 2593d8
         const ExecContext *c;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -5274,6 +5350,8 @@ void unit_export_state_files(Unit *u) {
Brian Stinson 2593d8
         if (c) {
Brian Stinson 2593d8
                 (void) unit_export_log_level_max(u, c);
Brian Stinson 2593d8
                 (void) unit_export_log_extra_fields(u, c);
Brian Stinson 2593d8
+                (void) unit_export_log_rate_limit_interval(u, c);
Brian Stinson 2593d8
+                (void) unit_export_log_rate_limit_burst(u, c);
Brian Stinson 2593d8
         }
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -5310,6 +5388,20 @@ void unit_unlink_state_files(Unit *u) {
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                 u->exported_log_extra_fields = false;
Brian Stinson 2593d8
         }
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (u->exported_log_rate_limit_interval) {
Brian Stinson 2593d8
+                p = strjoina("/run/systemd/units/log-rate-limit-interval:", u->id);
Brian Stinson 2593d8
+                (void) unlink(p);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                u->exported_log_rate_limit_interval = false;
Brian Stinson 2593d8
+        }
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (u->exported_log_rate_limit_burst) {
Brian Stinson 2593d8
+                p = strjoina("/run/systemd/units/log-rate-limit-burst:", u->id);
Brian Stinson 2593d8
+                (void) unlink(p);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                u->exported_log_rate_limit_burst = false;
Brian Stinson 2593d8
+        }
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 int unit_prepare_exec(Unit *u) {
Brian Stinson 2593d8
diff --git a/src/core/unit.h b/src/core/unit.h
Brian Stinson 2593d8
index 68cc1869e4..99755823eb 100644
Brian Stinson 2593d8
--- a/src/core/unit.h
Brian Stinson 2593d8
+++ b/src/core/unit.h
Brian Stinson 2593d8
@@ -349,6 +349,8 @@ typedef struct Unit {
Brian Stinson 2593d8
         bool exported_invocation_id:1;
Brian Stinson 2593d8
         bool exported_log_level_max:1;
Brian Stinson 2593d8
         bool exported_log_extra_fields:1;
Brian Stinson 2593d8
+        bool exported_log_rate_limit_interval:1;
Brian Stinson 2593d8
+        bool exported_log_rate_limit_burst:1;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         /* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If
Brian Stinson 2593d8
          * == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */
Brian Stinson 2593d8
diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c
Brian Stinson 2593d8
index dba3525ed8..c8e97e16de 100644
Brian Stinson 2593d8
--- a/src/journal/journald-context.c
Brian Stinson 2593d8
+++ b/src/journal/journald-context.c
Brian Stinson 2593d8
@@ -140,6 +140,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
Brian Stinson 2593d8
         c->timestamp = USEC_INFINITY;
Brian Stinson 2593d8
         c->extra_fields_mtime = NSEC_INFINITY;
Brian Stinson 2593d8
         c->log_level_max = -1;
Brian Stinson 2593d8
+        c->log_rate_limit_interval = s->rate_limit_interval;
Brian Stinson 2593d8
+        c->log_rate_limit_burst = s->rate_limit_burst;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
Brian Stinson 2593d8
         if (r < 0) {
Brian Stinson 2593d8
@@ -151,7 +153,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
Brian Stinson 2593d8
         return 0;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-static void client_context_reset(ClientContext *c) {
Brian Stinson 2593d8
+static void client_context_reset(Server *s, ClientContext *c) {
Brian Stinson 2593d8
+        assert(s);
Brian Stinson 2593d8
         assert(c);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         c->timestamp = USEC_INFINITY;
Brian Stinson 2593d8
@@ -186,6 +189,9 @@ static void client_context_reset(ClientContext *c) {
Brian Stinson 2593d8
         c->extra_fields_mtime = NSEC_INFINITY;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         c->log_level_max = -1;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        c->log_rate_limit_interval = s->rate_limit_interval;
Brian Stinson 2593d8
+        c->log_rate_limit_burst = s->rate_limit_burst;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 static ClientContext* client_context_free(Server *s, ClientContext *c) {
Brian Stinson 2593d8
@@ -199,7 +205,7 @@ static ClientContext* client_context_free(Server *s, ClientContext *c) {
Brian Stinson 2593d8
         if (c->in_lru)
Brian Stinson 2593d8
                 assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        client_context_reset(c);
Brian Stinson 2593d8
+        client_context_reset(s, c);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         return mfree(c);
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
@@ -464,6 +470,42 @@ static int client_context_read_extra_fields(
Brian Stinson 2593d8
         return 0;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+static int client_context_read_log_rate_limit_interval(ClientContext *c) {
Brian Stinson 2593d8
+        _cleanup_free_ char *value = NULL;
Brian Stinson 2593d8
+        const char *p;
Brian Stinson 2593d8
+        int r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        assert(c);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (!c->unit)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        p = strjoina("/run/systemd/units/log-rate-limit-interval:", c->unit);
Brian Stinson 2593d8
+        r = readlink_malloc(p, &value);
Brian Stinson 2593d8
+        if (r < 0)
Brian Stinson 2593d8
+                return r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        return safe_atou64(value, &c->log_rate_limit_interval);
Brian Stinson 2593d8
+}
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+static int client_context_read_log_rate_limit_burst(ClientContext *c) {
Brian Stinson 2593d8
+        _cleanup_free_ char *value = NULL;
Brian Stinson 2593d8
+        const char *p;
Brian Stinson 2593d8
+        int r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        assert(c);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (!c->unit)
Brian Stinson 2593d8
+                return 0;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        p = strjoina("/run/systemd/units/log-rate-limit-burst:", c->unit);
Brian Stinson 2593d8
+        r = readlink_malloc(p, &value);
Brian Stinson 2593d8
+        if (r < 0)
Brian Stinson 2593d8
+                return r;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        return safe_atou(value, &c->log_rate_limit_burst);
Brian Stinson 2593d8
+}
Brian Stinson 2593d8
+
Brian Stinson 2593d8
 static void client_context_really_refresh(
Brian Stinson 2593d8
                 Server *s,
Brian Stinson 2593d8
                 ClientContext *c,
Brian Stinson 2593d8
@@ -490,6 +532,8 @@ static void client_context_really_refresh(
Brian Stinson 2593d8
         (void) client_context_read_invocation_id(s, c);
Brian Stinson 2593d8
         (void) client_context_read_log_level_max(s, c);
Brian Stinson 2593d8
         (void) client_context_read_extra_fields(s, c);
Brian Stinson 2593d8
+        (void) client_context_read_log_rate_limit_interval(c);
Brian Stinson 2593d8
+        (void) client_context_read_log_rate_limit_burst(c);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         c->timestamp = timestamp;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -520,7 +564,7 @@ void client_context_maybe_refresh(
Brian Stinson 2593d8
         /* If the data isn't pinned and if the cashed data is older than the upper limit, we flush it out
Brian Stinson 2593d8
          * entirely. This follows the logic that as long as an entry is pinned the PID reuse is unlikely. */
Brian Stinson 2593d8
         if (c->n_ref == 0 && c->timestamp + MAX_USEC < timestamp) {
Brian Stinson 2593d8
-                client_context_reset(c);
Brian Stinson 2593d8
+                client_context_reset(s, c);
Brian Stinson 2593d8
                 goto refresh;
Brian Stinson 2593d8
         }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h
Brian Stinson 2593d8
index 9df3a38eff..5e19c71f14 100644
Brian Stinson 2593d8
--- a/src/journal/journald-context.h
Brian Stinson 2593d8
+++ b/src/journal/journald-context.h
Brian Stinson 2593d8
@@ -49,6 +49,9 @@ struct ClientContext {
Brian Stinson 2593d8
         size_t extra_fields_n_iovec;
Brian Stinson 2593d8
         void *extra_fields_data;
Brian Stinson 2593d8
         nsec_t extra_fields_mtime;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        usec_t log_rate_limit_interval;
Brian Stinson 2593d8
+        unsigned log_rate_limit_burst;
Brian Stinson 2593d8
 };
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 int client_context_get(
Brian Stinson 2593d8
diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c
Brian Stinson 2593d8
index 6a8a36a736..539efb8669 100644
Brian Stinson 2593d8
--- a/src/journal/journald-rate-limit.c
Brian Stinson 2593d8
+++ b/src/journal/journald-rate-limit.c
Brian Stinson 2593d8
@@ -39,6 +39,10 @@ struct JournalRateLimitGroup {
Brian Stinson 2593d8
         JournalRateLimit *parent;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         char *id;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        /* Interval is stored to keep track of when the group expires */
Brian Stinson 2593d8
+        usec_t interval;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         JournalRateLimitPool pools[POOLS_MAX];
Brian Stinson 2593d8
         uint64_t hash;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -47,8 +51,6 @@ struct JournalRateLimitGroup {
Brian Stinson 2593d8
 };
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 struct JournalRateLimit {
Brian Stinson 2593d8
-        usec_t interval;
Brian Stinson 2593d8
-        unsigned burst;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         JournalRateLimitGroup* buckets[BUCKETS_MAX];
Brian Stinson 2593d8
         JournalRateLimitGroup *lru, *lru_tail;
Brian Stinson 2593d8
@@ -58,18 +60,13 @@ struct JournalRateLimit {
Brian Stinson 2593d8
         uint8_t hash_key[16];
Brian Stinson 2593d8
 };
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst) {
Brian Stinson 2593d8
+JournalRateLimit *journal_rate_limit_new(void) {
Brian Stinson 2593d8
         JournalRateLimit *r;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        assert(interval > 0 || burst == 0);
Brian Stinson 2593d8
-
Brian Stinson 2593d8
         r = new0(JournalRateLimit, 1);
Brian Stinson 2593d8
         if (!r)
Brian Stinson 2593d8
                 return NULL;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        r->interval = interval;
Brian Stinson 2593d8
-        r->burst = burst;
Brian Stinson 2593d8
-
Brian Stinson 2593d8
         random_bytes(r->hash_key, sizeof(r->hash_key));
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         return r;
Brian Stinson 2593d8
@@ -109,7 +106,7 @@ _pure_ static bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, us
Brian Stinson 2593d8
         assert(g);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         for (i = 0; i < POOLS_MAX; i++)
Brian Stinson 2593d8
-                if (g->pools[i].begin + g->parent->interval >= ts)
Brian Stinson 2593d8
+                if (g->pools[i].begin + g->interval >= ts)
Brian Stinson 2593d8
                         return false;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         return true;
Brian Stinson 2593d8
@@ -126,7 +123,7 @@ static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
Brian Stinson 2593d8
                 journal_rate_limit_group_free(r->lru_tail);
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t ts) {
Brian Stinson 2593d8
+static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t interval, usec_t ts) {
Brian Stinson 2593d8
         JournalRateLimitGroup *g;
Brian Stinson 2593d8
         struct siphash state;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -145,6 +142,8 @@ static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r,
Brian Stinson 2593d8
         string_hash_func(g->id, &state);
Brian Stinson 2593d8
         g->hash = siphash24_finalize(&state);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        g->interval = interval;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         journal_rate_limit_vacuum(r, ts);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g);
Brian Stinson 2593d8
@@ -189,7 +188,7 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) {
Brian Stinson 2593d8
         return burst;
Brian Stinson 2593d8
 }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available) {
Brian Stinson 2593d8
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available) {
Brian Stinson 2593d8
         uint64_t h;
Brian Stinson 2593d8
         JournalRateLimitGroup *g;
Brian Stinson 2593d8
         JournalRateLimitPool *p;
Brian Stinson 2593d8
@@ -209,11 +208,6 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
Brian Stinson 2593d8
         if (!r)
Brian Stinson 2593d8
                 return 1;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        if (r->interval == 0 || r->burst == 0)
Brian Stinson 2593d8
-                return 1;
Brian Stinson 2593d8
-
Brian Stinson 2593d8
-        burst = burst_modulate(r->burst, available);
Brian Stinson 2593d8
-
Brian Stinson 2593d8
         ts = now(CLOCK_MONOTONIC);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         siphash24_init(&state, r->hash_key);
Brian Stinson 2593d8
@@ -226,10 +220,16 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
Brian Stinson 2593d8
                         break;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         if (!g) {
Brian Stinson 2593d8
-                g = journal_rate_limit_group_new(r, id, ts);
Brian Stinson 2593d8
+                g = journal_rate_limit_group_new(r, id, rl_interval, ts);
Brian Stinson 2593d8
                 if (!g)
Brian Stinson 2593d8
                         return -ENOMEM;
Brian Stinson 2593d8
-        }
Brian Stinson 2593d8
+        } else
Brian Stinson 2593d8
+                g->interval = rl_interval;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (rl_interval == 0 || rl_burst == 0)
Brian Stinson 2593d8
+                return 1;
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        burst = burst_modulate(rl_burst, available);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
         p = &g->pools[priority_map[priority]];
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -240,7 +240,7 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u
Brian Stinson 2593d8
                 return 1;
Brian Stinson 2593d8
         }
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        if (p->begin + r->interval < ts) {
Brian Stinson 2593d8
+        if (p->begin + rl_interval < ts) {
Brian Stinson 2593d8
                 unsigned s;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                 s = p->suppressed;
Brian Stinson 2593d8
diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h
Brian Stinson 2593d8
index 3a7f106de0..a2992800fe 100644
Brian Stinson 2593d8
--- a/src/journal/journald-rate-limit.h
Brian Stinson 2593d8
+++ b/src/journal/journald-rate-limit.h
Brian Stinson 2593d8
@@ -5,6 +5,6 @@
Brian Stinson 2593d8
 
Brian Stinson 2593d8
 typedef struct JournalRateLimit JournalRateLimit;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-JournalRateLimit *journal_rate_limit_new(usec_t interval, unsigned burst);
Brian Stinson 2593d8
+JournalRateLimit *journal_rate_limit_new(void);
Brian Stinson 2593d8
 void journal_rate_limit_free(JournalRateLimit *r);
Brian Stinson 2593d8
-int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, uint64_t available);
Brian Stinson 2593d8
+int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available);
Brian Stinson 2593d8
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
Brian Stinson 2593d8
index 8de45552f6..0c983e102a 100644
Brian Stinson 2593d8
--- a/src/journal/journald-server.c
Brian Stinson 2593d8
+++ b/src/journal/journald-server.c
Brian Stinson 2593d8
@@ -943,7 +943,7 @@ void server_dispatch_message(
Brian Stinson 2593d8
         if (c && c->unit) {
Brian Stinson 2593d8
                 (void) determine_space(s, &available, NULL);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-                rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available);
Brian Stinson 2593d8
+                rl = journal_rate_limit_test(s->rate_limit, c->unit, c->log_rate_limit_interval, c->log_rate_limit_burst, priority & LOG_PRIMASK, available);
Brian Stinson 2593d8
                 if (rl == 0)
Brian Stinson 2593d8
                         return;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
@@ -1852,7 +1852,7 @@ int server_init(Server *s) {
Brian Stinson 2593d8
         if (!s->udev)
Brian Stinson 2593d8
                 return -ENOMEM;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
-        s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
Brian Stinson 2593d8
+        s->rate_limit = journal_rate_limit_new();
Brian Stinson 2593d8
         if (!s->rate_limit)
Brian Stinson 2593d8
                 return -ENOMEM;
Brian Stinson 2593d8
 
Brian Stinson 2593d8
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
Brian Stinson 2593d8
index 3238b442c0..271cc054da 100644
Brian Stinson 2593d8
--- a/src/shared/bus-unit-util.c
Brian Stinson 2593d8
+++ b/src/shared/bus-unit-util.c
Brian Stinson 2593d8
@@ -755,6 +755,14 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                 return bus_append_parse_nsec(m, field, eq);
Brian Stinson 2593d8
 
Brian Stinson 2593d8
+        if (STR_IN_SET(field, "LogRateLimitIntervalSec"))
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                return bus_append_parse_sec_rename(m, field, eq);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+        if (streq(field, "LogRateLimitBurst"))
Brian Stinson 2593d8
+
Brian Stinson 2593d8
+                return bus_append_safe_atou(m, field, eq);
Brian Stinson 2593d8
+
Brian Stinson 2593d8
         if (streq(field, "MountFlags"))
Brian Stinson 2593d8
 
Brian Stinson 2593d8
                 return bus_append_mount_propagation_flags_from_string(m, field, eq);
Brian Stinson 2593d8
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
Brian Stinson 2593d8
index c2334d3b19..d8d1fc68b8 100644
Brian Stinson 2593d8
--- a/test/fuzz/fuzz-unit-file/directives.service
Brian Stinson 2593d8
+++ b/test/fuzz/fuzz-unit-file/directives.service
Brian Stinson 2593d8
@@ -792,6 +792,8 @@ LineMax=
Brian Stinson 2593d8
 LockPersonality=
Brian Stinson 2593d8
 LogExtraFields=
Brian Stinson 2593d8
 LogLevelMax=
Brian Stinson 2593d8
+LogRateLimitIntervalSec=
Brian Stinson 2593d8
+LogRateLimitBurst=
Brian Stinson 2593d8
 LogsDirectory=
Brian Stinson 2593d8
 LogsDirectoryMode=
Brian Stinson 2593d8
 MACVLAN=