vmspawn,journal-remote: add journal forwarding disk usage options

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>
This commit is contained in:
Daan De Meyer
2026-04-22 20:05:38 +02:00
committed by Daan De Meyer
parent fcff958319
commit 012d87c1fc
7 changed files with 147 additions and 5 deletions
+5
View File
@@ -679,6 +679,11 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \
string format. Overrides the default maximum allowed size for a file-descriptor string format. Overrides the default maximum allowed size for a file-descriptor
based input record to be stored in the journal. 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 * `$SYSTEMD_CATALOG` – path to the compiled catalog database file to use for
`journalctl -x`, `journalctl --update-catalog`, `journalctl --list-catalog` `journalctl -x`, `journalctl --update-catalog`, `journalctl --list-catalog`
and related calls. and related calls.
+13
View File
@@ -333,6 +333,19 @@
<xi:include href="version-info.xml" xpointer="v239"/></listitem> <xi:include href="version-info.xml" xpointer="v239"/></listitem>
</varlistentry> </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="help" />
<xi:include href="standard-options.xml" xpointer="version" /> <xi:include href="standard-options.xml" xpointer="version" />
</variablelist> </variablelist>
+15
View File
@@ -668,6 +668,21 @@
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><option>--pass-ssh-key=<replaceable>BOOL</replaceable></option></term> <term><option>--pass-ssh-key=<replaceable>BOOL</replaceable></option></term>
+1 -1
View File
@@ -38,7 +38,7 @@ _systemd_vmspawn() {
[BIND]='--bind --bind-ro' [BIND]='--bind --bind-ro'
[SSH_KEY]='--ssh-key' [SSH_KEY]='--ssh-key'
[CONSOLE]='--console' [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_FORMAT]='--image-format'
[IMAGE_DISK_TYPE]='--image-disk-type' [IMAGE_DISK_TYPE]='--image-disk-type'
) )
+41
View File
@@ -25,6 +25,7 @@
#include "parse-argument.h" #include "parse-argument.h"
#include "parse-helpers.h" #include "parse-helpers.h"
#include "parse-util.h" #include "parse-util.h"
#include "path-util.h"
#include "pretty-print.h" #include "pretty-print.h"
#include "process-util.h" #include "process-util.h"
#include "socket-netlink.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( return config_parse_standard_file_with_dropins(
"systemd/journal-remote.conf", "systemd/journal-remote.conf",
"Remote\0", "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."); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --gnutls-log= is not available.");
#endif #endif
break; 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)); arg_files = strv_copy(option_parser_get_args(&state));
+7 -3
View 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 fork_notify(char * const *argv, PidRef *ret_pidref) {
int r; int r;
assert(!strv_isempty(argv));
assert(ret_pidref); assert(ret_pidref);
if (!is_main_thread()) if (!is_main_thread())
@@ -119,7 +118,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
if (r < 0) if (r < 0)
return r; return r;
if (DEBUG_LOGGING) { if (DEBUG_LOGGING && argv) {
_cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY); _cleanup_free_ char *l = quote_command_line(argv, SHELL_ESCAPE_EMPTY);
log_debug("Invoking '%s' as child.", strnull(l)); log_debug("Invoking '%s' as child.", strnull(l));
} }
@@ -141,6 +140,11 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
_exit(EXIT_MEMORY); _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); r = invoke_callout_binary(argv[0], argv);
log_debug_errno(r, "Failed to invoke %s: %m", argv[0]); log_debug_errno(r, "Failed to invoke %s: %m", argv[0]);
_exit(EXIT_EXEC); _exit(EXIT_EXEC);
@@ -164,7 +168,7 @@ int fork_notify(char * const *argv, PidRef *ret_pidref) {
*ret_pidref = TAKE_PIDREF(child); *ret_pidref = TAKE_PIDREF(child);
return 0; return 1; /* In the parent */
} }
static void fork_notify_terminate_internal(PidRef *pidref) { static void fork_notify_terminate_internal(PidRef *pidref) {
+65 -1
View File
@@ -34,6 +34,7 @@
#include "escape.h" #include "escape.h"
#include "ether-addr-util.h" #include "ether-addr-util.h"
#include "event-util.h" #include "event-util.h"
#include "exit-status.h"
#include "extract-word.h" #include "extract-word.h"
#include "fd-util.h" #include "fd-util.h"
#include "fileio.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_include = NULL;
static Set *arg_firmware_features_exclude = NULL; static Set *arg_firmware_features_exclude = NULL;
static char *arg_forward_journal = 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 int arg_register = -1;
static bool arg_keep_unit = false; static bool arg_keep_unit = false;
static sd_id128_t arg_uuid = {}; static sd_id128_t arg_uuid = {};
@@ -844,6 +849,30 @@ static int parse_argv(int argc, char *argv[]) {
return r; return r;
break; 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"): 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); r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key);
if (r < 0) if (r < 0)
@@ -923,6 +952,12 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_ram_slots > 0 && arg_ram_max == 0) if (arg_ram_slots > 0 && arg_ram_max == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size"); 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) if (arg_ephemeral && arg_extra_drives.n_drives > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); 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) if (!argv)
return log_oom(); 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) if (r < 0)
return r; 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) if (ret_listen_address)
*ret_listen_address = TAKE_PTR(listen_address); *ret_listen_address = TAKE_PTR(listen_address);