]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: add --forward-journal= and --forward-journal-*= options
authorDaan De Meyer <daan@amutable.com>
Sun, 29 Mar 2026 11:15:35 +0000 (11:15 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 22 Apr 2026 18:05:38 +0000 (20:05 +0200)
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>
man/systemd-nspawn.xml
shell-completion/bash/systemd-nspawn
shell-completion/zsh/_systemd-nspawn
src/libsystemd/sd-path/path-lookup.c
src/libsystemd/sd-path/path-lookup.h
src/nspawn/nspawn.c
src/shared/fork-notify.c
src/shared/fork-notify.h
src/ssh-generator/ssh-generator.c
src/ssh-generator/ssh-proxy.c
src/vmspawn/vmspawn.c

index 045aa60db81f7390d764cfa439ff7ec1f9e55428..54d6f83915c7afaf59f3ed409ade742cea56271a 100644 (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>
index b39d3cbd6d85422997baef9fc220b56279982b7c..85f3023083c55f5c12b84c4416c82e0b7a98e832 100644 (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
index ee28fa74759ab82ff7e5583d2119012317ec8229..10dc527236c52604558392686bb7d0df6e570118 100644 (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'
index 32c14fb14a7d5d8d509261bb785ba6b7c8e2ccfe..4e4abaebf8f1cc67df90d639773b110ce3c902cb 100644 (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",
index 67a4f5d69cf0f5db56c5c3aa80c4bad98b4d2d7e..8dcbf766e6a1f3f3ff5997a76b7cb20fb3179791 100644 (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. */
index c7ccfd49963d3c6fd4fb4be738bc7ce3f5759701..b1c8defb6c46a607531f5527ad893de2af374244 100644 (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"
 /* 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);
index e9686786fe71926ba78823f20385bd94e0243426..066ee29115faa81b56234ca7074dd83974e49691 100644 (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;
+}
index 2dbfe368a4664f9972249ec5a9e57c1d113e272c..cc241beff9335b9981b5ceb42dcab5c1b48b9268 100644 (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);
index bc01250a55b123ffde4b90c141674a9a82f135bc..ae062fc58da476bcc7cd7401b7e0b292dcac80fc 100644 (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;
 }
index fa42c5bee8021c58c34d2f2bd94fb6775aa70810..79d8ee056568e440a9412bb7c11b41fbfa4c7fac 100644 (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);
index b7f03501f76b0541c979ce490589d024aed8f949..8ccd631762ccaeaf4c2981b4fd3e2d2efc21628f 100644 (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;