]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: extract snapshot creation into new shared helper
authorMichael Vogt <michael@amutable.com>
Sun, 1 Feb 2026 13:03:22 +0000 (14:03 +0100)
committerMichael Vogt <michael@amutable.com>
Mon, 2 Feb 2026 08:04:37 +0000 (09:04 +0100)
This commit extracts the creation of the snapshot for
nspawn into a shared helper. Its not a lot of code but its
subtle so having a single place seems beneficial.

While extracting the snapshot code got tweaked/simplified based
on the feedback from Yu (many thanks!).

With that we can simplify the snapshot delete and error handling
as well. The shared helper is then going to be used by vmspawn
when it also gets the --ephemeral option.

src/nspawn/nspawn.c
src/shared/meson.build
src/shared/snapshot-util.c [new file with mode: 0644]
src/shared/snapshot-util.h [new file with mode: 0644]

index 6cfc0e7395df1096e0c86bf51387ab45809c9b80..a010245da4d3d8fc540ba02aa709cb46e3db0214 100644 (file)
 #include "shift-uid.h"
 #include "signal-util.h"
 #include "siphash24.h"
+#include "snapshot-util.h"
 #include "socket-util.h"
 #include "stat-util.h"
 #include "stdio-util.h"
@@ -5915,7 +5916,7 @@ static int do_cleanup(void) {
 }
 
 static int run(int argc, char *argv[]) {
-        bool remove_directory = false, remove_image = false, veth_created = false;
+        bool remove_image = false, veth_created = false;
         _cleanup_close_ int master = -EBADF, userns_fd = -EBADF, mount_fd = -EBADF;
         _cleanup_fdset_free_ FDSet *fds = NULL;
         int r, ret = EXIT_SUCCESS;
@@ -5923,6 +5924,7 @@ static int run(int argc, char *argv[]) {
         struct ExposeArgs expose_args = {};
         _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
         _cleanup_(rmdir_and_freep) char *rootdir = NULL;
+        _cleanup_(rm_rf_subvolume_and_freep) char *snapshot_dir = NULL;
         _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
         _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL;
         _cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
@@ -6064,63 +6066,27 @@ static int run(int argc, char *argv[]) {
                 }
 
                 if (arg_ephemeral) {
-                        _cleanup_free_ char *np = NULL;
-
                         r = chase_and_update(&arg_directory, 0);
                         if (r < 0)
                                 goto finish;
 
-                        /* If the specified path is a mount point we generate the new snapshot immediately
-                         * inside it under a random name. However if the specified is not a mount point we
-                         * create the new snapshot in the parent directory, just next to it. */
-                        r = path_is_mount_point(arg_directory);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory);
-                                goto finish;
-                        }
-                        if (r > 0)
-                                r = tempfn_random_child(arg_directory, "machine.", &np);
-                        else
-                                r = tempfn_random(arg_directory, "machine.", &np);
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to generate name for directory snapshot: %m");
-                                goto finish;
-                        }
-
-                        /* We take an exclusive lock on this image, since it's our private, ephemeral copy
-                         * only owned by us and no one else. */
-                        r = image_path_lock(
+                        r = create_ephemeral_snapshot(
+                                        arg_directory,
                                         arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER,
-                                        np,
-                                        LOCK_EX|LOCK_NB,
-                                        arg_privileged ? &tree_global_lock : NULL,
-                                        &tree_local_lock);
+                                        arg_read_only,
+                                        &tree_global_lock,
+                                        &tree_local_lock,
+                                        &snapshot_dir);
                         if (r < 0) {
-                                log_error_errno(r, "Failed to lock %s: %m", np);
+                                log_error_errno(r, "Failed to create ephemeral snapshot: %m");
                                 goto finish;
                         }
 
-                        {
-                                BLOCK_SIGNALS(SIGINT);
-                                r = btrfs_subvol_snapshot_at(AT_FDCWD, arg_directory, AT_FDCWD, np,
-                                                             (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
-                                                             BTRFS_SNAPSHOT_FALLBACK_COPY |
-                                                             BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
-                                                             BTRFS_SNAPSHOT_RECURSIVE |
-                                                             BTRFS_SNAPSHOT_QUOTA |
-                                                             BTRFS_SNAPSHOT_SIGINT);
-                        }
-                        if (r == -EINTR) {
-                                log_error_errno(r, "Interrupted while copying file system tree to %s, removed again.", np);
+                        arg_directory = strdup(snapshot_dir);
+                        if (!arg_directory) {
+                                log_oom();
                                 goto finish;
                         }
-                        if (r < 0) {
-                                log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory);
-                                goto finish;
-                        }
-
-                        free_and_replace(arg_directory, np);
-                        remove_directory = true;
                 } else {
                         r = chase_and_update(&arg_directory, arg_template ? CHASE_NONEXISTENT : 0);
                         if (r < 0)
@@ -6471,14 +6437,6 @@ finish:
 
         pager_close();
 
-        if (remove_directory && arg_directory) {
-                int k;
-
-                k = rm_rf(arg_directory, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
-                if (k < 0)
-                        log_warning_errno(k, "Cannot remove '%s', ignoring: %m", arg_directory);
-        }
-
         if (remove_image && arg_image) {
                 if (unlink(arg_image) < 0)
                         log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image);
index 9fa976925a6e92e2870265396a6f969806b361f5..87802469de950f7f36eb557c8e6cd8210359d28f 100644 (file)
@@ -177,6 +177,7 @@ shared_sources = files(
         'sleep-config.c',
         'smack-util.c',
         'smbios11.c',
+        'snapshot-util.c',
         'socket-label.c',
         'socket-netlink.c',
         'specifier.c',
diff --git a/src/shared/snapshot-util.c b/src/shared/snapshot-util.c
new file mode 100644 (file)
index 0000000..a05cbcc
--- /dev/null
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/file.h>
+
+#include "alloc-util.h"
+#include "btrfs-util.h"
+#include "discover-image.h"
+#include "log.h"
+#include "mountpoint-util.h"
+#include "snapshot-util.h"
+#include "signal-util.h"
+#include "tmpfile-util.h"
+
+int create_ephemeral_snapshot(
+                const char *directory,
+                RuntimeScope scope,
+                bool read_only,
+                LockFile *tree_global_lock,
+                LockFile *tree_local_lock,
+                char **ret_new_path) {
+
+        _cleanup_free_ char *np = NULL;
+        int r;
+
+        /* If the specified path is a mount point we generate the new snapshot immediately
+         * inside it under a random name. However if the specified is not a mount point we
+         * create the new snapshot in the parent directory, just next to it. */
+        r = path_is_mount_point(directory);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to determine whether directory %s is mount point: %m", directory);
+        if (r > 0)
+                r = tempfn_random_child(directory, "snapshot.", &np);
+        else
+                r = tempfn_random(directory, "snapshot.", &np);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to generate name for directory snapshot: %m");
+
+        /* We take an exclusive lock on this image, since it's our private, ephemeral copy
+         * only owned by us and no one else. */
+        r = image_path_lock(
+                        scope,
+                        np,
+                        LOCK_EX|LOCK_NB,
+                        scope == RUNTIME_SCOPE_SYSTEM ? tree_global_lock : NULL,
+                        tree_local_lock);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to lock %s: %m", np);
+
+        {
+                BLOCK_SIGNALS(SIGINT);
+                r = btrfs_subvol_snapshot_at(AT_FDCWD, directory, AT_FDCWD, np,
+                                             (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
+                                             BTRFS_SNAPSHOT_FALLBACK_COPY |
+                                             BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
+                                             BTRFS_SNAPSHOT_RECURSIVE |
+                                             BTRFS_SNAPSHOT_QUOTA |
+                                             BTRFS_SNAPSHOT_SIGINT);
+        }
+        if (r < 0)
+                return log_debug_errno(r, "Failed to create snapshot %s from %s: %m", np, directory);
+
+        *ret_new_path = TAKE_PTR(np);
+
+        return 0;
+}
diff --git a/src/shared/snapshot-util.h b/src/shared/snapshot-util.h
new file mode 100644 (file)
index 0000000..18a593f
--- /dev/null
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "runtime-scope.h"
+
+/* create_ephemeral_snapshot - create a snapshot of the given directory.
+ *
+ * It will use a btrfs snapshot when available with fallback to traditional dir copy. It will set the global
+ * and local lock files based on the passed runtime scope. On success the new directory path is returned via
+ * `ret_new_path`.
+ *
+ * The caller is responsible for the cleanup of the directory, using `_cleanup_(rm_rf_subvolume_and_freep)`
+ * is recommended.
+ */
+int create_ephemeral_snapshot(
+                const char *directory,
+                RuntimeScope scope,
+                bool read_only,
+                LockFile *tree_global_lock,
+                LockFile *tree_local_lock,
+                char **ret_new_path);