]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journald: support reloading configuration at runtime
authorUbuntu <seaeunlee@systemd4.iwrm5ifo2b1evbwvpegt3ww1kh.xx.internal.cloudapp.net>
Wed, 11 Jun 2025 23:32:27 +0000 (23:32 +0000)
committerLennart Poettering <lennart@poettering.net>
Thu, 10 Jul 2025 19:38:36 +0000 (21:38 +0200)
22 files changed:
man/systemd-journald.service.xml
src/journal/fuzz-journald-audit.c
src/journal/fuzz-journald-kmsg.c
src/journal/fuzz-journald-native-fd.c
src/journal/fuzz-journald-stream.c
src/journal/fuzz-journald.c
src/journal/journald-console.c
src/journal/journald-gperf.gperf
src/journal/journald-kmsg.c
src/journal/journald-kmsg.h
src/journal/journald-manager.c
src/journal/journald-manager.h
src/journal/journald-native.c
src/journal/journald-socket.c
src/journal/journald-stream.c
src/journal/journald-syslog.c
src/journal/journald-wall.c
src/journal/journald.c
src/libsystemd/sd-journal/journal-file.c
src/libsystemd/sd-journal/journal-file.h
test/units/TEST-04-JOURNAL.journal-reload.sh [new file with mode: 0755]
units/systemd-journald.service.in

index 3a9d80497255652397fcab8e9cf3ce7edc13b399..eb84a31133887d2532119077e9df3bd9c24ec272 100644 (file)
@@ -218,6 +218,22 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
 
         <xi:include href="version-info.xml" xpointer="v228"/></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term>SIGHUP</term>
+
+        <listitem><para>Upon reception of the <constant>SIGHUP</constant> process signal
+         <command>systemd-journald</command> will reload its configuration values
+         and update the kernel log buffer and journals to reflect the new configuration.
+         If <varname>ReadKmsg=</varname> has changed, the kernel log buffer will be flushed
+         and updated as part of the reload. The active journal (e.g., persistent, volatile)
+         will continue to be used with the updated configuration.
+         However, if the storage mode has changed from persistent to volatile
+         and the current journal in use is the persistent journal, then the active journal will
+         be switched to the volatile journal.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 059becc04fe02b5c68e7a359c90993975f3ebda5..7558900ff51cb12e5e2fe998d79253bc811aafdd 100644 (file)
@@ -9,7 +9,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
 
         fuzz_setup_logging();
 
-        assert_se(manager_new(&m) >= 0);
+        assert_se(manager_new(&m, /* namespace= */ NULL) >= 0);
         dummy_manager_init(m, data, size);
         process_audit_string(m, 0, m->buffer, size);
 
index 9c65bcf1776af86c3d345f95a5237e536e4cfa3c..2ef36172887cff96525016a2547aa5b391c9ea0c 100644 (file)
@@ -12,7 +12,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
 
         fuzz_setup_logging();
 
-        assert_se(manager_new(&m) >= 0);
+        assert_se(manager_new(&m, /* namespace= */ NULL) >= 0);
         dummy_manager_init(m, data, size);
         dev_kmsg_record(m, m->buffer, size);
 
index ea07c5a354cd32c4b1d7e0ba73aac6e949b631b4..356e59046fbe3700053c41e219110aa9b80c13a2 100644 (file)
@@ -21,7 +21,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
 
         fuzz_setup_logging();
 
-        assert_se(manager_new(&m) >= 0);
+        assert_se(manager_new(&m, /* namespace= */ NULL) >= 0);
         dummy_manager_init(m, NULL, 0);
 
         sealed_fd = memfd_new_and_seal(NULL, data, size);
index cc3298f0dded76b13281da4f051fd5750174584c..602bd59c45f3b57315a84899fb22dfcfecea5660 100644 (file)
@@ -23,7 +23,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         fuzz_setup_logging();
 
         assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, stream_fds) >= 0);
-        assert_se(manager_new(&m) >= 0);
+        assert_se(manager_new(&m, /* namespace= */ NULL) >= 0);
         dummy_manager_init(m, NULL, 0);
 
         assert_se(stdout_stream_install(m, stream_fds[0], &stream) >= 0);
index 5c2953dbdc20c0835fb81a21dc4afdc09c414f06..0bb4d5d56504cf7f38f3455447daf4d7fb2080a4 100644 (file)
@@ -9,7 +9,7 @@
 void dummy_manager_init(Manager *m, const uint8_t *buffer, size_t size) {
         assert(m);
 
-        m->storage = STORAGE_NONE;
+        m->config.storage = STORAGE_NONE;
         assert_se(sd_event_default(&m->event) >= 0);
 
         if (buffer) {
@@ -33,7 +33,7 @@ void fuzz_journald_processing_function(
         if (size == 0)
                 return;
 
-        assert_se(manager_new(&m) >= 0);
+        assert_se(manager_new(&m, /* namespace= */ NULL) >= 0);
         dummy_manager_init(m, data, size);
         (*f)(m, m->buffer, size, ucred, tv, label, label_len);
 }
index 887b59b2690cd3b27e7c3df2e1e533356cad8d15..73cccb4da980980a389afd6fa3b2b72818ce462a 100644 (file)
@@ -52,7 +52,7 @@ void manager_forward_console(
         assert(m);
         assert(message);
 
-        if (LOG_PRI(priority) > m->max_level_console)
+        if (LOG_PRI(priority) > m->config.max_level_console)
                 return;
 
         /* First: timestamp */
index b2cfde3ade64fc13e8420cb67f59f3d6628f0d07..26cd08b384508e57e9c8fd597112fffe8dc309cb 100644 (file)
@@ -19,7 +19,7 @@ struct ConfigPerfItem;
 %struct-type
 %includes
 %%
-Journal.Storage,            config_parse_storage,           0, offsetof(Manager, storage)
+Journal.Storage,            config_parse_storage,           0, offsetof(Manager, config_by_conf.storage)
 Journal.Compress,           config_parse_compress,          0, offsetof(Manager, compress)
 Journal.Seal,               config_parse_bool,              0, offsetof(Manager, seal)
 Journal.ReadKMsg,           config_parse_bool,              0, offsetof(Manager, read_kmsg)
@@ -39,17 +39,17 @@ Journal.RuntimeKeepFree,    config_parse_iec_uint64,        0, offsetof(Manager,
 Journal.RuntimeMaxFiles,    config_parse_uint64,            0, offsetof(Manager, runtime_storage.metrics.n_max_files)
 Journal.MaxRetentionSec,    config_parse_sec,               0, offsetof(Manager, max_retention_usec)
 Journal.MaxFileSec,         config_parse_sec,               0, offsetof(Manager, max_file_usec)
-Journal.ForwardToSyslog,    config_parse_bool,              0, offsetof(Manager, forward_to_syslog)
-Journal.ForwardToKMsg,      config_parse_bool,              0, offsetof(Manager, forward_to_kmsg)
-Journal.ForwardToConsole,   config_parse_bool,              0, offsetof(Manager, forward_to_console)
-Journal.ForwardToWall,      config_parse_bool,              0, offsetof(Manager, forward_to_wall)
-Journal.ForwardToSocket,    config_parse_forward_to_socket, 0, offsetof(Manager, forward_to_socket)
+Journal.ForwardToSyslog,    config_parse_bool,              0, offsetof(Manager, config_by_conf.forward_to_syslog)
+Journal.ForwardToKMsg,      config_parse_bool,              0, offsetof(Manager, config_by_conf.forward_to_kmsg)
+Journal.ForwardToConsole,   config_parse_bool,              0, offsetof(Manager, config_by_conf.forward_to_console)
+Journal.ForwardToWall,      config_parse_bool,              0, offsetof(Manager, config_by_conf.forward_to_wall)
+Journal.ForwardToSocket,    config_parse_forward_to_socket, 0, offsetof(Manager, config_by_conf.forward_to_socket)
 Journal.TTYPath,            config_parse_path,              0, offsetof(Manager, tty_path)
-Journal.MaxLevelStore,      config_parse_log_level,         0, offsetof(Manager, max_level_store)
-Journal.MaxLevelSyslog,     config_parse_log_level,         0, offsetof(Manager, max_level_syslog)
-Journal.MaxLevelKMsg,       config_parse_log_level,         0, offsetof(Manager, max_level_kmsg)
-Journal.MaxLevelConsole,    config_parse_log_level,         0, offsetof(Manager, max_level_console)
-Journal.MaxLevelWall,       config_parse_log_level,         0, offsetof(Manager, max_level_wall)
-Journal.MaxLevelSocket,     config_parse_log_level,         0, offsetof(Manager, max_level_socket)
+Journal.MaxLevelStore,      config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_store)
+Journal.MaxLevelSyslog,     config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_syslog)
+Journal.MaxLevelKMsg,       config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_kmsg)
+Journal.MaxLevelConsole,    config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_console)
+Journal.MaxLevelWall,       config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_wall)
+Journal.MaxLevelSocket,     config_parse_log_level,         0, offsetof(Manager, config_by_conf.max_level_socket)
 Journal.SplitMode,          config_parse_split_mode,        0, offsetof(Manager, split_mode)
 Journal.LineMax,            config_parse_line_max,          0, offsetof(Manager, line_max)
index f8185baefc180f858538d26aa5db6d4a1740cb21..926cc823db5d4956b32bd9ce64980b69269d4d38 100644 (file)
@@ -46,7 +46,7 @@ void manager_forward_kmsg(
         assert(priority <= 999);
         assert(message);
 
-        if (_unlikely_(LOG_PRI(priority) > m->max_level_kmsg))
+        if (_unlikely_(LOG_PRI(priority) > m->config.max_level_kmsg))
                 return;
 
         if (_unlikely_(m->dev_kmsg_fd < 0))
@@ -128,7 +128,7 @@ void dev_kmsg_record(Manager *m, char *p, size_t l) {
         if (r < 0 || priority < 0 || priority > 999)
                 return;
 
-        if (m->forward_to_kmsg && LOG_FAC(priority) != LOG_KERN)
+        if (m->config.forward_to_kmsg && LOG_FAC(priority) != LOG_KERN)
                 return;
 
         /* seqnum */
@@ -389,6 +389,10 @@ static int dispatch_dev_kmsg(sd_event_source *es, int fd, uint32_t revents, void
         return manager_read_dev_kmsg(m);
 }
 
+static mode_t manager_kmsg_mode(bool read_kmsg) {
+        return O_CLOEXEC|O_NONBLOCK|O_NOCTTY|(read_kmsg ? O_RDWR : O_WRONLY);
+}
+
 int manager_open_dev_kmsg(Manager *m) {
         int r;
 
@@ -396,7 +400,7 @@ int manager_open_dev_kmsg(Manager *m) {
         assert(m->dev_kmsg_fd < 0);
         assert(!m->dev_kmsg_event_source);
 
-        mode_t mode = O_CLOEXEC|O_NONBLOCK|O_NOCTTY|(m->read_kmsg ? O_RDWR : O_WRONLY);
+        mode_t mode = manager_kmsg_mode(m->read_kmsg);
 
         _cleanup_close_ int fd = open("/dev/kmsg", mode);
         if (fd < 0) {
@@ -441,3 +445,33 @@ int manager_open_kernel_seqnum(Manager *m) {
 
         return 0;
 }
+
+int manager_reload_dev_kmsg(Manager *m) {
+        int r;
+
+        assert(m);
+
+        /* Check if the fd has not yet been initialized. If so, open /dev/kmsg. */
+        if (m->dev_kmsg_fd < 0)
+                return manager_open_dev_kmsg(m);
+
+        mode_t mode = manager_kmsg_mode(m->read_kmsg);
+        int flags = fcntl(m->dev_kmsg_fd, F_GETFL);
+        if (flags < 0)
+                /* Proceed with reload in case the flags have changed. */
+                log_warning_errno(errno, "Failed to get flags for /dev/kmsg, ignoring: %m");
+        else if ((flags & O_ACCMODE_STRICT) == mode)
+                /* Mode is the same. No-op. */
+                return 0;
+
+        /* Flush kmsg. */
+        r = manager_flush_dev_kmsg(m);
+        if (r < 0)
+                log_warning_errno(r, "Failed to flush /dev/kmsg on reload, ignoring: %m");
+
+        /* Set kmsg values to default. */
+        m->dev_kmsg_event_source = sd_event_source_disable_unref(m->dev_kmsg_event_source);
+        m->dev_kmsg_fd = safe_close(m->dev_kmsg_fd);
+
+        return manager_open_dev_kmsg(m);
+}
index e1f2114a8adf1e73c9c4986aa4c2521c83c966e7..51e5f34492dbda572e8b7c404cd4595152a06e89 100644 (file)
@@ -5,6 +5,7 @@
 
 int manager_open_dev_kmsg(Manager *m);
 int manager_flush_dev_kmsg(Manager *m);
+int manager_reload_dev_kmsg(Manager *m);
 
 void manager_forward_kmsg(Manager *m, int priority, const char *identifier, const char *message, const struct ucred *ucred);
 
index 6aa732e0eec2c314e44a5f7367e2b056f7a13a31..9f44fb80b4091658a52576a514979d3d240820f2 100644 (file)
 
 static int manager_schedule_sync(Manager *m, int priority);
 static int manager_refresh_idle_timer(Manager *m);
+static int dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata);
 
 static int manager_determine_path_usage(
                 Manager *m,
@@ -269,6 +270,12 @@ static void manager_add_acls(JournalFile *f, uid_t uid) {
 #endif
 }
 
+static int manager_get_file_flags(Manager *m, bool seal) {
+        return (m->compress.enabled ? JOURNAL_COMPRESS : 0) |
+               (seal ? JOURNAL_SEAL : 0) |
+               JOURNAL_STRICT_ORDER;
+}
+
 static int manager_open_journal(
                 Manager *m,
                 bool reliably,
@@ -286,10 +293,7 @@ static int manager_open_journal(
         assert(fname);
         assert(ret);
 
-        file_flags =
-                (m->compress.enabled ? JOURNAL_COMPRESS : 0) |
-                (seal ? JOURNAL_SEAL : 0) |
-                JOURNAL_STRICT_ORDER;
+        file_flags = manager_get_file_flags(m, seal);
 
         set_clear(m->deferred_closes);
 
@@ -363,7 +367,7 @@ static int manager_system_journal_open(
         int r = 0;
 
         if (!m->system_journal &&
-            IN_SET(m->storage, STORAGE_PERSISTENT, STORAGE_AUTO) &&
+            IN_SET(m->config.storage, STORAGE_PERSISTENT, STORAGE_AUTO) &&
             (flush_requested || manager_flushed_flag_is_set(m)) &&
             !relinquish_requested) {
 
@@ -371,7 +375,7 @@ static int manager_system_journal_open(
                  *
                  * If in persistent mode: create /var/log/journal and the machine path */
 
-                if (m->storage == STORAGE_PERSISTENT)
+                if (m->config.storage == STORAGE_PERSISTENT)
                         (void) mkdir_parents(m->system_storage.path, 0755);
 
                 (void) mkdir(m->system_storage.path, 0755);
@@ -408,7 +412,7 @@ static int manager_system_journal_open(
         }
 
         if (!m->runtime_journal &&
-            (m->storage != STORAGE_NONE)) {
+            (m->config.storage != STORAGE_NONE)) {
 
                 fn = strjoina(m->runtime_storage.path, "/system.journal");
 
@@ -532,7 +536,7 @@ static JournalFile* manager_find_journal(Manager *m, uid_t uid) {
          * persistent journal of any sort.
          *
          * Fixes https://github.com/systemd/systemd/issues/20390 */
-        if (!IN_SET(m->storage, STORAGE_AUTO, STORAGE_PERSISTENT))
+        if (!IN_SET(m->config.storage, STORAGE_AUTO, STORAGE_PERSISTENT))
                 return NULL;
 
         if (!uid_for_system_journal(uid)) {
@@ -1279,12 +1283,12 @@ void manager_dispatch_message(
         if (n == 0)
                 return;
 
-        if (LOG_PRI(priority) > m->max_level_store)
+        if (LOG_PRI(priority) > m->config.max_level_store)
                 return;
 
         /* Stop early in case the information will not be stored
          * in a journal. */
-        if (m->storage == STORAGE_NONE)
+        if (m->config.storage == STORAGE_NONE)
                 return;
 
         if (c && c->unit) {
@@ -1320,7 +1324,7 @@ int manager_flush_to_var(Manager *m, bool require_flag_file) {
 
         assert(m);
 
-        if (!IN_SET(m->storage, STORAGE_AUTO, STORAGE_PERSISTENT))
+        if (!IN_SET(m->config.storage, STORAGE_AUTO, STORAGE_PERSISTENT))
                 return 0;
 
         if (m->namespace) /* Flushing concept does not exist for namespace instances */
@@ -1468,7 +1472,7 @@ finish:
 int manager_relinquish_var(Manager *m) {
         assert(m);
 
-        if (m->storage == STORAGE_NONE)
+        if (m->config.storage == STORAGE_NONE)
                 return 0;
 
         if (m->namespace) /* Concept does not exist for namespaced instances */
@@ -1859,6 +1863,10 @@ static int manager_setup_signals(Manager *m) {
         if (r < 0)
                 return r;
 
+        r = sd_event_add_signal(m->event, NULL, SIGHUP|SD_EVENT_SIGNAL_PROCMASK, dispatch_reload_signal, m);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
@@ -1872,7 +1880,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse forward to syslog switch \"%s\". Ignoring.", value);
                 else
-                        m->forward_to_syslog = r;
+                        m->config_by_cmdline.forward_to_syslog = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_kmsg")) {
 
@@ -1880,7 +1888,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse forward to kmsg switch \"%s\". Ignoring.", value);
                 else
-                        m->forward_to_kmsg = r;
+                        m->config_by_cmdline.forward_to_kmsg = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_console")) {
 
@@ -1888,7 +1896,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse forward to console switch \"%s\". Ignoring.", value);
                 else
-                        m->forward_to_console = r;
+                        m->config_by_cmdline.forward_to_console = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_wall")) {
 
@@ -1896,7 +1904,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse forward to wall switch \"%s\". Ignoring.", value);
                 else
-                        m->forward_to_wall = r;
+                        m->config_by_cmdline.forward_to_wall = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_console")) {
 
@@ -1907,7 +1915,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level console value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_console = r;
+                        m->config_by_cmdline.max_level_console = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_store")) {
 
@@ -1918,7 +1926,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level store value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_store = r;
+                        m->config_by_cmdline.max_level_store = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_syslog")) {
 
@@ -1929,7 +1937,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level syslog value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_syslog = r;
+                        m->config_by_cmdline.max_level_syslog = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_kmsg")) {
 
@@ -1940,7 +1948,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level kmsg value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_kmsg = r;
+                        m->config_by_cmdline.max_level_kmsg = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_wall")) {
 
@@ -1951,7 +1959,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level wall value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_wall = r;
+                        m->config_by_cmdline.max_level_wall = r;
 
         } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_socket")) {
 
@@ -1962,7 +1970,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
                 if (r < 0)
                         log_warning("Failed to parse max level socket value \"%s\". Ignoring.", value);
                 else
-                        m->max_level_socket = r;
+                        m->config_by_cmdline.max_level_socket = r;
 
         } else if (startswith(key, "systemd.journald"))
                 log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key);
@@ -2394,7 +2402,7 @@ static void manager_load_credentials(Manager *m) {
         if (r < 0)
                 log_debug_errno(r, "Failed to read credential journal.forward_to_socket, ignoring: %m");
         else {
-                r = socket_address_parse(&m->forward_to_socket, data);
+                r = socket_address_parse(&m->config_by_cred.forward_to_socket, data);
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse socket address '%s' from credential journal.forward_to_socket, ignoring: %m", (char *) data);
         }
@@ -2409,12 +2417,225 @@ static void manager_load_credentials(Manager *m) {
                 if (r < 0)
                         log_debug_errno(r, "Failed to parse storage '%s' from credential journal.storage, ignoring: %m", (char *) data);
                 else
-                        m->storage = r;
+                        m->config_by_cred.storage = r;
         }
 }
 
-int manager_new(Manager **ret) {
+static void manager_set_defaults(Manager *m) {
+        assert(m);
+
+        m->compress.enabled = true;
+        m->compress.threshold_bytes = UINT64_MAX;
+
+        m->seal = true;
+
+        /* By default, only read from /dev/kmsg if are the main namespace */
+        m->read_kmsg = !m->namespace;
+
+        m->set_audit = true;
+
+        m->sync_interval_usec = DEFAULT_SYNC_INTERVAL_USEC;
+
+        m->ratelimit_interval = DEFAULT_RATE_LIMIT_INTERVAL;
+        m->ratelimit_burst = DEFAULT_RATE_LIMIT_BURST;
+
+        m->system_storage.name = "System Journal";
+        journal_reset_metrics(&m->system_storage.metrics);
+
+        m->runtime_storage.name = "Runtime Journal";
+        journal_reset_metrics(&m->runtime_storage.metrics);
+
+        m->max_file_usec = DEFAULT_MAX_FILE_USEC;
+
+        m->config.forward_to_wall = true;
+
+        m->config.max_level_store = LOG_DEBUG;
+        m->config.max_level_syslog = LOG_DEBUG;
+        m->config.max_level_kmsg = LOG_NOTICE;
+        m->config.max_level_console = LOG_INFO;
+        m->config.max_level_wall = LOG_EMERG;
+        m->config.max_level_socket = LOG_DEBUG;
+
+        m->line_max = DEFAULT_LINE_MAX;
+}
+
+static void manager_reset_configs(Manager *m) {
+        assert(m);
+
+        m->config_by_cmdline = JOURNAL_CONFIG_INIT;
+        m->config_by_conf = JOURNAL_CONFIG_INIT;
+        m->config_by_cred = JOURNAL_CONFIG_INIT;
+}
+
+static void manager_adjust_configs(Manager *m) {
+        assert(m);
+
+        if (!!m->ratelimit_interval != !!m->ratelimit_burst) { /* One set to 0 and the other not? */
+                log_debug(
+                                "Setting both rate limit interval and burst from %s/%u to 0/0",
+                                FORMAT_TIMESPAN(m->ratelimit_interval, USEC_PER_SEC),
+                                m->ratelimit_burst);
+                m->ratelimit_interval = m->ratelimit_burst = 0;
+        }
+}
+
+static void manager_merge_forward_to_socket(Manager *m) {
+        assert(m);
+
+        /* Conf file takes precendence over credentials. */
+        if (m->config_by_conf.forward_to_socket.sockaddr.sa.sa_family != AF_UNSPEC)
+                m->config.forward_to_socket = m->config_by_conf.forward_to_socket;
+        else if (m->config_by_cred.forward_to_socket.sockaddr.sa.sa_family != AF_UNSPEC)
+                m->config.forward_to_socket = m->config_by_cred.forward_to_socket;
+        else
+                m->config.forward_to_socket = (SocketAddress) { .sockaddr.sa.sa_family = AF_UNSPEC };
+}
+
+static void manager_merge_storage(Manager *m) {
+        assert(m);
+
+        /* Conf file takes precendence over credentials. */
+        if (m->config_by_conf.storage != _STORAGE_INVALID)
+                m->config.storage = m->config_by_conf.storage;
+        else if (m->config_by_cred.storage != _STORAGE_INVALID)
+                m->config.storage = m->config_by_cred.storage;
+        else
+                m->config.storage = m->namespace ? STORAGE_PERSISTENT : STORAGE_AUTO;
+}
+
+#define MERGE_BOOL(name, default_value)                                                 \
+    (m->config.name = (m->config_by_cmdline.name  ? m->config_by_cmdline.name :         \
+                       m->config_by_conf.name     ? m->config_by_conf.name     :        \
+                       m->config_by_cred.name     ? m->config_by_cred.name     :        \
+                       default_value))
+
+#define MERGE_NON_NEGATIVE(name, default_value)                                         \
+        (m->config.name = (m->config_by_cmdline.name >= 0 ? m->config_by_cmdline.name : \
+                           m->config_by_conf.name >= 0    ? m->config_by_conf.name :    \
+                           m->config_by_cred.name >= 0    ? m->config_by_cred.name :    \
+                           default_value))
+
+static void manager_merge_configs(Manager *m) {
+        assert(m);
+
+        /*
+         * From highest to lowest priority: cmdline, conf, cred
+         */
+        manager_merge_storage(m);
+        manager_merge_forward_to_socket(m);
+
+        MERGE_BOOL(forward_to_syslog, false);
+        MERGE_BOOL(forward_to_kmsg, false);
+        MERGE_BOOL(forward_to_console, false);
+        MERGE_BOOL(forward_to_wall, true);
+
+        MERGE_NON_NEGATIVE(max_level_store, LOG_DEBUG);
+        MERGE_NON_NEGATIVE(max_level_syslog, LOG_DEBUG);
+        MERGE_NON_NEGATIVE(max_level_kmsg, LOG_NOTICE);
+        MERGE_NON_NEGATIVE(max_level_console, LOG_INFO);
+        MERGE_NON_NEGATIVE(max_level_wall, LOG_EMERG);
+        MERGE_NON_NEGATIVE(max_level_socket, LOG_DEBUG);
+}
+
+static void manager_load_config(Manager *m) {
+        assert(m);
+
+        int r;
+
+        manager_set_defaults(m);
+        manager_reset_configs(m);
+
+        manager_load_credentials(m);
+        manager_parse_config_file(m);
+
+        if (!m->namespace) {
+                /* Parse kernel command line, but only if we are not a namespace instance */
+                r = proc_cmdline_parse(parse_proc_cmdline_item, m, PROC_CMDLINE_STRIP_RD_PREFIX);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+        }
+
+        manager_merge_configs(m);
+
+        manager_adjust_configs(m);
+}
+
+static void manager_reload_config(Manager *m) {
+        assert(m);
+
+        manager_set_defaults(m);
+
+        m->config_by_conf = JOURNAL_CONFIG_INIT;
+        manager_parse_config_file(m);
+
+        manager_merge_configs(m);
+        manager_adjust_configs(m);
+}
+
+static int manager_reload_journals(Manager *m) {
+        assert(m);
+
+        int r;
+
+        if (m->system_journal && IN_SET(m->config.storage, STORAGE_PERSISTENT, STORAGE_AUTO)) {
+                /* Current journal can continue being used. Update config values as needed. */
+                r = journal_file_reload(
+                                m->system_journal,
+                                manager_get_file_flags(m, m->seal),
+                                m->compress.threshold_bytes,
+                                &m->system_storage.metrics);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to reload system journal on reload, ignoring: %m");
+        } else if (m->system_journal && m->config.storage == STORAGE_VOLATILE) {
+                /* Journal needs to be switched from system to runtime. */
+                r = manager_relinquish_var(m);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to relinquish to runtime journal on reload, ignoring: %m");
+        } else if (m->runtime_journal && IN_SET(m->config.storage, STORAGE_PERSISTENT, STORAGE_AUTO, STORAGE_VOLATILE)) {
+                /* Current journal can continue being used. Update config values as needed.*/
+                r = journal_file_reload(
+                                m->runtime_journal,
+                                manager_get_file_flags(m, /* seal */ false),
+                                m->compress.threshold_bytes,
+                                &m->runtime_storage.metrics);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to reload runtime journal on reload, ignoring: %m");
+        }
+
+        /* If journal-related configuration, such as SystemMaxUse, SystemMaxFileSize, RuntimeMaxUse, RuntimeMaxFileSize,
+         * were to change, then we can vacuum for the change to take effect. For example, if pre-reload SystemMaxUse=2M,
+         * current usage=1.5M, and the post-reload SystemMaxUse=1M, the vacuum can shrink it to 1M.
+         */
+        manager_vacuum(m, /* verbose */ false);
+
+        return 0;
+}
+
+static int dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
+        Manager *m = ASSERT_PTR(userdata);
+        int r;
+
+        (void) notify_reloading();
+
+        manager_reload_config(m);
+
+        r = manager_reload_dev_kmsg(m);
+        if (r < 0)
+                return r;
+
+        r = manager_reload_journals(m);
+        if (r < 0)
+                return r;
+
+        log_info("Config file reloaded.");
+        (void) sd_notify(/* unset_environment */ false, NOTIFY_READY_MESSAGE);
+
+        return 0;
+}
+
+int manager_new(Manager **ret, const char *namespace) {
         _cleanup_(manager_freep) Manager *m = NULL;
+        int r;
 
         assert(ret);
 
@@ -2432,37 +2653,10 @@ int manager_new(Manager **ret) {
                 .notify_fd = -EBADF,
                 .forward_socket_fd = -EBADF,
 
-                .compress.enabled = true,
-                .compress.threshold_bytes = UINT64_MAX,
-                .seal = true,
-
-                .set_audit = true,
-
                 .watchdog_usec = USEC_INFINITY,
 
-                .sync_interval_usec = DEFAULT_SYNC_INTERVAL_USEC,
                 .sync_scheduled = false,
 
-                .ratelimit_interval = DEFAULT_RATE_LIMIT_INTERVAL,
-                .ratelimit_burst = DEFAULT_RATE_LIMIT_BURST,
-
-                .forward_to_wall = true,
-                .forward_to_socket = { .sockaddr.sa.sa_family = AF_UNSPEC },
-
-                .max_file_usec = DEFAULT_MAX_FILE_USEC,
-
-                .max_level_store = LOG_DEBUG,
-                .max_level_syslog = LOG_DEBUG,
-                .max_level_kmsg = LOG_NOTICE,
-                .max_level_console = LOG_INFO,
-                .max_level_wall = LOG_EMERG,
-                .max_level_socket = LOG_DEBUG,
-
-                .line_max = DEFAULT_LINE_MAX,
-
-                .runtime_storage.name = "Runtime Journal",
-                .system_storage.name = "System Journal",
-
                 .kmsg_own_ratelimit = {
                         .interval = DEFAULT_KMSG_OWN_INTERVAL,
                         .burst = DEFAULT_KMSG_OWN_BURST,
@@ -2472,11 +2666,17 @@ int manager_new(Manager **ret) {
                 .sigrtmin18_info.memory_pressure_userdata = m,
         };
 
+        r = manager_set_namespace(m, namespace);
+        if (r < 0)
+                return r;
+
+        manager_load_config(m);
+
         *ret = TAKE_PTR(m);
         return 0;
 }
 
-int manager_init(Manager *m, const char *namespace) {
+int manager_init(Manager *m) {
         const char *native_socket, *syslog_socket, *stdout_socket, *varlink_socket, *e;
         _cleanup_fdset_free_ FDSet *fds = NULL;
         int n, r, varlink_fd = -EBADF;
@@ -2484,33 +2684,6 @@ int manager_init(Manager *m, const char *namespace) {
 
         assert(m);
 
-        r = manager_set_namespace(m, namespace);
-        if (r < 0)
-                return r;
-
-        /* By default, only read from /dev/kmsg if are the main namespace */
-        m->read_kmsg = !m->namespace;
-        m->storage = m->namespace ? STORAGE_PERSISTENT : STORAGE_AUTO;
-
-        journal_reset_metrics(&m->system_storage.metrics);
-        journal_reset_metrics(&m->runtime_storage.metrics);
-
-        manager_load_credentials(m);
-        manager_parse_config_file(m);
-
-        if (!m->namespace) {
-                /* Parse kernel command line, but only if we are not a namespace instance */
-                r = proc_cmdline_parse(parse_proc_cmdline_item, m, PROC_CMDLINE_STRIP_RD_PREFIX);
-                if (r < 0)
-                        log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
-        }
-
-        if (!!m->ratelimit_interval != !!m->ratelimit_burst) { /* One set to 0 and the other not? */
-                log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0",
-                          m->ratelimit_interval, m->ratelimit_burst);
-                m->ratelimit_interval = m->ratelimit_burst = 0;
-        }
-
         e = getenv("RUNTIME_DIRECTORY");
         if (e)
                 m->runtime_directory = strdup(e);
index dda1184e25b35528ac61eea497ca7ea8809051e3..f1958f79bfec49e158a3e540e5df48eaacfcedcb 100644 (file)
@@ -55,6 +55,23 @@ typedef struct SeqnumData {
         uint64_t seqnum;
 } SeqnumData;
 
+typedef struct JournalConfig {
+        SocketAddress forward_to_socket;
+        Storage storage;
+
+        bool forward_to_kmsg;
+        bool forward_to_syslog;
+        bool forward_to_console;
+        bool forward_to_wall;
+
+        int max_level_store;
+        int max_level_syslog;
+        int max_level_kmsg;
+        int max_level_console;
+        int max_level_wall;
+        int max_level_socket;
+} JournalConfig;
+
 typedef struct Manager {
         char *namespace;
 
@@ -111,12 +128,6 @@ typedef struct Manager {
         bool sent_notify_ready;
         bool sync_scheduled;
 
-        bool forward_to_kmsg;
-        bool forward_to_syslog;
-        bool forward_to_console;
-        bool forward_to_wall;
-        SocketAddress forward_to_socket;
-
         unsigned n_forward_syslog_missed;
         usec_t last_warn_forward_syslog_missed;
 
@@ -130,14 +141,6 @@ typedef struct Manager {
 
         char *tty_path;
 
-        int max_level_store;
-        int max_level_syslog;
-        int max_level_kmsg;
-        int max_level_console;
-        int max_level_wall;
-        int max_level_socket;
-
-        Storage storage;
         SplitMode split_mode;
 
         MMapCache *mmap;
@@ -183,6 +186,21 @@ typedef struct Manager {
 
         /* Pending synchronization requests with non-zero rqlen counter */
         LIST_HEAD(SyncReq, sync_req_pending_rqlen);
+
+        /* These structs are used to preserve configurations set by credentials and command line.
+                config - main configuration used by journald manager,
+                config_by_cred - configuration set by credentials,
+                config_by_conf - configuration set by configuration file,
+                config_by_cmdline - configuration set by command line.
+           The priority order of the sub-configurations is:
+                config_by_cmdline > config_by_conf > config_by_cred
+           where A > B means that if the two have the same setting,
+           A's value overrides B's value for that setting.
+         */
+        JournalConfig config;
+        JournalConfig config_by_cred;
+        JournalConfig config_by_conf;
+        JournalConfig config_by_cmdline;
 } Manager;
 
 #define MANAGER_MACHINE_ID(s) ((s)->machine_id_field + STRLEN("_MACHINE_ID="))
@@ -209,6 +227,17 @@ void manager_dispatch_message(Manager *m, struct iovec *iovec, size_t n, size_t
 void manager_driver_message_internal(Manager *m, pid_t object_pid, const char *format, ...) _sentinel_;
 #define manager_driver_message(...) manager_driver_message_internal(__VA_ARGS__, NULL)
 
+#define JOURNAL_CONFIG_INIT                                                                     \
+        (JournalConfig) {                                                                       \
+                .forward_to_socket = (SocketAddress) { .sockaddr.sa.sa_family = AF_UNSPEC },    \
+                .storage = _STORAGE_INVALID,                                                    \
+                .max_level_store = -1,                                                          \
+                .max_level_syslog = -1,                                                         \
+                .max_level_kmsg = -1,                                                           \
+                .max_level_console = -1,                                                        \
+                .max_level_wall = -1,                                                           \
+        }
+
 /* gperf lookup function */
 const struct ConfigPerfItem* journald_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
 
@@ -225,8 +254,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_split_mode);
 const char* split_mode_to_string(SplitMode s) _const_;
 SplitMode split_mode_from_string(const char *s) _pure_;
 
-int manager_new(Manager **ret);
-int manager_init(Manager *m, const char *namespace);
+int manager_new(Manager **ret, const char *namespace);
+int manager_init(Manager *m);
 Manager* manager_free(Manager *m);
 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
 void manager_full_sync(Manager *m, bool wait);
index 4288bd4f3f5f5f0fa1a8018ae7cc5603ea331c65..d9c6fad4f63509e354f2521246ba3f5e89cff355 100644 (file)
@@ -267,16 +267,16 @@ static int manager_process_entry(
                 if (r <= 0)
                         goto finish;
 
-                if (m->forward_to_syslog)
+                if (m->config.forward_to_syslog)
                         manager_forward_syslog(m, syslog_fixup_facility(priority), identifier, message, ucred, tv);
 
-                if (m->forward_to_kmsg)
+                if (m->config.forward_to_kmsg)
                         manager_forward_kmsg(m, priority, identifier, message, ucred);
 
-                if (m->forward_to_console)
+                if (m->config.forward_to_console)
                         manager_forward_console(m, priority, identifier, message, ucred);
 
-                if (m->forward_to_wall)
+                if (m->config.forward_to_wall)
                         manager_forward_wall(m, priority, identifier, message, ucred);
         }
 
index ba3c37e45b7db8ebdf84becf66cff447cdf2e362..533fa664ced72bb80f320735754a3b3937b55e0e 100644 (file)
@@ -23,13 +23,13 @@ static int manager_open_forward_socket(Manager *m) {
         assert(m);
 
         /* Noop if there is nothing to do. */
-        if (m->forward_to_socket.sockaddr.sa.sa_family == AF_UNSPEC || m->namespace)
+        if (m->config.forward_to_socket.sockaddr.sa.sa_family == AF_UNSPEC || m->namespace)
                 return 0;
         /* All ready, nothing to do. */
         if (m->forward_socket_fd >= 0)
                 return 1;
 
-        addr = &m->forward_to_socket;
+        addr = &m->config.forward_to_socket;
 
         family = socket_address_family(addr);
 
@@ -86,7 +86,7 @@ int manager_forward_socket(
         assert(n_iovec > 0);
         assert(ts);
 
-        if (LOG_PRI(priority) > m->max_level_socket)
+        if (LOG_PRI(priority) > m->config.max_level_socket)
                 return 0;
 
         r = manager_open_forward_socket(m);
index 45fc3257fff9476376048549f242fbf7084cc3af..cfd17fcd83dcfd9bcbdf83cd5ad630e6e293fe5a 100644 (file)
@@ -251,16 +251,16 @@ static int stdout_stream_log(
         if (r <= 0)
                 return r;
 
-        if (s->forward_to_syslog || s->manager->forward_to_syslog)
+        if (s->forward_to_syslog || s->manager->config.forward_to_syslog)
                 manager_forward_syslog(s->manager, syslog_fixup_facility(priority), s->identifier, p, &s->ucred, NULL);
 
-        if (s->forward_to_kmsg || s->manager->forward_to_kmsg)
+        if (s->forward_to_kmsg || s->manager->config.forward_to_kmsg)
                 manager_forward_kmsg(s->manager, priority, s->identifier, p, &s->ucred);
 
-        if (s->forward_to_console || s->manager->forward_to_console)
+        if (s->forward_to_console || s->manager->config.forward_to_console)
                 manager_forward_console(s->manager, priority, s->identifier, p, &s->ucred);
 
-        if (s->manager->forward_to_wall)
+        if (s->manager->config.forward_to_wall)
                 manager_forward_wall(s->manager, priority, s->identifier, p, &s->ucred);
 
         m = N_IOVEC_META_FIELDS + 7 + client_context_extra_fields_n_iovec(s->context);
index ca204f25f5ec6be9f50c0db4fbaf96aef6616b62..8e9c0e2b402ae32e7d0f4222c567b7790e125f04 100644 (file)
@@ -127,7 +127,7 @@ static void forward_syslog_raw(
         assert(m);
         assert(buffer);
 
-        if (LOG_PRI(priority) > m->max_level_syslog)
+        if (LOG_PRI(priority) > m->config.max_level_syslog)
                 return;
 
         iovec = IOVEC_MAKE((char *) buffer, buffer_len);
@@ -154,7 +154,7 @@ void manager_forward_syslog(
         assert(priority <= 999);
         assert(message);
 
-        if (LOG_PRI(priority) > m->max_level_syslog)
+        if (LOG_PRI(priority) > m->config.max_level_syslog)
                 return;
 
         /* First: priority field */
@@ -403,16 +403,16 @@ void manager_process_syslog_message(
 
         syslog_parse_identifier(&msg, &identifier, &pid);
 
-        if (m->forward_to_syslog)
+        if (m->config.forward_to_syslog)
                 forward_syslog_raw(m, priority, buf, raw_len, ucred, tv);
 
-        if (m->forward_to_kmsg)
+        if (m->config.forward_to_kmsg)
                 manager_forward_kmsg(m, priority, identifier, msg, ucred);
 
-        if (m->forward_to_console)
+        if (m->config.forward_to_console)
                 manager_forward_console(m, priority, identifier, msg, ucred);
 
-        if (m->forward_to_wall)
+        if (m->config.forward_to_wall)
                 manager_forward_wall(m, priority, identifier, msg, ucred);
 
         mm = N_IOVEC_META_FIELDS + 8 + client_context_extra_fields_n_iovec(context);
index 375b03a65496e7dceff254b427d2859094d1faab..5c862821660d6cebfc9d574a1bdaf0524b613adf 100644 (file)
@@ -23,7 +23,7 @@ void manager_forward_wall(
         assert(m);
         assert(message);
 
-        if (LOG_PRI(priority) > m->max_level_wall)
+        if (LOG_PRI(priority) > m->config.max_level_wall)
                 return;
 
         if (ucred) {
index 1fcae98f70bcffe530a5832d60641aa094b4ce9a..95787418d228c93101f4f025e552917146af373c 100644 (file)
@@ -52,11 +52,11 @@ static int run(int argc, char *argv[]) {
 
         sigbus_install();
 
-        r = manager_new(&m);
+        r = manager_new(&m, namespace);
         if (r < 0)
                 return log_oom();
 
-        r = manager_init(m, namespace);
+        r = manager_init(m);
         if (r < 0)
                 return r;
 
index 11e251748a2e6652447a964daf16acef06f640fd..5b988e1f95d9244d3612c64ddb80f4fd9605ce3c 100644 (file)
@@ -4082,6 +4082,54 @@ static void journal_default_metrics(JournalMetrics *m, int fd, bool compact) {
                   m->n_max_files);
 }
 
+static uint64_t get_compress_threshold_bytes(uint64_t compress_threshold_bytes) {
+        return compress_threshold_bytes == UINT64_MAX ?
+                DEFAULT_COMPRESS_THRESHOLD :
+                MAX(MIN_COMPRESS_THRESHOLD, compress_threshold_bytes);
+}
+
+static int set_metrics(JournalFile *f, JournalMetrics *metrics, JournalFile *template) {
+        assert(f);
+        int r;
+
+        if (!journal_file_writable(f))
+                return 0;
+
+        if (metrics) {
+                journal_default_metrics(metrics, f->fd, JOURNAL_HEADER_COMPACT(f->header));
+                f->metrics = *metrics;
+        } else if (template)
+                f->metrics = template->metrics;
+
+        r = journal_file_refresh_header(f);
+        if (r < 0)
+                return log_error_errno(r, "Failed to refresh journal file header. Error to be handled by caller.");
+
+        return 0;
+}
+
+int journal_file_reload(
+                JournalFile *f,
+                JournalFileFlags file_flags,
+                uint64_t compress_threshold_bytes,
+                JournalMetrics *metrics) {
+
+        assert(f);
+        assert((file_flags & ~_JOURNAL_FILE_FLAGS_ALL) == 0);
+        assert(metrics);
+
+        int r;
+
+        f->compress_threshold_bytes = get_compress_threshold_bytes(compress_threshold_bytes);
+
+        r = set_metrics(f, metrics, /* template */ NULL);
+        if (r < 0)
+                /* Journal file header failed to be rotated. The changes may not have taken effect in this case. */
+                return r;
+
+        return 0;
+}
+
 int journal_file_open(
                 int fd,
                 const char *fname,
@@ -4121,9 +4169,7 @@ int journal_file_open(
                 .fd = fd,
                 .mode = mode,
                 .open_flags = open_flags,
-                .compress_threshold_bytes = compress_threshold_bytes == UINT64_MAX ?
-                                            DEFAULT_COMPRESS_THRESHOLD :
-                                            MAX(MIN_COMPRESS_THRESHOLD, compress_threshold_bytes),
+                .compress_threshold_bytes = get_compress_threshold_bytes(compress_threshold_bytes),
                 .strict_order = FLAGS_SET(file_flags, JOURNAL_STRICT_ORDER),
                 .newest_boot_id_prioq_idx = PRIOQ_IDX_NULL,
                 .last_direction = _DIRECTION_INVALID,
@@ -4232,17 +4278,9 @@ int journal_file_open(
         }
 #endif
 
-        if (journal_file_writable(f)) {
-                if (metrics) {
-                        journal_default_metrics(metrics, f->fd, JOURNAL_HEADER_COMPACT(f->header));
-                        f->metrics = *metrics;
-                } else if (template)
-                        f->metrics = template->metrics;
-
-                r = journal_file_refresh_header(f);
-                if (r < 0)
-                        goto fail;
-        }
+        r = set_metrics(f, metrics, template);
+        if (r < 0)
+                goto fail;
 
 #if HAVE_GCRYPT
         r = journal_file_hmac_setup(f);
index 1fcfafbb16e28a33325ed7012aa86d71f988751b..d00e5f8c535b925b40ba10e852549417e815858b 100644 (file)
@@ -149,6 +149,12 @@ int journal_file_open(
                 JournalFile *template,
                 JournalFile **ret);
 
+int journal_file_reload(
+        JournalFile *f,
+        JournalFileFlags file_flags,
+        uint64_t compress_threshold_bytes,
+        JournalMetrics *metrics);
+
 int journal_file_set_offline_thread_join(JournalFile *f);
 JournalFile* journal_file_close(JournalFile *j);
 int journal_file_fstat(JournalFile *f);
diff --git a/test/units/TEST-04-JOURNAL.journal-reload.sh b/test/units/TEST-04-JOURNAL.journal-reload.sh
new file mode 100755 (executable)
index 0000000..53314a5
--- /dev/null
@@ -0,0 +1,316 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2016
+set -eux
+set -o pipefail
+
+MACHINE_ID="$(</etc/machine-id)"
+TEST_MSG_PREFIX="JOURNAL-RELOAD TEST"
+SYSLOG_ID="$(systemd-id128 new)"
+
+write_to_journal() {
+    local rand_val
+
+    rand_val=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 14)
+    echo "$TEST_MSG_PREFIX $rand_val" | systemd-cat -t "$SYSLOG_ID"
+    journalctl --sync
+
+    echo "$rand_val"
+}
+
+verify_archived_journals() {
+    local msg="$1"
+    local journal_path_prefix="$2"
+    local journal_dir="/$journal_path_prefix/log/journal/$MACHINE_ID"
+
+    for journal_file in "$journal_dir"/*; do
+        filename=$(basename -- "$journal_file")
+        if [[ "$filename" != "system.journal" ]]; then
+            if journalctl --file="$journal_file" | grep -q "$msg"; then
+                echo "Message present in archived journal: $filename"
+                exit 0
+            fi
+        fi
+    done
+
+    exit 1
+}
+
+# shellcheck disable=SC2317
+verify_journal() {
+    local msg="$1"
+    local entry_expected="$2"
+    local test_name="$3"
+    local journal_path_prefix="$4"
+
+    local path="/$journal_path_prefix/log/journal/$MACHINE_ID/system.journal"
+
+    if [ ! -e "$path" ] || ! grep -Fxq "MESSAGE=$TEST_MSG_PREFIX $msg" "$path"; then
+        if [ "$entry_expected" == true ]; then
+            echo "$test_name ERROR: Message not present in $journal_path_prefix journal. Checking archived journals..."
+            if ! verify_archived_journals "$msg" "$journal_path_prefix"; then
+                echo "$test_name ERROR: Message also not present in archived journals"
+                cleanup
+                exit 1
+            fi
+
+        fi
+    else
+        if [ "$entry_expected" == false ]; then
+            echo "$test_name ERROR: Message present in $journal_path_prefix journal"
+            cleanup
+            exit 1
+        fi
+    fi
+}
+
+verify_journals() {
+    local msg="$1"
+    local runtime_expected="$2"
+    local system_expected="$3"
+    local test_name="$4"
+
+    local failed=false
+
+    if ! verify_journal "$msg" "$runtime_expected" "$test_name" "run"; then
+        failed=true
+    fi
+
+    if ! verify_journal "$msg" "$system_expected" "$test_name" "var"; then
+        failed=true
+    fi
+
+    if [ "$failed" == true ]; then
+        cleanup
+        exit 1
+    fi
+}
+
+get_num_archived_journals() {
+    local journal_path_prefix="$1"
+
+    local journal_dir="/$journal_path_prefix/log/journal/$MACHINE_ID/"
+    num_journal_files=$(find "$journal_dir" -type f -name "*.journal" ! -name "system.journal" | wc -l)
+
+    echo "$num_journal_files"
+}
+
+cleanup() {
+    rm /run/systemd/journald.conf.d/reload.conf
+    journalctl --vacuum-size=1M
+    systemctl daemon-reload
+    systemctl reload systemd-journald.service
+}
+
+# Start clean slate.
+mkdir -p /run/systemd/journald.conf.d
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=persistent
+EOF
+
+systemctl daemon-reload
+systemctl restart systemd-journald.service
+
+# Add entries in system.
+journalctl --flush
+rand_val1=$(write_to_journal)
+verify_journals "$rand_val1" false true "Confirming test setup after flush."
+
+# Reload journald (persistent->persistent)
+systemctl reload systemd-journald.service
+
+# Reload should persist persistent journal.
+verify_journals "$rand_val1" false true "Persistent->Persistent System Reload: "
+
+rand_val1=$(write_to_journal)
+verify_journals "$rand_val1" false true "Persistent->Persistent System Post-Reload: "
+
+# Add entries in runtime
+journalctl --relinquish
+rand_val2=$(write_to_journal)
+verify_journals "$rand_val2" true false "Confirming test setup after relinquish."
+
+# Reload journald (persistent->persistent)
+systemctl reload systemd-journald.service
+
+# System journal entries should stay in system journal, runtime in runtime.
+verify_journals "$rand_val1" false true "Persistent->Persistent Runtime Reload 1: "
+verify_journals "$rand_val2" true false "Persistent->Persistent Runtime Reload 2: "
+
+# Write new message and confirm it's written to runtime.
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" true false "Persistent->Persistent New Message After Reload: "
+
+# Flush and confirm that messages are written to system.
+journalctl --flush
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" false true "Persistent->Volatile New Message Before Reload: "
+
+# Test persistent->volatile
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=volatile
+EOF
+
+# Confirm old message exists where it was written to (storage->storage).
+systemctl reload systemd-journald.service
+verify_journals "$rand_val" false true "Persistent->Volatile Reload: "
+
+# Confirm that messages are written to only runtime journal.
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" true false "Persistent->Volatile New Message After Reload: "
+
+# Test volatile works and logs are NOT getting written to system journal despite flush.
+journalctl --flush
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" true false "Persistent->Volatile New Message After Flush: "
+
+# Test that the new limits (e.g., RuntimeMaxUse) take effect on reload.
+# Write 1M of data to runtime journal
+max_size=1048656 # (1 * 1024 * 1024) = 1048576, but centos has a different minimum value for some reason.
+set +x
+dd if=/dev/urandom bs=1M count=5 | base64 | systemd-cat -t "$SYSLOG_ID"
+set -x
+journalctl --vacuum-size=2M
+
+total_size=$(du -sb "/run/log/journal/$MACHINE_ID" | cut -f1)
+if [ "$total_size" -lt "$max_size" ]; then
+    echo "ERROR: Journal size does not exceed RuntimeMaxUse limit"
+    cleanup
+    exit 1
+fi
+
+# Reload with RuntimeMaxUse=1M.
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=volatile
+RuntimeMaxUse=1M
+EOF
+
+# systemctl daemon-reload
+systemctl reload systemd-journald.service
+
+sleep 15 # Wait for RuntimeMaxUse change to take effect.
+
+# Confirm that runtime journal size shrunk to <=1M.
+total_size=$(du -sb "/run/log/journal/$MACHINE_ID" | cut -f1)
+if [ "$total_size" -gt "$max_size" ]; then
+    echo "ERROR: Journal size exceeds RuntimeMaxUse limit"
+    cleanup
+    exit 1
+fi
+
+# Prepare for volatile->persistent by getting rid of runtime limit. Otherwise, it will not write.
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=volatile
+EOF
+systemctl daemon-reload
+systemctl reload systemd-journald.service
+sleep 15 # Wait for RuntimeMaxUse change to take effect.
+
+journalctl --vacuum-size=1M
+sleep 5
+
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" true false "Volatile->Persistent New Message Before Reload: "
+
+# Reload volatile->persistent
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=persistent
+EOF
+
+systemctl reload systemd-journald.service
+
+# Confirm that previous message is still in runtime journal.
+verify_journals "$rand_val" true false "Volatile->Persistent Reload: "
+
+# Confirm that new messages are written to runtime journal.
+rand_val=$(write_to_journal)
+verify_journals "$rand_val" true false "Volatile->Persistent New Message After Reload: "
+
+# Confirm that flushing writes to system journal.
+journalctl --flush
+verify_journals "$rand_val" false true "Volatile->Persistent New Message After Flush: "
+
+set +x
+dd if=/dev/urandom bs=1M count=5 | base64 | systemd-cat -t "$SYSLOG_ID"
+set -x
+
+max_size=$((2 * 1024 * 1024))
+total_size=$(du -sb "/var/log/journal/$MACHINE_ID" | cut -f1)
+if [ "$total_size" -lt "$max_size" ]; then
+    echo "ERROR: Journal size does not exceed SystemMaxUse limit"
+    cleanup
+    exit 1
+fi
+
+# Ensure reloading without limit does not interfere with SystemMaxUse test.
+systemctl reload systemd-journald.service
+total_size=$(du -sb "/var/log/journal/$MACHINE_ID" | cut -f1)
+if [ "$total_size" -lt "$max_size" ]; then
+    echo "ERROR: Journal size does not exceed SystemMaxUse limit"
+    cleanup
+    exit 1
+fi
+
+# Write to storage to prepare for SystemMaxFiles test.
+journalctl --flush
+
+num_var_journals=$(get_num_archived_journals "var")
+limit_var_journals=3
+if [ "$num_var_journals" -lt "$limit_var_journals" ]; then
+    echo "Creating archive files."
+    for (( i=0; i<=num_var_journals; i++ ))
+    do
+        echo "$TEST_MSG_PREFIX" | systemd-cat -t "$SYSLOG_ID"
+        journalctl --rotate
+    done
+
+    num_var_journals=$(get_num_archived_journals "var")
+    if [ "$num_var_journals" -lt "$limit_var_journals" ]; then
+        echo "ERROR: Number of journal files in /var/log/journal/$MACHINE_ID/ is less than $limit_var_journals"
+        cleanup
+        exit 1
+    fi
+fi
+
+# Reload with less SystemMaxUse and SystemMaxFiles.
+cat <<EOF >/run/systemd/journald.conf.d/reload.conf
+[Journal]
+Storage=persistent
+RuntimeMaxUse=2M
+SystemMaxUse=2M
+SystemMaxFiles=3
+EOF
+
+systemctl daemon-reload
+systemctl reload systemd-journald.service
+
+# New system journal needs to be created with the new configuration for change to take effect.
+journalctl --flush
+
+# Check SystemMaxFiles
+num_var_journals=$(get_num_archived_journals "var")
+if [ "$num_var_journals" -gt "$limit_var_journals" ]; then
+    echo "ERROR: Number of journal files in /var/log/journal/$MACHINE_ID/ is greater than $limit_var_journals"
+    cleanup
+    exit 1
+fi
+
+sleep 15
+
+# Check SystemMaxUse
+total_size=$(du -sb "/var/log/journal/$MACHINE_ID" | cut -f1)
+if [ "$total_size" -gt "$max_size" ]; then
+    echo "ERROR: Journal size exceeds SystemMaxUse limit"
+    cleanup
+    exit 1
+fi
+
+rm /run/systemd/journald.conf.d/reload.conf
+journalctl --vacuum-size=1M
+systemctl daemon-reload
+systemctl reload systemd-journald.service
index 45fd746526a5e0c359f517110db8795939b5a509..1fb080d26852f8b258de6e5eb964ed752a6afb2c 100644 (file)
@@ -57,7 +57,7 @@ StandardOutput=null
 SystemCallArchitectures=native
 SystemCallErrorNumber=EPERM
 SystemCallFilter=@system-service
-Type=notify
+Type=notify-reload
 PassEnvironment=TERM
 {{SERVICE_WATCHDOG}}