From 6819924c30e56866fc1c49e460bb259846e3dc99 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 18:29:25 +0200 Subject: [PATCH 1/6] path-util: Make ret argument optional for path_extract_directory() --- src/basic/path-util.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index f832f85c68..a4c9d332f0 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -1116,7 +1116,9 @@ int path_extract_directory(const char *path, char **ret) { if (!path_is_valid(a)) return -EINVAL; - *ret = TAKE_PTR(a); + if (ret) + *ret = TAKE_PTR(a); + return 0; } From e54c79ccc2e90a375640815b05f28ec22664e44c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 15:15:08 +0200 Subject: [PATCH 2/6] btrfs-util: Move subvolume creation to basic/btrfs.h Also make btrfs_subvol_make() an openat style function. --- src/basic/btrfs.c | 91 ++++++++++++++++++++++++++++++ src/basic/btrfs.h | 9 +++ src/basic/meson.build | 1 + src/home/homework-directory.c | 2 +- src/home/homework-luks.c | 2 +- src/import/import-tar.c | 2 +- src/import/pull-tar.c | 2 +- src/shared/btrfs-util.c | 101 +--------------------------------- src/shared/btrfs-util.h | 6 +- src/shared/label-util.c | 2 +- src/test/test-btrfs.c | 18 +++--- src/tmpfiles/tmpfiles.c | 2 +- 12 files changed, 120 insertions(+), 118 deletions(-) create mode 100644 src/basic/btrfs.c create mode 100644 src/basic/btrfs.h diff --git a/src/basic/btrfs.c b/src/basic/btrfs.c new file mode 100644 index 0000000000..a13fec4873 --- /dev/null +++ b/src/basic/btrfs.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "btrfs.h" +#include "fd-util.h" +#include "fs-util.h" +#include "path-util.h" + +int btrfs_validate_subvolume_name(const char *name) { + + if (!filename_is_valid(name)) + return -EINVAL; + + if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) + return -E2BIG; + + return 0; +} + +static int extract_subvolume_name(const char *path, char **ret) { + _cleanup_free_ char *fn = NULL; + int r; + + assert(path); + assert(ret); + + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + + r = btrfs_validate_subvolume_name(fn); + if (r < 0) + return r; + + *ret = TAKE_PTR(fn); + return 0; +} + +int btrfs_subvol_make(int dir_fd, const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_free_ char *subvolume = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(!isempty(path)); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + r = path_extract_directory(path, NULL); + if (r >= 0) { + fd = open_parent_at(dir_fd, path, O_RDONLY|O_CLOEXEC|O_CLOEXEC, 0); + if (fd < 0) + return fd; + + dir_fd = fd; + } + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + return RET_NERRNO(ioctl(dir_fd, BTRFS_IOC_SUBVOL_CREATE, &args)); +} + +int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode) { + mode_t old, combined; + int r; + + assert(path); + + /* Let's work like mkdir(), i.e. take the specified mode, and mask it with the current umask. */ + old = umask(~mode); + combined = old | ~mode; + if (combined != ~mode) + umask(combined); + r = btrfs_subvol_make(dir_fd, path); + umask(old); + + if (r >= 0) + return 1; /* subvol worked */ + if (r != -ENOTTY) + return r; + + if (mkdirat(dir_fd, path, mode) < 0) + return -errno; + + return 0; /* plain directory */ +} diff --git a/src/basic/btrfs.h b/src/basic/btrfs.h new file mode 100644 index 0000000000..38be9d2b3b --- /dev/null +++ b/src/basic/btrfs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +int btrfs_validate_subvolume_name(const char *name); + +int btrfs_subvol_make(int dir_fd, const char *path); + +int btrfs_subvol_make_fallback(int dir_fd, const char *path, mode_t mode); diff --git a/src/basic/meson.build b/src/basic/meson.build index f6e6b6ace5..77ce2cf262 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -8,6 +8,7 @@ basic_sources = files( 'argv-util.c', 'arphrd-util.c', 'audit-util.c', + 'btrfs.c', 'build.c', 'bus-label.c', 'cap-list.c', diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 575ac52551..4ec5f3dde1 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -131,7 +131,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco case USER_SUBVOLUME: WITH_UMASK(0077) - r = btrfs_subvol_make(d); + r = btrfs_subvol_make(AT_FDCWD, d); if (r >= 0) { log_info("Subvolume created."); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index e41920ce9c..23a29d5811 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -2383,7 +2383,7 @@ int home_create_luks( return log_oom(); /* Prefer using a btrfs subvolume if we can, fall back to directory otherwise */ - r = btrfs_subvol_make_fallback(subdir, 0700); + r = btrfs_subvol_make_fallback(AT_FDCWD, subdir, 0700); if (r < 0) return log_error_errno(r, "Failed to create user directory in mounted image file: %m"); diff --git a/src/import/import-tar.c b/src/import/import-tar.c index dff6d3276a..ff32ae4e59 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -225,7 +225,7 @@ static int tar_import_fork_tar(TarImport *i) { (void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(d, 0755); + r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755); else r = RET_NERRNO(mkdir(d, 0755)); if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index e474dffa72..f9c82daec0 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -516,7 +516,7 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); if (i->flags & PULL_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(where, 0755); + r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); else r = RET_NERRNO(mkdir(where, 0755)); if (r == -EEXIST && (i->flags & PULL_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index d6479929c1..d91c69b624 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -42,36 +42,6 @@ * device nodes (that reference drivers) rather than fds to normal * files or directories. */ -static int validate_subvolume_name(const char *name) { - - if (!filename_is_valid(name)) - return -EINVAL; - - if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) - return -E2BIG; - - return 0; -} - -static int extract_subvolume_name(const char *path, char **ret) { - _cleanup_free_ char *fn = NULL; - int r; - - assert(path); - assert(ret); - - r = path_extract_filename(path, &fn); - if (r < 0) - return r; - - r = validate_subvolume_name(fn); - if (r < 0) - return r; - - *ret = TAKE_PTR(fn); - return 0; -} - int btrfs_is_subvol_at(int dir_fd, const char *path) { struct stat st; @@ -88,71 +58,6 @@ int btrfs_is_subvol_at(int dir_fd, const char *path) { return is_fs_type_at(dir_fd, path, BTRFS_SUPER_MAGIC); } -int btrfs_subvol_make_fd(int fd, const char *subvolume) { - struct btrfs_ioctl_vol_args args = {}; - _cleanup_close_ int real_fd = -EBADF; - int r; - - assert(subvolume); - - r = validate_subvolume_name(subvolume); - if (r < 0) - return r; - - /* If an O_PATH fd was specified, let's convert here to a proper one, as btrfs ioctl's can't deal - * with O_PATH. */ - fd = fd_reopen_condition(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY, O_PATH|O_DIRECTORY, &real_fd); - if (fd < 0) - return fd; - - strncpy(args.name, subvolume, sizeof(args.name)-1); - - return RET_NERRNO(ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args)); -} - -int btrfs_subvol_make(const char *path) { - _cleanup_free_ char *subvolume = NULL; - _cleanup_close_ int fd = -EBADF; - int r; - - assert(path); - - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_CLOEXEC, 0); - if (fd < 0) - return fd; - - return btrfs_subvol_make_fd(fd, subvolume); -} - -int btrfs_subvol_make_fallback(const char *path, mode_t mode) { - mode_t old, combined; - int r; - - assert(path); - - /* Let's work like mkdir(), i.e. take the specified mode, and mask it with the current umask. */ - old = umask(~mode); - combined = old | ~mode; - if (combined != ~mode) - umask(combined); - r = btrfs_subvol_make(path); - umask(old); - - if (r >= 0) - return 1; /* subvol worked */ - if (r != -ENOTTY) - return r; - - if (mkdir(path, mode) < 0) - return -errno; - - return 0; /* plain directory */ -} - int btrfs_subvol_set_read_only_at(int dir_fd, const char *path, bool b) { _cleanup_close_ int fd = -EBADF; uint64_t flags, nflags; @@ -1131,7 +1036,7 @@ int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags) if (fd < 0) return fd; - r = validate_subvolume_name(subvolume); + r = btrfs_validate_subvolume_name(subvolume); if (r < 0) return r; @@ -1551,7 +1456,7 @@ int btrfs_subvol_snapshot_at_full( if (new_fd < 0) return new_fd; - r = validate_subvolume_name(subvolume); + r = btrfs_validate_subvolume_name(subvolume); if (r < 0) return r; @@ -1565,7 +1470,7 @@ int btrfs_subvol_snapshot_at_full( if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) return -EISDIR; - r = btrfs_subvol_make_fd(new_fd, subvolume); + r = btrfs_subvol_make(new_fd, subvolume); if (r < 0) { if (ERRNO_IS_NOT_SUPPORTED(r) && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) { /* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */ diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index c972cc0744..22ff0c1115 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -7,6 +7,7 @@ #include "sd-id128.h" +#include "btrfs.h" #include "copy.h" #include "time-util.h" @@ -70,11 +71,6 @@ int btrfs_quota_scan_start(int fd); int btrfs_quota_scan_wait(int fd); int btrfs_quota_scan_ongoing(int fd); -int btrfs_subvol_make(const char *path); -int btrfs_subvol_make_fd(int fd, const char *subvolume); - -int btrfs_subvol_make_fallback(const char *path, mode_t); - int btrfs_subvol_snapshot_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, BtrfsSnapshotFlags flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); static inline int btrfs_subvol_snapshot_at(int dir_fdf, const char *from, int dir_fdt, const char *to, BtrfsSnapshotFlags flags) { return btrfs_subvol_snapshot_at_full(dir_fdf, from, dir_fdt, to, flags, NULL, NULL, NULL); diff --git a/src/shared/label-util.c b/src/shared/label-util.c index 3316c9ed37..7262da2b1f 100644 --- a/src/shared/label-util.c +++ b/src/shared/label-util.c @@ -108,7 +108,7 @@ int btrfs_subvol_make_label(const char *path) { if (r < 0) return r; - r = btrfs_subvol_make(path); + r = btrfs_subvol_make(AT_FDCWD, path); mac_selinux_create_file_clear(); if (r < 0) diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index ba09563058..5980727c85 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) { safe_close(fd); } - r = btrfs_subvol_make("/xxxtest"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxtest"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); @@ -97,33 +97,33 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); - r = btrfs_subvol_make("/xxxrectest"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); - r = btrfs_subvol_make("/xxxrectest/xxxrectest2"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest2"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); - r = btrfs_subvol_make("/xxxrectest/xxxrectest3"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); - r = btrfs_subvol_make("/xxxrectest/xxxrectest3/sub"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/xxxrectest3/sub"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); if (mkdir("/xxxrectest/dir", 0755) < 0) log_error_errno(errno, "Failed to make directory: %m"); - r = btrfs_subvol_make("/xxxrectest/dir/xxxrectest4"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); if (mkdir("/xxxrectest/dir/xxxrectest4/dir", 0755) < 0) log_error_errno(errno, "Failed to make directory: %m"); - r = btrfs_subvol_make("/xxxrectest/dir/xxxrectest4/dir/xxxrectest5"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxrectest/dir/xxxrectest4/dir/xxxrectest5"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); @@ -142,7 +142,7 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to recursively remove subvolume: %m"); - r = btrfs_subvol_make("/xxxquotatest"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); @@ -150,7 +150,7 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to set up auto qgroup: %m"); - r = btrfs_subvol_make("/xxxquotatest/beneath"); + r = btrfs_subvol_make(AT_FDCWD, "/xxxquotatest/beneath"); if (r < 0) log_error_errno(r, "Failed to make subvolume: %m"); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index e765db4895..bff09376dc 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1932,7 +1932,7 @@ static int create_directory_or_subvolume( subvol = false; else { WITH_UMASK((~mode) & 0777) - r = btrfs_subvol_make_fd(pfd, bn); + r = btrfs_subvol_make(pfd, bn); } } else r = 0; From bc6a6130ace354ac774cbc8e55dad2eec4a02643 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 15:32:22 +0200 Subject: [PATCH 3/6] fs-util: Add XO_SUBVOLUME flag for xopenat() When specified, xopenat() will try to create a btrfs subvolume and fall back to creating a regular directory. --- src/basic/fs-util.c | 6 +++++- src/basic/fs-util.h | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 804440ef2a..a9336f1a67 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -9,6 +9,7 @@ #include #include "alloc-util.h" +#include "btrfs.h" #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" @@ -1110,7 +1111,10 @@ int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags } if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) { - r = RET_NERRNO(mkdirat(dir_fd, path, mode)); + if (FLAGS_SET(xopen_flags, XO_SUBVOLUME)) + r = btrfs_subvol_make_fallback(dir_fd, path, mode); + else + r = RET_NERRNO(mkdirat(dir_fd, path, mode)); if (r == -EEXIST) { if (FLAGS_SET(open_flags, O_EXCL)) return -EEXIST; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index a19836d138..1023ab73ca 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -133,7 +133,8 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode); int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); typedef enum XOpenFlags { - XO_LABEL = 1 << 0, + XO_LABEL = 1 << 0, + XO_SUBVOLUME = 1 << 1, } XOpenFlags; int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); From ad6fae7ffcdd10bc2b1523bfb08143f285287222 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 15:33:15 +0200 Subject: [PATCH 4/6] copy: Add support for creating subvolumes to copy_tree_at() The subvolumes set is a set of source inodes similar to how the denylist hashmap contains source inodes as keys. It indicates directories in the source tree that should become subvolumes in the target tree. --- src/dissect/dissect.c | 4 ++-- src/home/homework.c | 2 +- src/import/pull-tar.c | 2 +- src/machine/machine-dbus.c | 4 ++-- src/partition/repart.c | 4 ++-- src/shared/copy.c | 16 ++++++++++------ src/shared/copy.h | 10 +++++----- src/test/test-copy.c | 14 +++++++------- src/tmpfiles/tmpfiles.c | 2 +- 9 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 4b830bf2a6..103ee41fe7 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -1414,9 +1414,9 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { if (errno != ENOENT) return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target); - r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL); + r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL); } else - r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL); + r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); diff --git a/src/home/homework.c b/src/home/homework.c index 9773bfa7a3..066483e342 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -1036,7 +1036,7 @@ static int copy_skel(int root_fd, const char *skel) { assert(root_fd >= 0); - r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE, NULL); + r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE, NULL, NULL); if (r == -ENOENT) { log_info("Skeleton directory %s missing, ignoring.", skel); return 0; diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index f9c82daec0..2db25192da 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -245,7 +245,7 @@ static int tar_pull_make_local_copy(TarPull *i) { BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| BTRFS_SNAPSHOT_RECURSIVE); else - r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL); + r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to create local image: %m"); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 5ecb570848..effee717b6 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -1019,9 +1019,9 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro * 0 or to the actual UID shift depending on the direction we copy. If no UID shift is set we'll copy * the UID/GIDs as they are. */ if (copy_from) - r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags, NULL); + r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, uid_shift == 0 ? UID_INVALID : 0, uid_shift == 0 ? GID_INVALID : 0, copy_flags, NULL, NULL); else - r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags, NULL); + r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, uid_shift == 0 ? UID_INVALID : uid_shift, uid_shift == 0 ? GID_INVALID : uid_shift, copy_flags, NULL, NULL); hostfd = safe_close(hostfd); containerfd = safe_close(containerfd); diff --git a/src/partition/repart.c b/src/partition/repart.c index 52b03ec7f0..b73427c9df 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -4381,14 +4381,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { pfd, fn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist); + denylist, NULL); } else r = copy_tree_at( sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist); + denylist, NULL); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", strempty(arg_root), *source, strempty(root), *target); diff --git a/src/shared/copy.c b/src/shared/copy.c index d8355a837c..04603fd20e 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -730,6 +730,7 @@ static int fd_copy_tree_generic( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, + Set *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -933,6 +934,7 @@ static int fd_copy_directory( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, + Set *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -986,7 +988,7 @@ static int fd_copy_directory( fdt = xopenat_lock(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|(exists ? 0 : O_CREAT|O_EXCL), - (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0), + (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0)|(set_contains(subvolumes, st) ? XO_SUBVOLUME : 0), st->st_mode & 07777, copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, LOCK_EX); @@ -1068,7 +1070,7 @@ static int fd_copy_directory( q = fd_copy_tree_generic(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, depth_left-1, override_uid, override_gid, copy_flags & ~COPY_LOCK_BSD, - denylist, hardlink_context, child_display_path, progress_path, + denylist, subvolumes, hardlink_context, child_display_path, progress_path, progress_bytes, userdata); if (q == -EINTR) /* Propagate SIGINT/SIGTERM up instantly */ @@ -1145,6 +1147,7 @@ static int fd_copy_tree_generic( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, + Set *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -1157,8 +1160,8 @@ static int fd_copy_tree_generic( if (S_ISDIR(st->st_mode)) return fd_copy_directory(df, from, st, dt, to, original_device, depth_left-1, override_uid, - override_gid, copy_flags, denylist, hardlink_context, display_path, - progress_path, progress_bytes, userdata); + override_gid, copy_flags, denylist, subvolumes, hardlink_context, + display_path, progress_path, progress_bytes, userdata); DenyType t = PTR_TO_INT(hashmap_get(denylist, st)); if (t == DENY_INODE) { @@ -1189,6 +1192,7 @@ int copy_tree_at_full( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, + Set *subvolumes, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata) { @@ -1204,7 +1208,7 @@ int copy_tree_at_full( return -errno; r = fd_copy_tree_generic(fdf, from, &st, fdt, to, st.st_dev, COPY_DEPTH_MAX, override_uid, - override_gid, copy_flags, denylist, NULL, NULL, progress_path, + override_gid, copy_flags, denylist, subvolumes, NULL, NULL, progress_path, progress_bytes, userdata); if (r < 0) return r; @@ -1273,7 +1277,7 @@ int copy_directory_at_full( COPY_DEPTH_MAX, UID_INVALID, GID_INVALID, copy_flags, - NULL, NULL, NULL, + NULL, NULL, NULL, NULL, progress_path, progress_bytes, userdata); diff --git a/src/shared/copy.h b/src/shared/copy.h index 3bf496e42b..4e4eb74986 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -76,12 +76,12 @@ static inline int copy_file_atomic(const char *from, const char *to, mode_t mode return copy_file_atomic_full(from, to, mode, 0, 0, copy_flags, NULL, NULL); } -int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist) { - return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL); +int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); +static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes) { + return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, denylist, subvolumes, NULL, NULL, NULL); } -static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist) { - return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL); +static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes) { + return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, subvolumes, NULL, NULL, NULL); } int copy_directory_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index e22a620ade..06fa05d8fa 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -67,11 +67,11 @@ TEST(copy_tree_replace_file) { /* The file exists- now overwrite original contents, and test the COPY_REPLACE flag. */ - assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK, NULL) == -EEXIST); + assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK, NULL, NULL) == -EEXIST); assert_se(read_file_at_and_streq(AT_FDCWD, dst, "foo foo foo\n")); - assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE, NULL) == 0); + assert_se(copy_tree(src, dst, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE, NULL, NULL) == 0); assert_se(read_file_at_and_streq(AT_FDCWD, dst, "bar bar\n")); } @@ -92,14 +92,14 @@ TEST(copy_tree_replace_dirs) { assert_se(write_string_file_at(dst, "bar", "dest file 2", WRITE_STRING_FILE_CREATE) == 0); /* Copying without COPY_REPLACE should fail because the destination file already exists. */ - assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK, NULL) == -EEXIST); + assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK, NULL, NULL) == -EEXIST); assert_se(read_file_at_and_streq(src, "foo", "src file 1\n")); assert_se(read_file_at_and_streq(src, "bar", "src file 2\n")); assert_se(read_file_at_and_streq(dst, "foo", "dest file 1\n")); assert_se(read_file_at_and_streq(dst, "bar", "dest file 2\n")); - assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE, NULL) == 0); + assert_se(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE, NULL, NULL) == 0); assert_se(read_file_at_and_streq(src, "foo", "src file 1\n")); assert_se(read_file_at_and_streq(src, "bar", "src file 2\n")); @@ -191,7 +191,7 @@ TEST(copy_tree) { assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_INODE)) >= 0); TAKE_PTR(cp); - assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist) == 0); + assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist, NULL) == 0); STRV_FOREACH(p, files) { _cleanup_free_ char *buf = NULL, *f = NULL, *c = NULL; @@ -243,8 +243,8 @@ TEST(copy_tree) { assert_se(stat(unixsockp, &st) >= 0); assert_se(S_ISSOCK(st.st_mode)); - assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist) < 0); - assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist) < 0); + assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist, NULL) < 0); + assert_se(copy_tree("/tmp/inexistent/foo/bar/fsdoi", copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK, denylist, NULL) < 0); ignorep = strjoina(copy_dir, "ignore/file"); assert_se(RET_NERRNO(access(ignorep, F_OK)) == -ENOENT); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index bff09376dc..376d3ad7d3 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -1866,7 +1866,7 @@ static int copy_files(Context *c, Item *i) { i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK | ((i->append_or_force) ? COPY_MERGE : COPY_MERGE_EMPTY) | COPY_MAC_CREATE | COPY_HARDLINKS, - NULL); + NULL, NULL); fd = openat(dfd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH); if (fd < 0) { From c55a97f1fd879f08052729f901fb899c3d8dfd52 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 16:39:39 +0200 Subject: [PATCH 5/6] mkdir: Add support for creating subvolumes to mkdir_p_root() We pass in the paths which should be subvolumes and try to create those as subvolumes if we can. --- src/basic/mkdir.c | 15 ++++++++++----- src/basic/mkdir.h | 2 +- src/partition/repart.c | 6 +++--- src/shared/dissect-image.c | 2 +- src/test/test-mkdir.c | 8 ++++---- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 41af1482bc..c770e5ed32 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -5,6 +5,7 @@ #include #include "alloc-util.h" +#include "btrfs.h" #include "chase.h" #include "fd-util.h" #include "format-util.h" @@ -209,7 +210,7 @@ int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, g return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper); } -int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) { +int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes) { _cleanup_free_ char *pp = NULL, *bn = NULL; _cleanup_close_ int dfd = -EBADF; int r; @@ -227,7 +228,7 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m return r; else { /* Extracting the parent dir worked, hence we aren't top-level? Recurse up first. */ - r = mkdir_p_root(root, pp, uid, gid, m); + r = mkdir_p_root(root, pp, uid, gid, m, subvolumes); if (r < 0) return r; @@ -242,11 +243,15 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m if (r < 0) return r; - if (mkdirat(dfd, bn, m) < 0) { - if (errno == EEXIST) + if (path_strv_contains(subvolumes, p)) + r = btrfs_subvol_make_fallback(dfd, bn, m); + else + r = RET_NERRNO(mkdirat(dfd, bn, m)); + if (r < 0) { + if (r == -EEXIST) return 0; - return -errno; + return r; } if (uid_is_valid(uid) || gid_is_valid(gid)) { diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h index 117d9e8061..e5387483e2 100644 --- a/src/basic/mkdir.h +++ b/src/basic/mkdir.h @@ -23,7 +23,7 @@ static inline int mkdir_parents(const char *path, mode_t mode) { int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); int mkdir_p(const char *path, mode_t mode); int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); -int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m); +int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes); /* The following are used to implement the mkdir_xyz_label() calls, don't use otherwise. */ typedef int (*mkdirat_func_t)(int dir_fd, const char *pathname, mode_t mode); diff --git a/src/partition/repart.c b/src/partition/repart.c index b73427c9df..0a335b2cc1 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -4368,7 +4368,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); if (r < 0) return log_error_errno(r, "Failed to create parent directory '%s': %m", dn); @@ -4408,7 +4408,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); if (r < 0) return log_error_errno(r, "Failed to create parent directory: %m"); @@ -4441,7 +4441,7 @@ static int do_make_directories(Partition *p, const char *root) { STRV_FOREACH(d, p->make_directories) { - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755); + r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, NULL); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index b4fbc77177..3b77706f8f 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1936,7 +1936,7 @@ static int mount_partition( if (directory) { /* Automatically create missing mount points inside the image, if necessary. */ - r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755); + r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL); if (r < 0 && r != -EROFS) return r; diff --git a/src/test/test-mkdir.c b/src/test/test-mkdir.c index 24933a3464..03e5a4997c 100644 --- a/src/test/test-mkdir.c +++ b/src/test/test-mkdir.c @@ -96,7 +96,7 @@ TEST(mkdir_p_root) { assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0); assert_se(p = path_join(tmp, "run/aaa/bbb")); - assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755) >= 0); + assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); assert_se(is_dir(p, false) > 0); assert_se(is_dir(p, true) > 0); @@ -109,18 +109,18 @@ TEST(mkdir_p_root) { p = mfree(p); assert_se(p = path_join(tmp, "var/run/hoge/foo/baz")); - assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755) >= 0); + assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); assert_se(is_dir(p, false) > 0); assert_se(is_dir(p, true) > 0); p = mfree(p); assert_se(p = path_join(tmp, "not-exists")); - assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOENT); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOENT); p = mfree(p); assert_se(p = path_join(tmp, "regular-file")); assert_se(touch(p) >= 0); - assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOTDIR); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOTDIR); /* FIXME: The tests below do not work. p = mfree(p); From 440f805c173f979cb5f7e28becd167467a1c5a2a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 16:44:30 +0200 Subject: [PATCH 6/6] repart: Add Subvolumes= setting This setting indicates which directories in the target partition should be btrfs subvolumes. If set, we'll try to create these directories as subvolumes. Note that this only works when running as root without --offline, as mkfs.btrfs does not support creating subvolumes. --- man/repart.d.xml | 15 +++++++ src/partition/repart.c | 90 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index a79724a93e..261c673249 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -500,6 +500,21 @@ attributes. + + Subvolumes= + + Takes one or more absolute paths, separated by whitespace, each declaring a directory + that should be a subvolume within the new file system. This option may be used more than once to + specify multiple directories. Note that this setting does not create the directories themselves, that + can be configured with MakeDirectories= and CopyFiles=. + + Note that this option only takes effect if the target filesystem supports subvolumes, such as + btrfs. + + Note that due to limitations of mkfs.btrfs, this option is only supported + when running with . + + Encrypt= diff --git a/src/partition/repart.c b/src/partition/repart.c index 0a335b2cc1..decf4e8589 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -243,6 +243,7 @@ typedef struct Partition { char **exclude_files_source; char **exclude_files_target; char **make_directories; + char **subvolumes; EncryptMode encrypt; VerityMode verity; char *verity_match_key; @@ -389,6 +390,7 @@ static Partition* partition_free(Partition *p) { strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); + strv_free(p->subvolumes); free(p->verity_match_key); free(p->roothash); @@ -417,6 +419,7 @@ static void partition_foreignize(Partition *p) { p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); + p->subvolumes = strv_free(p->subvolumes); p->verity_match_key = mfree(p->verity_match_key); p->priority = 0; @@ -1503,7 +1506,7 @@ static int config_parse_make_dirs( void *data, void *userdata) { - Partition *partition = ASSERT_PTR(data); + char ***sv = ASSERT_PTR(data); const char *p = ASSERT_PTR(rvalue); int r; @@ -1531,7 +1534,7 @@ static int config_parse_make_dirs( if (r < 0) continue; - r = strv_consume(&partition->make_directories, TAKE_PTR(d)); + r = strv_consume(sv, TAKE_PTR(d)); if (r < 0) return log_oom(); } @@ -1626,7 +1629,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, - { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p }, + { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, { "Partition", "Verity", config_parse_verity, 0, &p->verity }, { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, @@ -1636,6 +1639,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, + { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, {} }; int r; @@ -1749,6 +1753,10 @@ static int partition_read_definition(Partition *p, const char *path, const char "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s", verity_mode_to_string(p->verity)); + if (!strv_isempty(p->subvolumes) && arg_offline != 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Subvolumes= can only be used with --offline=no"); + /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((IN_SET(p->type.designator, PARTITION_ROOT_VERITY, @@ -4304,6 +4312,66 @@ static int make_copy_files_denylist( return 0; } +static int add_subvolume_path(const char *path, Set **subvolumes) { + _cleanup_free_ struct stat *st = NULL; + int r; + + assert(path); + assert(subvolumes); + + st = new(struct stat, 1); + if (!st) + return log_oom(); + + r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to stat source file '%s/%s': %m", strempty(arg_root), path); + + r = set_ensure_put(subvolumes, &inode_hash_ops, st); + if (r < 0) + return log_oom(); + if (r > 0) + TAKE_PTR(st); + + return 0; +} + +static int make_subvolumes_set( + Context *context, + const Partition *p, + const char *source, + const char *target, + Set **ret) { + _cleanup_set_free_ Set *subvolumes = NULL; + int r; + + assert(context); + assert(p); + assert(target); + assert(ret); + + STRV_FOREACH(subvolume, p->subvolumes) { + _cleanup_free_ char *path = NULL; + + const char *s = path_startswith(*subvolume, target); + if (!s) + continue; + + path = path_join(source, s); + if (!path) + return log_oom(); + + r = add_subvolume_path(path, &subvolumes); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(subvolumes); + return 0; +} + static int do_copy_files(Context *context, Partition *p, const char *root) { int r; @@ -4337,12 +4405,17 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { STRV_FOREACH_PAIR(source, target, p->copy_files) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; + _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; r = make_copy_files_denylist(context, p, *source, *target, &denylist); if (r < 0) return r; + r = make_subvolumes_set(context, p, *source, *target, &subvolumes_by_source_inode); + if (r < 0) + return r; + sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL); if (sfd < 0) return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source); @@ -4368,7 +4441,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory '%s': %m", dn); @@ -4381,14 +4454,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { pfd, fn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist, NULL); + denylist, subvolumes_by_source_inode); } else r = copy_tree_at( sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist, NULL); + denylist, subvolumes_by_source_inode); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", strempty(arg_root), *source, strempty(root), *target); @@ -4408,7 +4481,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory: %m"); @@ -4440,8 +4513,7 @@ static int do_make_directories(Partition *p, const char *root) { assert(root); STRV_FOREACH(d, p->make_directories) { - - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); }