nspawn: add --forward-journal= and --forward-journal-*= options

Add --forward-journal=FILE|DIR to forward the container's journal
entries to the host via systemd-journal-remote. When specified,
nspawn starts systemd-journal-remote listening on a Unix socket,
bind-mounts it into the container at /run/host/journal/socket, and
passes a journal.forward_to_socket credential pointing to it.

Add --forward-journal-max-use=, --forward-journal-keep-free=,
--forward-journal-max-file-size=, and --forward-journal-max-files=
to configure disk usage limits for the forwarded journal.

Consolidate nspawn's per-machine on-disk state under a single runtime
directory at /run/systemd/nspawn/<machine>/. The container rootdir
mount point moves from /tmp/nspawn-root-XXXXXX to <runtime_dir>/root,
the unix-export directory moves from
/run/systemd/nspawn/unix-export/<machine> to <runtime_dir>/unix-export,
and the journal-remote socket lives at
<runtime_dir>/journal-remote-socket. Update ssh-generator and
ssh-proxy to follow the new unix-export path layout.

Extract fork_journal_remote() into fork-notify.{c,h} as a shared
helper used by both nspawn and vmspawn, replacing vmspawn's
start_systemd_journal_remote().

Extract runtime_directory_make() into path-lookup.{c,h} as a shared
helper used by both nspawn and vmspawn, replacing vmspawn's inline
runtime directory creation logic.

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 012d87c1fc
commit cc8f398202
11 changed files with 346 additions and 152 deletions
+30
View File
@@ -1590,6 +1590,36 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
<xi:include href="version-info.xml" xpointer="v187"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--forward-journal=</option></term>
<listitem><para>Forward the container's journal to the host by starting
<citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>
listening on a Unix socket that is bind-mounted into the container. The container's
<citerefentry><refentrytitle>systemd-journald</refentrytitle><manvolnum>8</manvolnum></citerefentry>
connects to the socket via the <varname>journal.forward_to_socket</varname> credential and streams
journal entries to the host in real-time. Takes a path to a journal file or directory where the received
entries will be stored. If the path ends in <literal>.journal</literal>, entries are written to a single
file; otherwise, entries are split per host into the specified directory.</para>
<xi:include href="version-info.xml" xpointer="v261"/></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 container. 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>
</variablelist>
</refsect2><refsect2>
+3 -1
View File
@@ -78,7 +78,9 @@ _systemd_nspawn() {
--network-ipvlan --network-veth-extra --network-zone -p --port --system-call-filter --overlay
--overlay-ro --settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity
--resolv-conf --timezone --root-hash-sig --background --oci-bundle --verity-data
--restrict-address-families'
--restrict-address-families
--forward-journal --forward-journal-max-use --forward-journal-keep-free
--forward-journal-max-file-size --forward-journal-max-files'
)
_init_completion || return
+5
View File
@@ -54,4 +54,9 @@ _arguments \
"--notify-ready=[Control when the ready notification is sent]:options:(yes no)" \
"--suppress-sync=[Control whether to suppress disk synchronization for the container payload]:options:(yes no)" \
'--restrict-address-families=[Restrict socket address families accessible in the container.]: : _message "address families"' \
'--forward-journal=[Forward the container journal to the host via systemd-journal-remote.]: : _files' \
'--forward-journal-max-use=[Maximum disk space used by forwarded journal files.]: : _message bytes' \
'--forward-journal-keep-free=[Disk space to keep free for forwarded journal files.]: : _message bytes' \
'--forward-journal-max-file-size=[Maximum size of individual forwarded journal files.]: : _message bytes' \
'--forward-journal-max-files=[Maximum number of forwarded journal files.]: : _message number' \
'*:: : _normal'
+39
View File
@@ -5,6 +5,7 @@
#include "alloc-util.h"
#include "fs-util.h"
#include "log.h"
#include "mkdir.h"
#include "path-lookup.h"
#include "path-util.h"
#include "stat-util.h"
@@ -101,6 +102,44 @@ int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **re
return 1;
}
int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy) {
_cleanup_free_ char *dir = NULL, *destroy = NULL;
int r;
assert(subdir);
assert(ret_dir);
assert(ret_dir_destroy);
/* Use runtime_directory() (not _generic()) so that when we run in a systemd service
* with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the
* directory the service manager prepared for us. When the env var is unset, we fall back
* to the provided subdirectory under /run (or the $XDG_RUNTIME_DIR equivalent in user scope)
* and take care of creation and destruction ourselves. */
r = runtime_directory(scope, subdir, &dir);
if (r < 0)
return r;
if (r > 0) {
/* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and
* clean up the directory ourselves. */
destroy = strdup(dir);
if (!destroy)
return -ENOMEM;
r = mkdir_p(dir, 0755);
if (r < 0)
return r;
}
/* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and
* will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */
*ret_dir = TAKE_PTR(dir);
*ret_dir_destroy = TAKE_PTR(destroy);
return 0;
}
static const char* const user_data_unit_paths[] = {
"/usr/local/lib/systemd/user",
"/usr/local/share/systemd/user",
+1
View File
@@ -60,6 +60,7 @@ void lookup_paths_done(LookupPaths *p);
int config_directory_generic(RuntimeScope scope, const char *suffix, char **ret);
int runtime_directory_generic(RuntimeScope scope, const char *suffix, char **ret);
int runtime_directory(RuntimeScope scope, const char *fallback_suffix, char **ret);
int runtime_directory_make(RuntimeScope scope, const char *subdir, char **ret_dir, char **ret_dir_destroy);
/* We don't treat /etc/xdg/systemd/ in these functions as the xdg base dir spec suggests because we assume
* that is a link to /etc/systemd/ anyway. */
+145 -24
View File
@@ -49,6 +49,7 @@
#include "fd-util.h"
#include "fdset.h"
#include "fileio.h"
#include "fork-notify.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
@@ -95,6 +96,7 @@
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-lookup.h"
#include "path-util.h"
#include "pidref.h"
#include "polkit-agent.h"
@@ -133,6 +135,7 @@
/* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */
#define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify"
#define NSPAWN_MOUNT_TUNNEL "/run/host/incoming"
#define NSPAWN_JOURNAL_SOCKET_PATH "/run/host/journal/socket"
#define EXIT_FORCE_RESTART 133
@@ -262,6 +265,11 @@ static char *arg_background = NULL;
static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID;
static bool arg_cleanup = false;
static bool arg_ask_password = true;
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_DESTRUCTOR_REGISTER(arg_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
@@ -303,6 +311,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_restrict_address_families, set_freep);
STATIC_DESTRUCTOR_REGISTER(arg_settings_filename, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep);
static int parse_private_users(
const char *s,
@@ -1250,6 +1259,36 @@ static int parse_argv(int argc, char *argv[]) {
arg_settings_mask |= SETTING_LINK_JOURNAL;
break;
OPTION_LONG("forward-journal", "FILE|DIR", "Forward the container's journal to the host"):
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal);
if (r < 0)
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_GROUP("Mounts"): {}
OPTION_LONG("bind", "PATH[:PATH[:OPTIONS]]",
@@ -1446,6 +1485,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_caps_retain |= arg_private_network ? UINT64_C(1) << CAP_NET_ADMIN : 0;
arg_caps_retain &= ~minus;
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=.");
/* Make sure to parse environment before we reset the settings mask below */
r = parse_environment();
if (r < 0)
@@ -3708,9 +3753,10 @@ static int setup_notify_child(const void *directory) {
return TAKE_FD(fd);
}
static int setup_unix_export_dir_outside(char **ret) {
static int setup_unix_export_dir_outside(const char *runtime_dir, char **ret) {
int r;
assert(runtime_dir);
assert(ret);
if (arg_userns_mode == USER_NAMESPACE_MANAGED) {
@@ -3719,7 +3765,7 @@ static int setup_unix_export_dir_outside(char **ret) {
}
_cleanup_free_ char *p = NULL;
p = path_join("/run/systemd/nspawn/unix-export", arg_machine);
p = path_join(runtime_dir, "unix-export");
if (!p)
return log_oom();
@@ -5103,6 +5149,7 @@ static int load_oci_bundle(void) {
}
static int run_container(
const char *runtime_dir,
const char *directory,
int mount_fd,
DissectedImage *dissected_image,
@@ -5141,7 +5188,7 @@ static int run_container(
assert_se(sigaddset(&mask_chld, SIGCHLD) == 0);
/* Set up the unix export host directory on the host first */
r = setup_unix_export_dir_outside(&unix_export_host_dir);
r = setup_unix_export_dir_outside(runtime_dir, &unix_export_host_dir);
if (r < 0)
return r;
@@ -5907,18 +5954,22 @@ static int cant_be_in_netns(void) {
return 0;
}
static void cleanup_propagation_and_export_directories(void) {
const char *p;
static void cleanup_propagation_and_export_directories(const char *runtime_dir) {
_cleanup_free_ char *p = NULL;
if (!arg_machine || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)
if (!runtime_dir || arg_userns_mode == USER_NAMESPACE_MANAGED)
return;
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) rm_rf(p, REMOVE_ROOT);
p = path_join("/run/systemd/nspawn/propagate", arg_machine);
if (p)
(void) rm_rf(p, REMOVE_ROOT);
p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) rmdir(p);
free(p);
p = path_join(runtime_dir, "unix-export");
if (p) {
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) rmdir(p);
}
}
static int do_cleanup(void) {
@@ -5931,7 +5982,16 @@ static int do_cleanup(void) {
if (r < 0)
return r;
cleanup_propagation_and_export_directories();
_cleanup_free_ char *subdir = path_join("systemd/nspawn", arg_machine);
if (!subdir)
return log_oom();
_cleanup_free_ char *runtime_dir = NULL;
r = runtime_directory(arg_runtime_scope, subdir, &runtime_dir);
if (r < 0)
return r;
cleanup_propagation_and_export_directories(runtime_dir);
return 0;
}
@@ -5951,6 +6011,9 @@ static int run(int argc, char *argv[]) {
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
_cleanup_(pidref_done) PidRef pid = PIDREF_NULL;
_cleanup_(sd_varlink_unrefp) sd_varlink *nsresource_link = NULL, *mountfsd_link = NULL;
_cleanup_free_ char *runtime_dir = NULL, *subdir = NULL;
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL;
_cleanup_(fork_notify_terminate) PidRef journal_remote_pidref = PIDREF_NULL;
log_setup();
@@ -6440,6 +6503,22 @@ static int run(int argc, char *argv[]) {
} else
assert_not_reached();
subdir = path_join("systemd/nspawn", arg_machine);
if (!subdir) {
r = log_oom();
goto finish;
}
r = runtime_directory_make(
arg_runtime_scope,
subdir,
&runtime_dir,
&runtime_dir_destroy);
if (r < 0) {
log_error_errno(r, "Failed to create runtime directory: %m");
goto finish;
}
/* Create a temporary place to mount stuff. */
r = mkdtemp_malloc("/tmp/nspawn-root-XXXXXX", &rootdir);
if (r < 0) {
@@ -6486,8 +6565,61 @@ static int run(int argc, char *argv[]) {
expose_args.nfnl = nfnl;
}
if (arg_forward_journal) {
_cleanup_free_ char *socket_path = path_join(runtime_dir, "journal-remote-socket");
if (!socket_path) {
r = log_oom();
goto finish;
}
union sockaddr_union sa;
r = sockaddr_un_set_path(&sa.un, socket_path);
if (r < 0) {
log_error_errno(r, "Failed to set AF_UNIX path to '%s': %m", socket_path);
goto finish;
}
(void) sockaddr_un_unlink(&sa.un);
r = fork_journal_remote(
socket_path,
arg_forward_journal,
arg_forward_journal_max_use,
arg_forward_journal_keep_free,
arg_forward_journal_max_file_size,
arg_forward_journal_max_files,
&journal_remote_pidref);
if (r < 0)
goto finish;
CustomMount *cm = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_BIND);
if (!cm) {
r = log_oom();
goto finish;
}
cm->source = TAKE_PTR(socket_path);
cm->read_only = true;
cm->destination = strdup(NSPAWN_JOURNAL_SOCKET_PATH);
if (!cm->destination) {
r = log_oom();
goto finish;
}
r = machine_credential_add(&arg_credentials, "journal.forward_to_socket", NSPAWN_JOURNAL_SOCKET_PATH, SIZE_MAX);
if (r == -EEXIST) {
log_error_errno(r, "Credential 'journal.forward_to_socket' already set via --set-credential=, refusing --forward-journal=.");
goto finish;
}
if (r < 0) {
log_error_errno(r, "Failed to add 'journal.forward_to_socket' credential: %m");
goto finish;
}
}
for (;;) {
r = run_container(
runtime_dir,
rootdir,
mount_fd,
dissected_image,
@@ -6528,18 +6660,7 @@ finish:
log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image);
}
if (arg_machine && arg_userns_mode != USER_NAMESPACE_MANAGED) {
const char *p;
p = strjoina("/run/systemd/nspawn/propagate/", arg_machine);
(void) rm_rf(p, REMOVE_ROOT);
p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine);
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
(void) rmdir(p);
}
cleanup_propagation_and_export_directories();
cleanup_propagation_and_export_directories(runtime_dir);
expose_port_flush(nfnl, arg_expose_ports, AF_INET, &expose_args.address4);
expose_port_flush(nfnl, arg_expose_ports, AF_INET6, &expose_args.address6);
+94
View File
@@ -3,14 +3,19 @@
#include <stdlib.h>
#include <unistd.h>
#include "alloc-util.h"
#include "build-path.h"
#include "chase.h"
#include "chattr-util.h"
#include "escape.h"
#include "event-util.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fork-notify.h"
#include "log.h"
#include "notify-recv.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "runtime-scope.h"
@@ -238,3 +243,92 @@ int journal_fork(RuntimeScope scope, char * const* units, OutputMode output, Pid
return fork_notify(argv, ret_pidref);
}
int fork_journal_remote(
const char *listen_address,
const char *output,
uint64_t max_use,
uint64_t keep_free,
uint64_t max_file_size,
uint64_t max_files,
PidRef *ret_pidref) {
int r;
assert(listen_address);
assert(output);
assert(ret_pidref);
ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY;
if (endswith(output, ".journal"))
chase_flags |= CHASE_PARENT;
_cleanup_close_ int fd = -EBADF;
r = chase(output, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &fd);
if (r < 0)
return log_error_errno(r, "Failed to create journal directory for '%s': %m", output);
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
if (r < 0)
log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", output);
_cleanup_free_ char *sd_socket_activate = NULL;
r = find_executable("systemd-socket-activate", &sd_socket_activate);
if (r < 0)
return log_error_errno(r, "Failed to find systemd-socket-activate binary: %m");
_cleanup_free_ char *sd_journal_remote = NULL;
r = find_executable_full(
"systemd-journal-remote",
/* root= */ NULL,
STRV_MAKE(LIBEXECDIR),
/* use_path_envvar= */ true,
&sd_journal_remote,
/* ret_fd= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m");
_cleanup_strv_free_ char **argv = strv_new(
sd_socket_activate,
"--listen", listen_address,
sd_journal_remote,
"--output", output,
"--split-mode", endswith(output, ".journal") ? "none" : "host");
if (!argv)
return log_oom();
if (max_use != UINT64_MAX &&
strv_extendf(&argv, "--max-use=%" PRIu64, max_use) < 0)
return log_oom();
if (keep_free != UINT64_MAX &&
strv_extendf(&argv, "--keep-free=%" PRIu64, keep_free) < 0)
return log_oom();
if (max_file_size != UINT64_MAX &&
strv_extendf(&argv, "--max-file-size=%" PRIu64, max_file_size) < 0)
return log_oom();
if (max_files != UINT64_MAX &&
strv_extendf(&argv, "--max-files=%" PRIu64, 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_debug_errno(r, "Failed to invoke %s: %m", argv[0]);
_exit(EXIT_EXEC);
}
return 0;
}
+9
View File
@@ -11,3 +11,12 @@ void fork_notify_terminate(PidRef *pidref);
void fork_notify_terminate_many(sd_event_source **array, size_t n);
int journal_fork(RuntimeScope scope, char * const *units, OutputMode output, PidRef *ret_pidref);
int fork_journal_remote(
const char *listen_address,
const char *output,
uint64_t max_use,
uint64_t keep_free,
uint64_t max_file_size,
uint64_t max_files,
PidRef *ret_pidref);
+1 -1
View File
@@ -338,7 +338,7 @@ static int add_export_unix_socket(
return r;
log_debug("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n"
"→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host");
"→ connect via 'ssh unix/run/systemd/nspawn/\?\?\?/unix-export/ssh' from host");
return 0;
}
+2 -2
View File
@@ -349,11 +349,11 @@ static int process_machine(const char *machine, const char *port) {
if (!streq_ptr(p.service, "systemd-nspawn"))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Don't know how to SSH into '%s' container %s.", p.service, machine);
r = runtime_directory_generic(scope, "systemd/nspawn/unix-export", &path);
r = runtime_directory_generic(scope, "systemd/nspawn", &path);
if (r < 0)
return log_error_errno(r, "Failed to determine runtime directory: %m");
if (!path_extend(&path, machine, "ssh"))
if (!path_extend(&path, machine, "unix-export", "ssh"))
return log_oom();
r = is_socket(path);
+17 -124
View File
@@ -25,8 +25,6 @@
#include "bus-locator.h"
#include "bus-util.h"
#include "capability-util.h"
#include "chase.h"
#include "chattr-util.h"
#include "common-signal.h"
#include "copy.h"
#include "discover-image.h"
@@ -34,7 +32,6 @@
#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"
@@ -1621,87 +1618,6 @@ static int start_tpm(
return 0;
}
static int start_systemd_journal_remote(
const char *scope,
unsigned port,
const char *sd_socket_activate,
char **ret_listen_address,
PidRef *ret_pidref) {
int r;
assert(scope);
assert(sd_socket_activate);
_cleanup_free_ char *scope_prefix = NULL;
r = unit_name_to_prefix(scope, &scope_prefix);
if (r < 0)
return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
_cleanup_free_ char *listen_address = NULL;
if (asprintf(&listen_address, "vsock:2:%u", port) < 0)
return log_oom();
_cleanup_free_ char *sd_journal_remote = NULL;
r = find_executable_full(
"systemd-journal-remote",
/* root= */ NULL,
STRV_MAKE(LIBEXECDIR),
/* use_path_envvar= */ true, /* systemd-journal-remote should be installed in
* LIBEXECDIR, but for supporting fancy setups. */
&sd_journal_remote,
/* ret_fd= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m");
_cleanup_strv_free_ char **argv = strv_new(
sd_socket_activate,
"--listen", listen_address,
sd_journal_remote,
"--output", arg_forward_journal,
"--split-mode", endswith(arg_forward_journal, ".journal") ? "none" : "host");
if (!argv)
return log_oom();
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);
return 0;
}
static int discover_root(char **ret) {
int r;
_cleanup_(dissected_image_unrefp) DissectedImage *image = NULL;
@@ -2725,13 +2641,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return log_oom();
/* Create our runtime directory. We need this for the QMP varlink control socket, the QEMU
* config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material.
*
* Use runtime_directory() (not _generic()) so that when vmspawn runs in a systemd service
* with RuntimeDirectory= set, we pick up $RUNTIME_DIRECTORY and place our stuff into the
* directory the service manager prepared for us. When the env var is unset, we fall back
* to /run/systemd/vmspawn/<machine>/ (or the $XDG_RUNTIME_DIR equivalent in user scope)
* and take care of creation and destruction ourselves. */
* config file, TPM state, virtiofsd sockets, runtime mounts, and SSH key material. */
_cleanup_free_ char *runtime_dir = NULL, *runtime_dir_suffix = NULL;
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir_destroy = NULL;
@@ -2739,27 +2649,14 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (!runtime_dir_suffix)
return log_oom();
r = runtime_directory(arg_runtime_scope, runtime_dir_suffix, &runtime_dir);
r = runtime_directory_make(arg_runtime_scope, runtime_dir_suffix, &runtime_dir, &runtime_dir_destroy);
if (r < 0)
return log_error_errno(r, "Failed to determine runtime directory: %m");
if (r > 0) {
/* $RUNTIME_DIRECTORY was not set, so we got the fallback path and need to create and
* clean up the directory ourselves.
*
* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may
* already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale
* sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches
* nspawn's approach of not proactively cleaning stale runtime directories. */
r = mkdir_p(runtime_dir, 0755);
if (r < 0)
return log_error_errno(r, "Failed to create runtime directory '%s': %m", runtime_dir);
return log_error_errno(r, "Failed to create runtime directory: %m");
runtime_dir_destroy = strdup(runtime_dir);
if (!runtime_dir_destroy)
return log_oom();
}
/* When $RUNTIME_DIRECTORY is set the service manager created the directory for us and
* will destroy it (or preserve it, per RuntimeDirectoryPreserve=) when the service stops. */
/* If a previous vmspawn instance was killed without cleanup (e.g. SIGKILL), the directory may
* already exist with stale contents. This is harmless: varlink's sockaddr_un_unlink() removes stale
* sockets before bind(), and other files (QEMU config, SSH keys) are created fresh. This matches
* nspawn's approach of not proactively cleaning stale runtime directories. */
log_debug("Using runtime directory: %s", runtime_dir);
@@ -3471,25 +3368,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (arg_forward_journal) {
_cleanup_free_ char *listen_address = NULL;
ChaseFlags chase_flags = CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY;
if (endswith(arg_forward_journal, ".journal"))
chase_flags |= CHASE_PARENT;
_cleanup_close_ int journal_fd = -EBADF;
r = chase(arg_forward_journal, /* root= */ NULL, chase_flags, /* ret_path= */ NULL, &journal_fd);
if (r < 0)
return log_error_errno(r, "Failed to create journal directory for '%s': %m", arg_forward_journal);
r = chattr_fd(journal_fd, FS_NOCOW_FL, FS_NOCOW_FL);
if (r < 0)
log_debug_errno(r, "Failed to set NOCOW flag on journal directory for '%s', ignoring: %m", arg_forward_journal);
if (asprintf(&listen_address, "vsock:2:%u", child_cid) < 0)
return log_oom();
if (!GREEDY_REALLOC(children, n_children + 1))
return log_oom();
_cleanup_(fork_notify_terminate) PidRef child = PIDREF_NULL;
r = start_systemd_journal_remote(unit, child_cid, sd_socket_activate, &listen_address, &child);
r = fork_journal_remote(
listen_address,
arg_forward_journal,
arg_forward_journal_max_use,
arg_forward_journal_keep_free,
arg_forward_journal_max_file_size,
arg_forward_journal_max_files,
&child);
if (r < 0)
return r;