diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 6cfc0e7395..a010245da4 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -107,6 +107,7 @@ #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); diff --git a/src/shared/meson.build b/src/shared/meson.build index 9fa976925a..87802469de 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -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 index 0000000000..a05cbcc8ea --- /dev/null +++ b/src/shared/snapshot-util.c @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#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 index 0000000000..18a593f5c8 --- /dev/null +++ b/src/shared/snapshot-util.h @@ -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);