]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn,journal-remote: add journal forwarding disk usage options
authorDaan De Meyer <daan@amutable.com>
Fri, 27 Mar 2026 14:38:09 +0000 (14:38 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 22 Apr 2026 18:05:38 +0000 (20:05 +0200)
Add options to vmspawn to configure journal-remote disk usage limits
when forwarding journal entries from the VM. These are passed through
as --max-use=, --keep-free=, --max-file-size=, and --max-files=
command-line arguments to systemd-journal-remote.

Add --max-use=, --keep-free=, --max-file-size=, and --max-files=
command-line options to systemd-journal-remote to allow overriding the
corresponding settings from the configuration file.

Add $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE environment variable support
to systemd-journal-remote. When set, the specified file is used
instead of the default configuration file and drop-in directories.
When set to the empty string or /dev/null, configuration file parsing
is skipped entirely. vmspawn sets this to /dev/null in the child
process to avoid inheriting the host's journal-remote configuration.

Make fork_notify() argv parameter optional. When NULL is passed,
fork_notify() returns 0 in the child (with $NOTIFY_SOCKET set) and
lets the caller run custom code before exec. Returns 1 in the parent.
This allows vmspawn to set environment variables in the child without
polluting the parent process.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
docs/ENVIRONMENT.md
man/systemd-journal-remote.service.xml
man/systemd-vmspawn.xml
shell-completion/bash/systemd-vmspawn
src/journal-remote/journal-remote-main.c
src/shared/fork-notify.c
src/vmspawn/vmspawn.c

index 5390754661879ed314d7dc3a104939fe6469533e..53f4a1a60c8967907da48d95a09ca758277f7e37 100644 (file)
@@ -679,6 +679,11 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
   string format. Overrides the default maximum allowed size for a file-descriptor
   based input record to be stored in the journal.
 
+* `$SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE` – path to a configuration file for
+  `systemd-journal-remote`. When set, the specified file is used instead of the
+  default configuration file and drop-in directories. If set to the empty string
+  or `/dev/null`, configuration file parsing is skipped entirely.
+
 * `$SYSTEMD_CATALOG` – path to the compiled catalog database file to use for
   `journalctl -x`, `journalctl --update-catalog`, `journalctl --list-catalog`
   and related calls.
index d6258ce2fcd0f9b843b28919868bfaf0f9ff55e3..7beb96403d19528713a04f5a4a88677356ef10ee 100644 (file)
         <xi:include href="version-info.xml" xpointer="v239"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--max-use=</option></term>
+        <term><option>--keep-free=</option></term>
+        <term><option>--max-file-size=</option></term>
+        <term><option>--max-files=</option></term>
+
+        <listitem><para>These options override the corresponding settings from the configuration file
+        (see <citerefentry><refentrytitle>journal-remote.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+        See that page for the descriptions of these options.</para>
+
+        <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
     </variablelist>
index 5749136a5d310f7b7ce3fecddb5d6335c7132436..5c5ec4ccbcd554cf197de52ba0943f04c0f7cd99 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><option>--forward-journal-max-use=<replaceable>BYTES</replaceable></option></term>
+          <term><option>--forward-journal-keep-free=<replaceable>BYTES</replaceable></option></term>
+          <term><option>--forward-journal-max-file-size=<replaceable>BYTES</replaceable></option></term>
+          <term><option>--forward-journal-max-files=<replaceable>N</replaceable></option></term>
+
+          <listitem><para>These options configure the corresponding settings of
+          <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+          when forwarding journal entries from the VM. See
+          <citerefentry><refentrytitle>journal-remote.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for the descriptions of these settings.</para>
+
+          <xi:include href="version-info.xml" xpointer="v261"/></listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><option>--pass-ssh-key=<replaceable>BOOL</replaceable></option></term>
 
index b2b3f4f5a8ba38e817012bf301452764d23d48b5..efa0dae58de04c70df8f0af65fdeb504a6302859 100644 (file)
@@ -38,7 +38,7 @@ _systemd_vmspawn() {
         [BIND]='--bind --bind-ro'
         [SSH_KEY]='--ssh-key'
         [CONSOLE]='--console'
-        [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential'
+        [ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files'
         [IMAGE_FORMAT]='--image-format'
         [IMAGE_DISK_TYPE]='--image-disk-type'
     )
index d5277ad7ef1b3a7cec1cbc005ca7b1481081ce37..4867bf360946f8485eb27c93248d117a67c6b23d 100644 (file)
@@ -25,6 +25,7 @@
 #include "parse-argument.h"
 #include "parse-helpers.h"
 #include "parse-util.h"
+#include "path-util.h"
 #include "pretty-print.h"
 #include "process-util.h"
 #include "socket-netlink.h"
@@ -829,6 +830,22 @@ static int parse_config(void) {
                 {}
         };
 
+        const char *config_file = secure_getenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE");
+        if (config_file) {
+                if (isempty(config_file) || path_equal(config_file, "/dev/null"))
+                        return 0;
+
+                return config_parse(
+                                /* unit= */ NULL,
+                                config_file,
+                                /* f= */ NULL,
+                                "Remote\0",
+                                config_item_table_lookup, items,
+                                CONFIG_PARSE_WARN,
+                                /* userdata= */ NULL,
+                                /* ret_stat= */ NULL);
+        }
+
         return config_parse_standard_file_with_dropins(
                         "systemd/journal-remote.conf",
                         "Remote\0",
@@ -1004,6 +1021,30 @@ static int parse_argv(int argc, char *argv[]) {
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --gnutls-log= is not available.");
 #endif
                         break;
+
+                OPTION_LONG("max-use", "BYTES", "Maximum disk space to use"):
+                        r = parse_size(arg, 1024, &arg_max_use);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --max-use= value: %s", arg);
+                        break;
+
+                OPTION_LONG("keep-free", "BYTES", "Minimum disk space to keep free"):
+                        r = parse_size(arg, 1024, &arg_keep_free);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --keep-free= value: %s", arg);
+                        break;
+
+                OPTION_LONG("max-file-size", "BYTES", "Maximum size of individual journal files"):
+                        r = parse_size(arg, 1024, &arg_max_size);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --max-file-size= value: %s", arg);
+                        break;
+
+                OPTION_LONG("max-files", "N", "Maximum number of journal files to keep"):
+                        r = safe_atou64(arg, &arg_n_max_files);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --max-files= value: %s", arg);
+                        break;
                 }
 
         arg_files = strv_copy(option_parser_get_args(&state));
index 307197ac197019b1061236465103e4162bff9e5d..e9686786fe71926ba78823f20385bd94e0243426 100644 (file)
@@ -90,7 +90,6 @@ static int on_child_notify(sd_event_source *s, int fd, uint32_t revents, void *u
 int fork_notify(char * const *argv, PidRef *ret_pidref) {
         int r;
 
-        assert(!strv_isempty(argv));
         assert(ret_pidref);
 
         if (!is_main_thread())
@@ -119,7 +118,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
         if (r < 0)
                 return r;
 
-        if (DEBUG_LOGGING) {
+        if (DEBUG_LOGGING && argv) {
                 _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
                 log_debug("Invoking '%s' as child.", strnull(l));
         }
@@ -141,6 +140,11 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
                         _exit(EXIT_MEMORY);
                 }
 
+                if (!argv) {
+                        *ret_pidref = TAKE_PIDREF(child);
+                        return 0; /* Let the caller run custom code in the child */
+                }
+
                 r = invoke_callout_binary(argv[0], argv);
                 log_debug_errno(r, "Failed to invoke %s: %m", argv[0]);
                 _exit(EXIT_EXEC);
@@ -164,7 +168,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
 
         *ret_pidref = TAKE_PIDREF(child);
 
-        return 0;
+        return 1; /* In the parent */
 }
 
 static void fork_notify_terminate_internal(PidRef *pidref) {
index fa8c3402ffc1ac6c19dfe24bcfbe00a7df98c36a..b7f03501f76b0541c979ce490589d024aed8f949 100644 (file)
@@ -34,6 +34,7 @@
 #include "escape.h"
 #include "ether-addr-util.h"
 #include "event-util.h"
+#include "exit-status.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "fileio.h"
@@ -159,6 +160,10 @@ static bool arg_firmware_describe = false;
 static Set *arg_firmware_features_include = NULL;
 static Set *arg_firmware_features_exclude = NULL;
 static char *arg_forward_journal = NULL;
+static uint64_t arg_forward_journal_max_use = UINT64_MAX;
+static uint64_t arg_forward_journal_keep_free = UINT64_MAX;
+static uint64_t arg_forward_journal_max_file_size = UINT64_MAX;
+static uint64_t arg_forward_journal_max_files = UINT64_MAX;
 static int arg_register = -1;
 static bool arg_keep_unit = false;
 static sd_id128_t arg_uuid = {};
@@ -844,6 +849,30 @@ static int parse_argv(int argc, char *argv[]) {
                                 return r;
                         break;
 
+                OPTION_LONG("forward-journal-max-use", "BYTES", "Maximum disk space for forwarded journal"):
+                        r = parse_size(arg, 1024, &arg_forward_journal_max_use);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --forward-journal-max-use= value: %s", optarg);
+                        break;
+
+                OPTION_LONG("forward-journal-keep-free", "BYTES", "Minimum disk space to keep free"):
+                        r = parse_size(arg, 1024, &arg_forward_journal_keep_free);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --forward-journal-keep-free= value: %s", optarg);
+                        break;
+
+                OPTION_LONG("forward-journal-max-file-size", "BYTES", "Maximum size of individual journal files"):
+                        r = parse_size(arg, 1024, &arg_forward_journal_max_file_size);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --forward-journal-max-file-size= value: %s", optarg);
+                        break;
+
+                OPTION_LONG("forward-journal-max-files", "N", "Maximum number of journal files to keep"):
+                        r = safe_atou64(arg, &arg_forward_journal_max_files);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --forward-journal-max-files= value: %s", optarg);
+                        break;
+
                 OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"):
                         r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key);
                         if (r < 0)
@@ -923,6 +952,12 @@ static int parse_argv(int argc, char *argv[]) {
         if (arg_ram_slots > 0 && arg_ram_max == 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size");
 
+        if ((arg_forward_journal_max_use != UINT64_MAX ||
+             arg_forward_journal_keep_free != UINT64_MAX ||
+             arg_forward_journal_max_file_size != UINT64_MAX ||
+             arg_forward_journal_max_files != UINT64_MAX) && !arg_forward_journal)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--forward-journal-max-use=/--forward-journal-keep-free=/--forward-journal-max-file-size=/--forward-journal-max-files= require --forward-journal=.");
+
         if (arg_ephemeral && arg_extra_drives.n_drives > 0)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive=");
 
@@ -1628,9 +1663,38 @@ static int start_systemd_journal_remote(
         if (!argv)
                 return log_oom();
 
-        r = fork_notify(argv, ret_pidref);
+        if (arg_forward_journal_max_use != UINT64_MAX &&
+            strv_extendf(&argv, "--max-use=%" PRIu64, arg_forward_journal_max_use) < 0)
+                return log_oom();
+
+        if (arg_forward_journal_keep_free != UINT64_MAX &&
+            strv_extendf(&argv, "--keep-free=%" PRIu64, arg_forward_journal_keep_free) < 0)
+                return log_oom();
+
+        if (arg_forward_journal_max_file_size != UINT64_MAX &&
+            strv_extendf(&argv, "--max-file-size=%" PRIu64, arg_forward_journal_max_file_size) < 0)
+                return log_oom();
+
+        if (arg_forward_journal_max_files != UINT64_MAX &&
+            strv_extendf(&argv, "--max-files=%" PRIu64, arg_forward_journal_max_files) < 0)
+                return log_oom();
+
+        r = fork_notify(/* argv= */ NULL, ret_pidref);
         if (r < 0)
                 return r;
+        if (r == 0) {
+                /* In the child */
+                if (setenv("SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE",
+                            "/dev/null",
+                            /* overwrite= */ true) < 0) {
+                        log_debug_errno(errno, "Failed to set $SYSTEMD_JOURNAL_REMOTE_CONFIG_FILE: %m");
+                        _exit(EXIT_MEMORY);
+                }
+
+                r = invoke_callout_binary(argv[0], argv);
+                log_error_errno(r, "Failed to invoke %s: %m", argv[0]);
+                _exit(EXIT_EXEC);
+        }
 
         if (ret_listen_address)
                 *ret_listen_address = TAKE_PTR(listen_address);