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/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/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); 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/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/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; } 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-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/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/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..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"); @@ -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/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..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); + 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); + 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); + 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); + 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); + 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); } 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/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/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/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/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/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); diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index e765db4895..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) { @@ -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;