--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "../global.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/fsuid.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#ifdef HAVE_LINUX_BTRFS_H
+# ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID
+# define btrfs_ioctl_vol_args_v2 override_btrfs_ioctl_vol_args_v2
+# endif
+#include <linux/btrfs.h>
+# undef btrfs_ioctl_vol_args_v2
+#endif
+
+#ifdef HAVE_LINUX_BTRFS_TREE_H
+#include <linux/btrfs_tree.h>
+#endif
+
+#include "missing.h"
+#include "utils.h"
+
+static char t_buf[PATH_MAX];
+
+#ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS
+
+#ifndef BTRFS_PATH_NAME_MAX
+#define BTRFS_PATH_NAME_MAX 4087
+#endif
+
+struct btrfs_ioctl_vol_args {
+ __s64 fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+#endif
+
+#ifndef HAVE_STRUCT_BTRFS_QGROUP_LIMIT
+struct btrfs_qgroup_limit {
+ __u64 flags;
+ __u64 max_rfer;
+ __u64 max_excl;
+ __u64 rsv_rfer;
+ __u64 rsv_excl;
+};
+#endif
+
+#ifndef HAVE_STRUCT_BTRFS_QGROUP_INHERIT
+struct btrfs_qgroup_inherit {
+ __u64 flags;
+ __u64 num_qgroups;
+ __u64 num_ref_copies;
+ __u64 num_excl_copies;
+ struct btrfs_qgroup_limit lim;
+ __u64 qgroups[0];
+};
+#endif
+
+#if !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2) || !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID)
+
+#ifndef BTRFS_SUBVOL_NAME_MAX
+#define BTRFS_SUBVOL_NAME_MAX 4039
+#endif
+
+struct btrfs_ioctl_vol_args_v2 {
+ __s64 fd;
+ __u64 transid;
+ __u64 flags;
+ union {
+ struct {
+ __u64 size;
+ struct btrfs_qgroup_inherit *qgroup_inherit;
+ };
+ __u64 unused[4];
+ };
+ union {
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+ __u64 devid;
+ __u64 subvolid;
+ };
+};
+#endif
+
+#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_ARGS
+
+#ifndef BTRFS_INO_LOOKUP_PATH_MAX
+#define BTRFS_INO_LOOKUP_PATH_MAX 4080
+#endif
+struct btrfs_ioctl_ino_lookup_args {
+ __u64 treeid;
+ __u64 objectid;
+ char name[BTRFS_INO_LOOKUP_PATH_MAX];
+};
+#endif
+
+#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_USER_ARGS
+
+#ifndef BTRFS_VOL_NAME_MAX
+#define BTRFS_VOL_NAME_MAX 255
+#endif
+
+#ifndef BTRFS_INO_LOOKUP_USER_PATH_MAX
+#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080 - BTRFS_VOL_NAME_MAX - 1)
+#endif
+
+struct btrfs_ioctl_ino_lookup_user_args {
+ __u64 dirid;
+ __u64 treeid;
+ char name[BTRFS_VOL_NAME_MAX + 1];
+ char path[BTRFS_INO_LOOKUP_USER_PATH_MAX];
+};
+#endif
+
+#ifndef HAVE_STRUCT_BTRFS_IOCTL_GET_SUBVOL_ROOTREF_ARGS
+
+#ifndef BTRFS_MAX_ROOTREF_BUFFER_NUM
+#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255
+#endif
+
+struct btrfs_ioctl_get_subvol_rootref_args {
+ __u64 min_treeid;
+ struct {
+ __u64 treeid;
+ __u64 dirid;
+ } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM];
+ __u8 num_items;
+ __u8 align[7];
+};
+#endif
+
+#ifndef BTRFS_IOCTL_MAGIC
+#define BTRFS_IOCTL_MAGIC 0x94
+#endif
+
+#ifndef BTRFS_IOC_SNAP_DESTROY
+#define BTRFS_IOC_SNAP_DESTROY \
+ _IOW(BTRFS_IOCTL_MAGIC, 15, struct btrfs_ioctl_vol_args)
+#endif
+
+#ifndef BTRFS_IOC_SNAP_DESTROY_V2
+#define BTRFS_IOC_SNAP_DESTROY_V2 \
+ _IOW(BTRFS_IOCTL_MAGIC, 63, struct btrfs_ioctl_vol_args_v2)
+#endif
+
+#ifndef BTRFS_IOC_SNAP_CREATE_V2
+#define BTRFS_IOC_SNAP_CREATE_V2 \
+ _IOW(BTRFS_IOCTL_MAGIC, 23, struct btrfs_ioctl_vol_args_v2)
+#endif
+
+#ifndef BTRFS_IOC_SUBVOL_CREATE_V2
+#define BTRFS_IOC_SUBVOL_CREATE_V2 \
+ _IOW(BTRFS_IOCTL_MAGIC, 24, struct btrfs_ioctl_vol_args_v2)
+#endif
+
+#ifndef BTRFS_IOC_SUBVOL_GETFLAGS
+#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64)
+#endif
+
+#ifndef BTRFS_IOC_SUBVOL_SETFLAGS
+#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64)
+#endif
+
+#ifndef BTRFS_IOC_INO_LOOKUP
+#define BTRFS_IOC_INO_LOOKUP \
+ _IOWR(BTRFS_IOCTL_MAGIC, 18, struct btrfs_ioctl_ino_lookup_args)
+#endif
+
+#ifndef BTRFS_IOC_INO_LOOKUP_USER
+#define BTRFS_IOC_INO_LOOKUP_USER \
+ _IOWR(BTRFS_IOCTL_MAGIC, 62, struct btrfs_ioctl_ino_lookup_user_args)
+#endif
+
+#ifndef BTRFS_IOC_GET_SUBVOL_ROOTREF
+#define BTRFS_IOC_GET_SUBVOL_ROOTREF \
+ _IOWR(BTRFS_IOCTL_MAGIC, 61, struct btrfs_ioctl_get_subvol_rootref_args)
+#endif
+
+#ifndef BTRFS_SUBVOL_RDONLY
+#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
+#endif
+
+#ifndef BTRFS_SUBVOL_SPEC_BY_ID
+#define BTRFS_SUBVOL_SPEC_BY_ID (1ULL << 4)
+#endif
+
+#ifndef BTRFS_FIRST_FREE_OBJECTID
+#define BTRFS_FIRST_FREE_OBJECTID 256ULL
+#endif
+
+static int btrfs_delete_subvolume(int parent_fd, const char *name)
+{
+ struct btrfs_ioctl_vol_args args = {};
+ size_t len;
+ int ret;
+
+ len = strlen(name);
+ if (len >= sizeof(args.name))
+ return -ENAMETOOLONG;
+
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+static int btrfs_delete_subvolume_id(int parent_fd, uint64_t subvolid)
+{
+ struct btrfs_ioctl_vol_args_v2 args = {};
+ int ret;
+
+ args.flags = BTRFS_SUBVOL_SPEC_BY_ID;
+ args.subvolid = subvolid;
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY_V2, &args);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+static int btrfs_create_subvolume(int parent_fd, const char *name)
+{
+ struct btrfs_ioctl_vol_args_v2 args = {};
+ size_t len;
+ int ret;
+
+ len = strlen(name);
+ if (len >= sizeof(args.name))
+ return -ENAMETOOLONG;
+
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ ret = ioctl(parent_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+static int btrfs_create_snapshot(int fd, int parent_fd, const char *name,
+ int flags)
+{
+ struct btrfs_ioctl_vol_args_v2 args = {
+ .fd = fd,
+ };
+ size_t len;
+ int ret;
+
+ if (flags & ~BTRFS_SUBVOL_RDONLY)
+ return -EINVAL;
+
+ len = strlen(name);
+ if (len >= sizeof(args.name))
+ return -ENAMETOOLONG;
+ memcpy(args.name, name, len);
+ args.name[len] = '\0';
+
+ if (flags & BTRFS_SUBVOL_RDONLY)
+ args.flags |= BTRFS_SUBVOL_RDONLY;
+ ret = ioctl(parent_fd, BTRFS_IOC_SNAP_CREATE_V2, &args);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+static int btrfs_get_subvolume_ro(int fd, bool *read_only_ret)
+{
+ uint64_t flags;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
+ if (ret < 0)
+ return -1;
+
+ *read_only_ret = flags & BTRFS_SUBVOL_RDONLY;
+ return 0;
+}
+
+static int btrfs_set_subvolume_ro(int fd, bool read_only)
+{
+ uint64_t flags;
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
+ if (ret < 0)
+ return -1;
+
+ if (read_only)
+ flags |= BTRFS_SUBVOL_RDONLY;
+ else
+ flags &= ~BTRFS_SUBVOL_RDONLY;
+
+ ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
+ if (ret < 0)
+ return -1;
+
+ return 0;
+}
+
+static int btrfs_get_subvolume_id(int fd, uint64_t *id_ret)
+{
+ struct btrfs_ioctl_ino_lookup_args args = {
+ .treeid = 0,
+ .objectid = BTRFS_FIRST_FREE_OBJECTID,
+ };
+ int ret;
+
+ ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
+ if (ret < 0)
+ return -1;
+
+ *id_ret = args.treeid;
+
+ return 0;
+}
+
+/*
+ * The following helpers are adapted from the btrfsutils library. We can't use
+ * the library directly since we need full control over how the subvolume
+ * iteration happens. We need to be able to check whether unprivileged
+ * subvolume iteration is possible, i.e. whether BTRFS_IOC_INO_LOOKUP_USER is
+ * available and also ensure that it is actually used when looking up paths.
+ */
+struct btrfs_stack {
+ uint64_t tree_id;
+ struct btrfs_ioctl_get_subvol_rootref_args rootref_args;
+ size_t items_pos;
+ size_t path_len;
+};
+
+struct btrfs_iter {
+ int fd;
+ int cur_fd;
+
+ struct btrfs_stack *search_stack;
+ size_t stack_len;
+ size_t stack_capacity;
+
+ char *cur_path;
+ size_t cur_path_capacity;
+};
+
+static struct btrfs_stack *top_stack_entry(struct btrfs_iter *iter)
+{
+ return &iter->search_stack[iter->stack_len - 1];
+}
+
+static int pop_stack(struct btrfs_iter *iter)
+{
+ struct btrfs_stack *top, *parent;
+ int fd, parent_fd;
+ size_t i;
+
+ if (iter->stack_len == 1) {
+ iter->stack_len--;
+ return 0;
+ }
+
+ top = top_stack_entry(iter);
+ iter->stack_len--;
+ parent = top_stack_entry(iter);
+
+ fd = iter->cur_fd;
+ for (i = parent->path_len; i < top->path_len; i++) {
+ if (i == 0 || iter->cur_path[i] == '/') {
+ parent_fd = openat(fd, "..", O_RDONLY);
+ if (fd != iter->cur_fd)
+ close(fd);
+ if (parent_fd == -1)
+ return -1;
+ fd = parent_fd;
+ }
+ }
+ if (iter->cur_fd != iter->fd)
+ close(iter->cur_fd);
+ iter->cur_fd = fd;
+
+ return 0;
+}
+
+static int append_stack(struct btrfs_iter *iter, uint64_t tree_id, size_t path_len)
+{
+ struct btrfs_stack *entry;
+
+ if (iter->stack_len >= iter->stack_capacity) {
+ size_t new_capacity = iter->stack_capacity * 2;
+ struct btrfs_stack *new_search_stack;
+#ifdef HAVE_REALLOCARRAY
+ new_search_stack = reallocarray(iter->search_stack, new_capacity,
+ sizeof(*iter->search_stack));
+#else
+ new_search_stack = realloc(iter->search_stack, new_capacity * sizeof(*iter->search_stack));
+#endif
+ if (!new_search_stack)
+ return -ENOMEM;
+
+ iter->stack_capacity = new_capacity;
+ iter->search_stack = new_search_stack;
+ }
+
+ entry = &iter->search_stack[iter->stack_len];
+
+ memset(entry, 0, sizeof(*entry));
+ entry->path_len = path_len;
+ entry->tree_id = tree_id;
+
+ if (iter->stack_len) {
+ struct btrfs_stack *top;
+ char *path;
+ int fd;
+
+ top = top_stack_entry(iter);
+ path = &iter->cur_path[top->path_len];
+ if (*path == '/')
+ path++;
+ fd = openat(iter->cur_fd, path, O_RDONLY);
+ if (fd == -1)
+ return -errno;
+
+ close(iter->cur_fd);
+ iter->cur_fd = fd;
+ }
+
+ iter->stack_len++;
+
+ return 0;
+}
+
+static int btrfs_iterator_start(int fd, uint64_t top, struct btrfs_iter **ret)
+{
+ struct btrfs_iter *iter;
+ int err;
+
+ iter = malloc(sizeof(*iter));
+ if (!iter)
+ return -ENOMEM;
+
+ iter->fd = fd;
+ iter->cur_fd = fd;
+
+ iter->stack_len = 0;
+ iter->stack_capacity = 4;
+ iter->search_stack = malloc(sizeof(*iter->search_stack) *
+ iter->stack_capacity);
+ if (!iter->search_stack) {
+ err = -ENOMEM;
+ goto out_iter;
+ }
+
+ iter->cur_path_capacity = 256;
+ iter->cur_path = malloc(iter->cur_path_capacity);
+ if (!iter->cur_path) {
+ err = -ENOMEM;
+ goto out_search_stack;
+ }
+
+ err = append_stack(iter, top, 0);
+ if (err)
+ goto out_cur_path;
+
+ *ret = iter;
+
+ return 0;
+
+out_cur_path:
+ free(iter->cur_path);
+out_search_stack:
+ free(iter->search_stack);
+out_iter:
+ free(iter);
+ return err;
+}
+
+static void btrfs_iterator_end(struct btrfs_iter *iter)
+{
+ if (iter) {
+ free(iter->cur_path);
+ free(iter->search_stack);
+ if (iter->cur_fd != iter->fd)
+ close(iter->cur_fd);
+ close(iter->fd);
+ free(iter);
+ }
+}
+
+static int __append_path(struct btrfs_iter *iter, const char *name,
+ size_t name_len, const char *dir, size_t dir_len,
+ size_t *path_len_ret)
+{
+ struct btrfs_stack *top = top_stack_entry(iter);
+ size_t path_len;
+ char *p;
+
+ path_len = top->path_len;
+ /*
+ * We need a joining slash if we have a current path and a subdirectory.
+ */
+ if (top->path_len && dir_len)
+ path_len++;
+ path_len += dir_len;
+ /*
+ * We need another joining slash if we have a current path and a name,
+ * but not if we have a subdirectory, because the lookup ioctl includes
+ * a trailing slash.
+ */
+ if (top->path_len && !dir_len && name_len)
+ path_len++;
+ path_len += name_len;
+
+ /* We need one extra character for the NUL terminator. */
+ if (path_len + 1 > iter->cur_path_capacity) {
+ char *tmp = realloc(iter->cur_path, path_len + 1);
+
+ if (!tmp)
+ return -ENOMEM;
+ iter->cur_path = tmp;
+ iter->cur_path_capacity = path_len + 1;
+ }
+
+ p = iter->cur_path + top->path_len;
+ if (top->path_len && dir_len)
+ *p++ = '/';
+ memcpy(p, dir, dir_len);
+ p += dir_len;
+ if (top->path_len && !dir_len && name_len)
+ *p++ = '/';
+ memcpy(p, name, name_len);
+ p += name_len;
+ *p = '\0';
+
+ *path_len_ret = path_len;
+
+ return 0;
+}
+
+static int get_subvolume_path(struct btrfs_iter *iter, uint64_t treeid,
+ uint64_t dirid, size_t *path_len_ret)
+{
+ struct btrfs_ioctl_ino_lookup_user_args args = {
+ .treeid = treeid,
+ .dirid = dirid,
+ };
+ int ret;
+
+ ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args);
+ if (ret == -1)
+ return -1;
+
+ return __append_path(iter, args.name, strlen(args.name), args.path,
+ strlen(args.path), path_len_ret);
+}
+
+static int btrfs_iterator_next(struct btrfs_iter *iter, char **path_ret,
+ uint64_t *id_ret)
+{
+ struct btrfs_stack *top;
+ uint64_t treeid, dirid;
+ size_t path_len;
+ int ret, err;
+
+ for (;;) {
+ for (;;) {
+ if (iter->stack_len == 0)
+ return 1;
+
+ top = top_stack_entry(iter);
+ if (top->items_pos < top->rootref_args.num_items) {
+ break;
+ } else {
+ ret = ioctl(iter->cur_fd,
+ BTRFS_IOC_GET_SUBVOL_ROOTREF,
+ &top->rootref_args);
+ if (ret == -1 && errno != EOVERFLOW)
+ return -1;
+ top->items_pos = 0;
+
+ if (top->rootref_args.num_items == 0) {
+ err = pop_stack(iter);
+ if (err)
+ return err;
+ }
+ }
+ }
+
+ treeid = top->rootref_args.rootref[top->items_pos].treeid;
+ dirid = top->rootref_args.rootref[top->items_pos].dirid;
+ top->items_pos++;
+ err = get_subvolume_path(iter, treeid, dirid, &path_len);
+ if (err) {
+ /* Skip the subvolume if we can't access it. */
+ if (errno == EACCES)
+ continue;
+ return err;
+ }
+
+ err = append_stack(iter, treeid, path_len);
+ if (err) {
+ /*
+ * Skip the subvolume if it does not exist (which can
+ * happen if there is another filesystem mounted over a
+ * parent directory) or we don't have permission to
+ * access it.
+ */
+ if (errno == ENOENT || errno == EACCES)
+ continue;
+ return err;
+ }
+
+ top = top_stack_entry(iter);
+ goto out;
+ }
+
+out:
+ if (path_ret) {
+ *path_ret = malloc(top->path_len + 1);
+ if (!*path_ret)
+ return -ENOMEM;
+ memcpy(*path_ret, iter->cur_path, top->path_len);
+ (*path_ret)[top->path_len] = '\0';
+ }
+ if (id_ret)
+ *id_ret = top->tree_id;
+ return 0;
+}
+
+#define BTRFS_SUBVOLUME1 "subvol1"
+#define BTRFS_SUBVOLUME1_SNAPSHOT1 "subvol1_snapshot1"
+#define BTRFS_SUBVOLUME1_SNAPSHOT1_RO "subvol1_snapshot1_ro"
+#define BTRFS_SUBVOLUME1_RENAME "subvol1_rename"
+#define BTRFS_SUBVOLUME2 "subvol2"
+
+static int btrfs_subvolumes_fsids_mapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_up())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must succeed.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: check ownership");
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: check ownership");
+
+ if (!caps_down())
+ die("failure: lower caps");
+
+ /*
+ * The filesystem is not mounted with user_subvol_rm_allowed so
+ * subvolume deletion must fail.
+ */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: check ownership");
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_fsids_mapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /* The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: check ownership");
+
+ /* remove subvolume */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_fsids_unmapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+
+ /* create directory for rename test */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ /* change ownership of all files to uid 0 */
+ if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ if (!switch_fsids(0, 0)) {
+ log_stderr("failure: switch_fsids");
+ goto out;
+ }
+
+ /*
+ * The caller's fsids don't have a mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+ if (errno != EOVERFLOW) {
+ log_stderr("failure: errno");
+ goto out;
+ }
+
+ /* try to rename a subvolume */
+ if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
+ BTRFS_SUBVOLUME1_RENAME)) {
+ log_stderr("failure: renameat");
+ goto out;
+ }
+ if (errno != EOVERFLOW) {
+ log_stderr("failure: errno");
+ goto out;
+ }
+
+ /* The caller is privileged over the inode so file deletion must work. */
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_fsids_unmapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ /* create directory for rename test */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ /* change ownership of all files to uid 0 */
+ if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ userns_fd = get_userns_fd(0, 30000, 10000);
+ if (userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ /*
+ * The caller's fsids don't have a mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
+ die("failure: btrfs_create_subvolume");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /* try to rename a subvolume */
+ if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
+ BTRFS_SUBVOLUME1_RENAME))
+ die("failure: renameat");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /*
+ * The caller is not privileged over the inode so subvolume
+ * deletion must fail.
+ */
+
+ /* remove subvolume */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+ safe_close(userns_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_mapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_up())
+ die("failure: raise caps");
+
+ /* The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* create read-only snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ safe_close(subvolume_fd);
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ /* remove read-write snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
+ die("failure: btrfs_delete_subvolume");
+
+ /* remove read-only snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
+ die("failure: btrfs_delete_subvolume");
+
+ /* create directory */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* create read-only snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove read-write snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove read-only snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_mapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ /* create read-only snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* remove read-write snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* remove read-only snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_unmapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* create directory for rename test */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ /* change ownership of all files to uid 0 */
+ if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
+ sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+
+ if (!switch_fsids(0, 0)) {
+ log_stderr("failure: switch_fsids");
+ goto out;
+ }
+
+ /*
+ * The caller's fsids don't have a mappings in the idmapped
+ * mount so any file creation must fail.
+ */
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor
+ * which we can't use with ioctl(). So let's reopen it as a
+ * proper file descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".",
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0)
+ die("failure: openat");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create directory */
+ if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
+ die("failure: btrfs_create_subvolume");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /* create read-write snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /* create read-only snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /* try to rename a directory */
+ if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
+ BTRFS_SUBVOLUME1_RENAME))
+ die("failure: renameat");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ if (!caps_down())
+ die("failure: caps_down");
+
+ /* create read-write snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* create read-only snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /*
+ * The caller is not privileged over the inode so subvolume
+ * deletion must fail.
+ */
+
+ /* remove directory */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ if (!caps_up())
+ die("failure: caps_down");
+
+ /*
+ * The caller is privileged over the inode so subvolume
+ * deletion must work.
+ */
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_unmapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF,
+ userns_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* create directory for rename test */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ /* change ownership of all files to uid 0 */
+ if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ userns_fd = get_userns_fd(0, 30000, 10000);
+ if (userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
+ sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor
+ * which we can't use with ioctl(). So let's reopen it as a
+ * proper file descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".",
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ /*
+ * The caller's fsids don't have a mappings in the idmapped
+ * mount so any file creation must fail.
+ */
+
+ /* create directory */
+ if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
+ die("failure: btrfs_create_subvolume");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /* create read-write snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* create read-only snapshot */
+ if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ /* try to rename a directory */
+ if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
+ BTRFS_SUBVOLUME1_RENAME))
+ die("failure: renameat");
+ if (errno != EOVERFLOW)
+ die("failure: errno");
+
+ /*
+ * The caller is not privileged over the inode so subvolume
+ * deletion must fail.
+ */
+
+ /* remove directory */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(subvolume_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must succedd.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: check ownership");
+
+ /*
+ * The scratch device is mounted with user_subvol_rm_allowed so
+ * subvolume deletion must succeed.
+ */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /* The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: check ownership");
+
+ /*
+ * The scratch device is mounted with user_subvol_rm_allowed so
+ * subvolume deletion must succeed.
+ */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must succeed.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ /* create read-only snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ safe_close(subvolume_fd);
+
+ /* remove subvolume */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ /* remove read-write snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
+ die("failure: btrfs_delete_subvolume");
+
+ /* remove read-only snapshot */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
+ die("failure: btrfs_delete_subvolume");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ safe_close(subvolume_fd);
+
+ /* remove read-only snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ /* create read-only snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ BTRFS_SUBVOL_RDONLY))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_delete_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ /* remove read-write snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
+ die("failure: btrfs_delete_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ /* remove read-only snapshot */
+ if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
+ die("failure: btrfs_delete_subvolume");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ safe_close(subvolume_fd);
+
+ /* remove read-only snapshot */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
+ die("failure: btrfs_delete_subvolume");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_delete_by_spec_id(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF;
+ uint64_t subvolume_id1 = -EINVAL, subvolume_id2 = -EINVAL;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "A")) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "B")) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ subvolume_fd = openat(info->t_mnt_scratch_fd, "B", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(subvolume_fd, "C")) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ safe_close(subvolume_fd);
+
+ subvolume_fd = openat(info->t_mnt_scratch_fd, "A", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id1)) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+ subvolume_fd = openat(info->t_mnt_scratch_fd, "B/C", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id2)) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+ if (sys_mount(info->t_device_scratch, info->t_mountpoint, "btrfs", 0, "subvol=B/C")) {
+ log_stderr("failure: mount");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(-EBADF, info->t_mountpoint,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ /*
+ * The subvolume isn't exposed in the idmapped mount so
+ * delation via spec id must fail.
+ */
+ if (!btrfs_delete_subvolume_id(tree_fd, subvolume_id1))
+ die("failure: btrfs_delete_subvolume_id");
+ if (errno != EOPNOTSUPP)
+ die("failure: errno");
+
+ if (btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id1))
+ die("failure: btrfs_delete_subvolume_id");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+ sys_umount2(info->t_mountpoint, MNT_DETACH);
+ btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id2);
+ btrfs_delete_subvolume(info->t_mnt_scratch_fd, "B");
+
+ return fret;
+}
+
+static int btrfs_subvolumes_setflags_fsids_mapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /* The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (!read_only)
+ die("failure: not read_only");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_setflags_fsids_mapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /* The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must fail.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (!read_only)
+ die("failure: not read_only");
+
+ if (btrfs_set_subvolume_ro(subvolume_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_setflags_fsids_unmapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (!switch_fsids(0, 0))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids don't have mappings in the idmapped mount
+ * so any file creation must fail.
+ */
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (!btrfs_set_subvolume_ro(subvolume_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_subvolumes_setflags_fsids_unmapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ userns_fd = get_userns_fd(0, 30000, 10000);
+ if (userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ /*
+ * The caller's fsids don't have mappings in the idmapped mount
+ * so any file creation must fail.
+ */
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ if (!switch_userns(userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (!btrfs_set_subvolume_ro(subvolume_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+ safe_close(userns_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_setflags_fsids_mapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int snapshot_fd = -EBADF, subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ if (!switch_fsids(10000, 10000))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids now have mappings in the idmapped mount
+ * so any file creation must succeed.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
+ die("failure: expected_uid_gid");
+
+ snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (snapshot_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (btrfs_set_subvolume_ro(snapshot_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (!read_only)
+ die("failure: not read_only");
+
+ if (btrfs_set_subvolume_ro(snapshot_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ safe_close(snapshot_fd);
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_setflags_fsids_mapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int snapshot_fd = -EBADF, subvolume_fd = -EBADF;
+ bool read_only = false;
+
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ /*
+ * The caller's fsids now have mappings in the idmapped mount so
+ * any file creation must succeed.
+ */
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
+ die("failure: btrfs_create_subvolume");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0)
+ die("failure: openat");
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, tree_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
+ die("failure: btrfs_create_snapshot");
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
+ die("failure: expected_uid_gid");
+
+ snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (snapshot_fd < 0)
+ die("failure: openat");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (btrfs_set_subvolume_ro(snapshot_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (!read_only)
+ die("failure: not read_only");
+
+ if (btrfs_set_subvolume_ro(snapshot_fd, false))
+ die("failure: btrfs_set_subvolume_ro");
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ safe_close(snapshot_fd);
+ safe_close(subvolume_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_setflags_fsids_unmapped(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) {
+ log_stderr("failure: btrfs_create_snapshot");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int snapshot_fd = -EBADF;
+ bool read_only = false;
+
+ snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (snapshot_fd < 0)
+ die("failure: openat");
+
+ if (!switch_fsids(0, 0))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: raise caps");
+
+ /*
+ * The caller's fsids don't have mappings in the idmapped mount
+ * so any file creation must fail.
+ */
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (!btrfs_set_subvolume_ro(snapshot_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ safe_close(snapshot_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(subvolume_fd);
+ safe_close(tree_fd);
+
+ return fret;
+}
+
+static int btrfs_snapshots_setflags_fsids_unmapped_userns(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF,
+ userns_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+
+ if (!caps_supported())
+ return 0;
+
+ /* Changing mount properties on a detached mount. */
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ /* Changing mount properties on a detached mount. */
+ userns_fd = get_userns_fd(0, 30000, 10000);
+ if (userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create read-write snapshot */
+ if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd,
+ BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) {
+ log_stderr("failure: btrfs_create_snapshot");
+ goto out;
+ }
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) {
+ log_stderr("failure: expected_uid_gid");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ int snapshot_fd = -EBADF;
+ bool read_only = false;
+
+ /*
+ * The caller's fsids don't have mappings in the idmapped mount
+ * so any file creation must fail.
+ */
+
+ snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
+ O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (snapshot_fd < 0)
+ die("failure: openat");
+
+
+ if (!switch_userns(userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
+ info->t_overflowuid, info->t_overflowgid))
+ die("failure: expected_uid_gid");
+
+ /*
+ * The caller's fsids don't have mappings in the idmapped mount
+ * so any file creation must fail.
+ */
+
+ if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
+ die("failure: btrfs_get_subvolume_ro");
+
+ if (read_only)
+ die("failure: read_only");
+
+ if (!btrfs_set_subvolume_ro(snapshot_fd, true))
+ die("failure: btrfs_set_subvolume_ro");
+ if (errno != EPERM)
+ die("failure: errno");
+
+ safe_close(snapshot_fd);
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ /* remove directory */
+ if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
+ log_stderr("failure: btrfs_delete_subvolume");
+ goto out;
+ }
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(attr.userns_fd);
+ safe_close(open_tree_fd);
+ safe_close(subvolume_fd);
+ safe_close(tree_fd);
+ safe_close(userns_fd);
+
+ return fret;
+}
+
+#define BTRFS_SUBVOLUME_SUBVOL1 "subvol1"
+#define BTRFS_SUBVOLUME_SUBVOL2 "subvol2"
+#define BTRFS_SUBVOLUME_SUBVOL3 "subvol3"
+#define BTRFS_SUBVOLUME_SUBVOL4 "subvol4"
+
+#define BTRFS_SUBVOLUME_SUBVOL1_ID 0
+#define BTRFS_SUBVOLUME_SUBVOL2_ID 1
+#define BTRFS_SUBVOLUME_SUBVOL3_ID 2
+#define BTRFS_SUBVOLUME_SUBVOL4_ID 3
+
+#define BTRFS_SUBVOLUME_DIR1 "dir1"
+#define BTRFS_SUBVOLUME_DIR2 "dir2"
+
+#define BTRFS_SUBVOLUME_MNT "mnt_subvolume1"
+
+#define BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3 "subvol1/subvol3"
+#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2 "subvol1/dir1/dir2"
+#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4 "subvol1/dir1/dir2/subvol4"
+
+/*
+ * We create the following mount layout to test lookup:
+ *
+ * |-/mnt/test /dev/loop0 btrfs rw,relatime,space_cache,subvolid=5,subvol=/
+ * | |-/mnt/test/mnt1 /dev/loop1[/subvol1] btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=268,subvol=/subvol1
+ * '-/mnt/scratch /dev/loop1 btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=5,subvol=/
+ */
+static int btrfs_subvolume_lookup_user(const struct vfstest_info *info)
+{
+ int fret = -1, i;
+ int dir1_fd = -EBADF, dir2_fd = -EBADF, mnt_fd = -EBADF,
+ open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
+ int subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID + 1];
+ uint64_t subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID + 1];
+ uint64_t subvolid = -EINVAL;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ };
+ pid_t pid;
+ struct btrfs_iter *iter;
+
+ if (!caps_supported())
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++)
+ subvolume_fds[i] = -EBADF;
+
+ for (i = 0; i < ARRAY_SIZE(subvolume_ids); i++)
+ subvolume_ids[i] = -EINVAL;
+
+ if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL1)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL2)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] = openat(info->t_mnt_scratch_fd,
+ BTRFS_SUBVOLUME_SUBVOL1,
+ O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ /* create subvolume */
+ if (btrfs_create_subvolume(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_SUBVOL3)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (mkdirat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ dir1_fd = openat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1,
+ O_CLOEXEC | O_DIRECTORY);
+ if (dir1_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (mkdirat(dir1_fd, BTRFS_SUBVOLUME_DIR2, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ dir2_fd = openat(dir1_fd, BTRFS_SUBVOLUME_DIR2, O_CLOEXEC | O_DIRECTORY);
+ if (dir2_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_create_subvolume(dir2_fd, BTRFS_SUBVOLUME_SUBVOL4)) {
+ log_stderr("failure: btrfs_create_subvolume");
+ goto out;
+ }
+
+ if (mkdirat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
+
+ snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT);
+ if (sys_mount(info->t_device_scratch, t_buf, "btrfs", 0,
+ "subvol=" BTRFS_SUBVOLUME_SUBVOL1)) {
+ log_stderr("failure: mount");
+ goto out;
+ }
+
+ mnt_fd = openat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, O_CLOEXEC | O_DIRECTORY);
+ if (mnt_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (chown_r(info->t_mnt_scratch_fd, ".", 1000, 1000)) {
+ log_stderr("failure: chown_r");
+ goto out;
+ }
+
+ subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] = openat(info->t_mnt_scratch_fd,
+ BTRFS_SUBVOLUME_SUBVOL2,
+ O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID],
+ &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL1_ID])) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID],
+ &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL2_ID])) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+ subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] = openat(info->t_mnt_scratch_fd,
+ BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3,
+ O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID],
+ &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+ subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] = openat(info->t_mnt_scratch_fd,
+ BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4,
+ O_CLOEXEC | O_DIRECTORY);
+ if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID],
+ &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])) {
+ log_stderr("failure: btrfs_get_subvolume_id");
+ goto out;
+ }
+
+
+ if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) {
+ log_stderr("failure: fchmod");
+ goto out;
+ }
+
+ if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) {
+ log_stderr("failure: fchmod");
+ goto out;
+ }
+
+ attr.userns_fd = get_userns_fd(0, 10000, 10000);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ open_tree_fd = sys_open_tree(mnt_fd, "",
+ AT_EMPTY_PATH |
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
+
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
+
+ /*
+ * The open_tree() syscall returns an O_PATH file descriptor which we
+ * can't use with ioctl(). So let's reopen it as a proper file
+ * descriptor.
+ */
+ tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
+ if (tree_fd < 0) {
+ log_stderr("failure: openat");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ bool subvolume3_found = false, subvolume4_found = false;
+
+ if (!switch_fsids(11000, 11000))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: lower caps");
+
+ if (btrfs_iterator_start(tree_fd, 0, &iter))
+ die("failure: btrfs_iterator_start");
+
+ for (;;) {
+ char *subvol_path = NULL;
+ int ret;
+
+ ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
+ if (ret == 1)
+ break;
+ else if (ret)
+ die("failure: btrfs_iterator_next");
+
+ if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] &&
+ subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
+ die("failure: subvolume id %llu->%s",
+ (long long unsigned)subvolid, subvol_path);
+
+ if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])
+ subvolume3_found = true;
+
+ if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
+ subvolume4_found = true;
+
+ free(subvol_path);
+ }
+ btrfs_iterator_end(iter);
+
+ if (!subvolume3_found || !subvolume4_found)
+ die("failure: subvolume id");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ bool subvolume3_found = false, subvolume4_found = false;
+
+ if (!switch_userns(attr.userns_fd, 0, 0, false))
+ die("failure: switch_userns");
+
+ if (btrfs_iterator_start(tree_fd, 0, &iter))
+ die("failure: btrfs_iterator_start");
+
+ for (;;) {
+ char *subvol_path = NULL;
+ int ret;
+
+ ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
+ if (ret == 1)
+ break;
+ else if (ret)
+ die("failure: btrfs_iterator_next");
+
+ if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] &&
+ subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
+ die("failure: subvolume id %llu->%s",
+ (long long unsigned)subvolid, subvol_path);
+
+ if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])
+ subvolume3_found = true;
+
+ if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
+ subvolume4_found = true;
+
+ free(subvol_path);
+ }
+ btrfs_iterator_end(iter);
+
+ if (!subvolume3_found || !subvolume4_found)
+ die("failure: subvolume id");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ bool subvolume_found = false;
+
+ if (!switch_fsids(0, 0))
+ die("failure: switch fsids");
+
+ if (!caps_down())
+ die("failure: lower caps");
+
+ if (btrfs_iterator_start(tree_fd, 0, &iter))
+ die("failure: btrfs_iterator_start");
+
+ for (;;) {
+ char *subvol_path = NULL;
+ int ret;
+
+ ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
+ if (ret == 1)
+ break;
+ else if (ret)
+ die("failure: btrfs_iterator_next");
+
+ free(subvol_path);
+
+ subvolume_found = true;
+ break;
+ }
+ btrfs_iterator_end(iter);
+
+ if (subvolume_found)
+ die("failure: subvolume id");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ userns_fd = get_userns_fd(0, 30000, 10000);
+ if (userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ bool subvolume_found = false;
+
+ if (!switch_userns(userns_fd, 0, 0, true))
+ die("failure: switch_userns");
+
+ if (btrfs_iterator_start(tree_fd, 0, &iter))
+ die("failure: btrfs_iterator_start");
+
+ for (;;) {
+ char *subvol_path = NULL;
+ int ret;
+
+ ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
+ if (ret == 1)
+ break;
+ else if (ret)
+ die("failure: btrfs_iterator_next");
+
+ free(subvol_path);
+
+ subvolume_found = true;
+ break;
+ }
+ btrfs_iterator_end(iter);
+
+ if (subvolume_found)
+ die("failure: subvolume id");
+
+ exit(EXIT_SUCCESS);
+ }
+ if (wait_for_pid(pid))
+ goto out;
+
+ fret = 0;
+ log_debug("Ran test");
+out:
+ safe_close(dir1_fd);
+ safe_close(dir2_fd);
+ safe_close(open_tree_fd);
+ safe_close(tree_fd);
+ safe_close(userns_fd);
+ for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++)
+ safe_close(subvolume_fds[i]);
+ snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT);
+ sys_umount2(t_buf, MNT_DETACH);
+ unlinkat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, AT_REMOVEDIR);
+
+ return fret;
+}
+
+static const struct test_struct t_btrfs[] = {
+ { btrfs_subvolumes_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids", },
+ { btrfs_subvolumes_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids inside user namespace", },
+ { btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option", },
+ { btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option inside user namespace", },
+ { btrfs_subvolumes_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids", },
+ { btrfs_subvolumes_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids inside user namespace", },
+ { btrfs_snapshots_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids", },
+ { btrfs_snapshots_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids inside user namespace", },
+ { btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option", },
+ { btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option inside user namespace", },
+ { btrfs_snapshots_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids", },
+ { btrfs_snapshots_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids inside user namespace", },
+ { btrfs_delete_by_spec_id, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion by spec id", },
+ { btrfs_subvolumes_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids", },
+ { btrfs_subvolumes_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids inside user namespace", },
+ { btrfs_subvolumes_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids", },
+ { btrfs_subvolumes_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids inside user namespace", },
+ { btrfs_snapshots_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids", },
+ { btrfs_snapshots_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids inside user namespace", },
+ { btrfs_snapshots_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids", },
+ { btrfs_snapshots_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids inside user namespace", },
+ { btrfs_subvolume_lookup_user, T_REQUIRE_IDMAPPED_MOUNTS, "test unprivileged subvolume lookup", },
+};
+
+const struct test_suite s_btrfs_idmapped_mounts = {
+ .tests = t_btrfs,
+ .nr_tests = ARRAY_SIZE(t_btrfs),
+};
#include <sys/xattr.h>
#include <unistd.h>
-#ifdef HAVE_LINUX_BTRFS_H
-# ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID
-# define btrfs_ioctl_vol_args_v2 override_btrfs_ioctl_vol_args_v2
-# endif
-#include <linux/btrfs.h>
-# undef btrfs_ioctl_vol_args_v2
-#endif
-
-#ifdef HAVE_LINUX_BTRFS_TREE_H
-#include <linux/btrfs_tree.h>
-#endif
-
+#include "btrfs-idmapped-mounts.h"
#include "idmapped-mounts.h"
#include "missing.h"
#include "utils.h"
return fret;
}
-#ifndef HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS
-
-#ifndef BTRFS_PATH_NAME_MAX
-#define BTRFS_PATH_NAME_MAX 4087
-#endif
+#define USER1 "fsgqa"
+#define USER2 "fsgqa2"
-struct btrfs_ioctl_vol_args {
- __s64 fd;
- char name[BTRFS_PATH_NAME_MAX + 1];
-};
-#endif
+/**
+ * lookup_ids - lookup uid and gid for a username
+ * @name: [in] name of the user
+ * @uid: [out] pointer to the user-ID
+ * @gid: [out] pointer to the group-ID
+ *
+ * Lookup the uid and gid of a user.
+ *
+ * Return: On success, true is returned.
+ * On error, false is returned.
+ */
+static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid)
+{
+ bool bret = false;
+ struct passwd *pwentp = NULL;
+ struct passwd pwent;
+ char *buf;
+ ssize_t bufsize;
+ int ret;
-#ifndef HAVE_STRUCT_BTRFS_QGROUP_LIMIT
-struct btrfs_qgroup_limit {
- __u64 flags;
- __u64 max_rfer;
- __u64 max_excl;
- __u64 rsv_rfer;
- __u64 rsv_excl;
-};
-#endif
+ bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (bufsize < 0)
+ bufsize = 1024;
-#ifndef HAVE_STRUCT_BTRFS_QGROUP_INHERIT
-struct btrfs_qgroup_inherit {
- __u64 flags;
- __u64 num_qgroups;
- __u64 num_ref_copies;
- __u64 num_excl_copies;
- struct btrfs_qgroup_limit lim;
- __u64 qgroups[0];
-};
-#endif
+ buf = malloc(bufsize);
+ if (!buf)
+ return bret;
-#if !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2) || !defined(HAVE_STRUCT_BTRFS_IOCTL_VOL_ARGS_V2_SUBVOLID)
+ ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp);
+ if (!ret && pwentp) {
+ *uid = pwent.pw_uid;
+ *gid = pwent.pw_gid;
+ bret = true;
+ }
-#ifndef BTRFS_SUBVOL_NAME_MAX
-#define BTRFS_SUBVOL_NAME_MAX 4039
-#endif
+ free(buf);
+ return bret;
+}
-struct btrfs_ioctl_vol_args_v2 {
- __s64 fd;
- __u64 transid;
- __u64 flags;
- union {
- struct {
- __u64 size;
- struct btrfs_qgroup_inherit *qgroup_inherit;
- };
- __u64 unused[4];
- };
- union {
- char name[BTRFS_SUBVOL_NAME_MAX + 1];
- __u64 devid;
- __u64 subvolid;
+/**
+ * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly")
+ *
+ * Test that ->setattr() works correctly for idmapped mounts with circular
+ * idmappings such as:
+ *
+ * b:1000:1001:1
+ * b:1001:1000:1
+ *
+ * Assume a directory /source with two files:
+ *
+ * /source/file1 | 1000:1000
+ * /source/file2 | 1001:1001
+ *
+ * and we create an idmapped mount of /source at /target with an idmapped of:
+ *
+ * mnt_userns: 1000:1001:1
+ * 1001:1000:1
+ *
+ * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000:
+ *
+ * /target/file1 | 1001:1001
+ * /target/file2 | 1000:1000
+ *
+ * Because in essence the idmapped mount switches ownership for {g,u}id 1000
+ * and {g,u}id 1001.
+ *
+ * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from
+ * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
+ * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from
+ * {g,u}id 1001 in the idmapped mount to {g,u}id 1001.
+ * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from
+ * {g,u}id 1001 in the idmapped mount to {g,u}id 1000.
+ * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
+ * {g,u}id of the file.
+ * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from
+ * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
+ * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
+ * {g,u}id of the file.
+ * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must
+ * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount
+ * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any
+ * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL.
+ * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other
+ * {g,u}id are unmapped.
+ */
+static int setattr_fix_968219708108(const struct vfstest_info *info)
+{
+ int fret = -1;
+ int open_tree_fd = -EBADF;
+ struct mount_attr attr = {
+ .attr_set = MOUNT_ATTR_IDMAP,
+ .userns_fd = -EBADF,
};
-};
-#endif
-
-#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_ARGS
-
-#ifndef BTRFS_INO_LOOKUP_PATH_MAX
-#define BTRFS_INO_LOOKUP_PATH_MAX 4080
-#endif
-struct btrfs_ioctl_ino_lookup_args {
- __u64 treeid;
- __u64 objectid;
- char name[BTRFS_INO_LOOKUP_PATH_MAX];
-};
-#endif
-
-#ifndef HAVE_STRUCT_BTRFS_IOCTL_INO_LOOKUP_USER_ARGS
+ int ret;
+ uid_t user1_uid, user2_uid;
+ gid_t user1_gid, user2_gid;
+ pid_t pid;
+ struct list idmap;
+ struct list *it_cur, *it_next;
-#ifndef BTRFS_VOL_NAME_MAX
-#define BTRFS_VOL_NAME_MAX 255
-#endif
+ if (!caps_supported())
+ return 0;
-#ifndef BTRFS_INO_LOOKUP_USER_PATH_MAX
-#define BTRFS_INO_LOOKUP_USER_PATH_MAX (4080 - BTRFS_VOL_NAME_MAX - 1)
-#endif
+ list_init(&idmap);
-struct btrfs_ioctl_ino_lookup_user_args {
- __u64 dirid;
- __u64 treeid;
- char name[BTRFS_VOL_NAME_MAX + 1];
- char path[BTRFS_INO_LOOKUP_USER_PATH_MAX];
-};
-#endif
+ if (!lookup_ids(USER1, &user1_uid, &user1_gid)) {
+ log_stderr("failure: lookup_user");
+ goto out;
+ }
-#ifndef HAVE_STRUCT_BTRFS_IOCTL_GET_SUBVOL_ROOTREF_ARGS
+ if (!lookup_ids(USER2, &user2_uid, &user2_gid)) {
+ log_stderr("failure: lookup_user");
+ goto out;
+ }
-#ifndef BTRFS_MAX_ROOTREF_BUFFER_NUM
-#define BTRFS_MAX_ROOTREF_BUFFER_NUM 255
-#endif
+ log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)",
+ user1_uid, user1_gid, user2_uid, user2_gid);
-struct btrfs_ioctl_get_subvol_rootref_args {
- __u64 min_treeid;
- struct {
- __u64 treeid;
- __u64 dirid;
- } rootref[BTRFS_MAX_ROOTREF_BUFFER_NUM];
- __u8 num_items;
- __u8 align[7];
-};
-#endif
+ if (mkdirat(info->t_dir1_fd, DIR1, 0777)) {
+ log_stderr("failure: mkdirat");
+ goto out;
+ }
-#ifndef BTRFS_IOCTL_MAGIC
-#define BTRFS_IOCTL_MAGIC 0x94
-#endif
+ if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) {
+ log_stderr("failure: mknodat");
+ goto out;
+ }
-#ifndef BTRFS_IOC_SNAP_DESTROY
-#define BTRFS_IOC_SNAP_DESTROY \
- _IOW(BTRFS_IOCTL_MAGIC, 15, struct btrfs_ioctl_vol_args)
-#endif
+ if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) {
+ log_stderr("failure: chown_r");
+ goto out;
+ }
-#ifndef BTRFS_IOC_SNAP_DESTROY_V2
-#define BTRFS_IOC_SNAP_DESTROY_V2 \
- _IOW(BTRFS_IOCTL_MAGIC, 63, struct btrfs_ioctl_vol_args_v2)
-#endif
+ if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) {
+ log_stderr("failure: mknodat");
+ goto out;
+ }
-#ifndef BTRFS_IOC_SNAP_CREATE_V2
-#define BTRFS_IOC_SNAP_CREATE_V2 \
- _IOW(BTRFS_IOCTL_MAGIC, 23, struct btrfs_ioctl_vol_args_v2)
-#endif
+ if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) {
+ log_stderr("failure: fchownat");
+ goto out;
+ }
-#ifndef BTRFS_IOC_SUBVOL_CREATE_V2
-#define BTRFS_IOC_SUBVOL_CREATE_V2 \
- _IOW(BTRFS_IOCTL_MAGIC, 24, struct btrfs_ioctl_vol_args_v2)
-#endif
+ print_r(info->t_mnt_fd, T_DIR1);
-#ifndef BTRFS_IOC_SUBVOL_GETFLAGS
-#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64)
-#endif
+ /* u:1000:1001:1 */
+ ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
-#ifndef BTRFS_IOC_SUBVOL_SETFLAGS
-#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64)
-#endif
+ /* u:1001:1000:1 */
+ ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
-#ifndef BTRFS_IOC_INO_LOOKUP
-#define BTRFS_IOC_INO_LOOKUP \
- _IOWR(BTRFS_IOCTL_MAGIC, 18, struct btrfs_ioctl_ino_lookup_args)
-#endif
+ /* g:1000:1001:1 */
+ ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
-#ifndef BTRFS_IOC_INO_LOOKUP_USER
-#define BTRFS_IOC_INO_LOOKUP_USER \
- _IOWR(BTRFS_IOCTL_MAGIC, 62, struct btrfs_ioctl_ino_lookup_user_args)
-#endif
+ /* g:1001:1000:1 */
+ ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID);
+ if (ret) {
+ log_stderr("failure: add_map_entry");
+ goto out;
+ }
-#ifndef BTRFS_IOC_GET_SUBVOL_ROOTREF
-#define BTRFS_IOC_GET_SUBVOL_ROOTREF \
- _IOWR(BTRFS_IOCTL_MAGIC, 61, struct btrfs_ioctl_get_subvol_rootref_args)
-#endif
+ attr.userns_fd = get_userns_fd_from_idmap(&idmap);
+ if (attr.userns_fd < 0) {
+ log_stderr("failure: get_userns_fd");
+ goto out;
+ }
-#ifndef BTRFS_SUBVOL_RDONLY
-#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
-#endif
+ open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1,
+ AT_NO_AUTOMOUNT |
+ AT_SYMLINK_NOFOLLOW |
+ OPEN_TREE_CLOEXEC |
+ OPEN_TREE_CLONE |
+ AT_RECURSIVE);
+ if (open_tree_fd < 0) {
+ log_stderr("failure: sys_open_tree");
+ goto out;
+ }
-#ifndef BTRFS_SUBVOL_SPEC_BY_ID
-#define BTRFS_SUBVOL_SPEC_BY_ID (1ULL << 4)
-#endif
+ if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
+ log_stderr("failure: sys_mount_setattr");
+ goto out;
+ }
-#ifndef BTRFS_FIRST_FREE_OBJECTID
-#define BTRFS_FIRST_FREE_OBJECTID 256ULL
-#endif
+ print_r(open_tree_fd, "");
-static int btrfs_delete_subvolume(int parent_fd, const char *name)
-{
- struct btrfs_ioctl_vol_args args = {};
- size_t len;
- int ret;
+ pid = fork();
+ if (pid < 0) {
+ log_stderr("failure: fork");
+ goto out;
+ }
+ if (pid == 0) {
+ /* switch to {g,u}id 1001 */
+ if (!switch_resids(user2_uid, user2_gid))
+ die("failure: switch_resids");
- len = strlen(name);
- if (len >= sizeof(args.name))
- return -ENAMETOOLONG;
+ /* drop all capabilities */
+ if (!caps_down())
+ die("failure: caps_down");
- memcpy(args.name, name, len);
- args.name[len] = '\0';
+ /*
+ * The {g,u}id 0 is not mapped in this idmapped mount so this
+ * needs to fail with EINVAL.
+ */
+ if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
+ if (errno != EINVAL)
+ die("failure: errno");
- ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY, &args);
- if (ret < 0)
- return -1;
+ /*
+ * A user with fs{g,u}id 1001 must be allowed to change
+ * ownership of /target/file1 owned by {g,u}id 1001 in this
+ * idmapped mount to {g,u}id 1001.
+ */
+ if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
+ AT_SYMLINK_NOFOLLOW))
+ die("failure: change ownership");
- return 0;
-}
-
-static int btrfs_delete_subvolume_id(int parent_fd, uint64_t subvolid)
-{
- struct btrfs_ioctl_vol_args_v2 args = {};
- int ret;
-
- args.flags = BTRFS_SUBVOL_SPEC_BY_ID;
- args.subvolid = subvolid;
-
- ret = ioctl(parent_fd, BTRFS_IOC_SNAP_DESTROY_V2, &args);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static int btrfs_create_subvolume(int parent_fd, const char *name)
-{
- struct btrfs_ioctl_vol_args_v2 args = {};
- size_t len;
- int ret;
-
- len = strlen(name);
- if (len >= sizeof(args.name))
- return -ENAMETOOLONG;
-
- memcpy(args.name, name, len);
- args.name[len] = '\0';
-
- ret = ioctl(parent_fd, BTRFS_IOC_SUBVOL_CREATE_V2, &args);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static int btrfs_create_snapshot(int fd, int parent_fd, const char *name,
- int flags)
-{
- struct btrfs_ioctl_vol_args_v2 args = {
- .fd = fd,
- };
- size_t len;
- int ret;
-
- if (flags & ~BTRFS_SUBVOL_RDONLY)
- return -EINVAL;
-
- len = strlen(name);
- if (len >= sizeof(args.name))
- return -ENAMETOOLONG;
- memcpy(args.name, name, len);
- args.name[len] = '\0';
-
- if (flags & BTRFS_SUBVOL_RDONLY)
- args.flags |= BTRFS_SUBVOL_RDONLY;
- ret = ioctl(parent_fd, BTRFS_IOC_SNAP_CREATE_V2, &args);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static int btrfs_get_subvolume_ro(int fd, bool *read_only_ret)
-{
- uint64_t flags;
- int ret;
-
- ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
- if (ret < 0)
- return -1;
-
- *read_only_ret = flags & BTRFS_SUBVOL_RDONLY;
- return 0;
-}
-
-static int btrfs_set_subvolume_ro(int fd, bool read_only)
-{
- uint64_t flags;
- int ret;
-
- ret = ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags);
- if (ret < 0)
- return -1;
-
- if (read_only)
- flags |= BTRFS_SUBVOL_RDONLY;
- else
- flags &= ~BTRFS_SUBVOL_RDONLY;
-
- ret = ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags);
- if (ret < 0)
- return -1;
-
- return 0;
-}
-
-static int btrfs_get_subvolume_id(int fd, uint64_t *id_ret)
-{
- struct btrfs_ioctl_ino_lookup_args args = {
- .treeid = 0,
- .objectid = BTRFS_FIRST_FREE_OBJECTID,
- };
- int ret;
-
- ret = ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args);
- if (ret < 0)
- return -1;
-
- *id_ret = args.treeid;
-
- return 0;
-}
-
-/*
- * The following helpers are adapted from the btrfsutils library. We can't use
- * the library directly since we need full control over how the subvolume
- * iteration happens. We need to be able to check whether unprivileged
- * subvolume iteration is possible, i.e. whether BTRFS_IOC_INO_LOOKUP_USER is
- * available and also ensure that it is actually used when looking up paths.
- */
-struct btrfs_stack {
- uint64_t tree_id;
- struct btrfs_ioctl_get_subvol_rootref_args rootref_args;
- size_t items_pos;
- size_t path_len;
-};
-
-struct btrfs_iter {
- int fd;
- int cur_fd;
-
- struct btrfs_stack *search_stack;
- size_t stack_len;
- size_t stack_capacity;
-
- char *cur_path;
- size_t cur_path_capacity;
-};
-
-static struct btrfs_stack *top_stack_entry(struct btrfs_iter *iter)
-{
- return &iter->search_stack[iter->stack_len - 1];
-}
-
-static int pop_stack(struct btrfs_iter *iter)
-{
- struct btrfs_stack *top, *parent;
- int fd, parent_fd;
- size_t i;
-
- if (iter->stack_len == 1) {
- iter->stack_len--;
- return 0;
- }
-
- top = top_stack_entry(iter);
- iter->stack_len--;
- parent = top_stack_entry(iter);
-
- fd = iter->cur_fd;
- for (i = parent->path_len; i < top->path_len; i++) {
- if (i == 0 || iter->cur_path[i] == '/') {
- parent_fd = openat(fd, "..", O_RDONLY);
- if (fd != iter->cur_fd)
- close(fd);
- if (parent_fd == -1)
- return -1;
- fd = parent_fd;
- }
- }
- if (iter->cur_fd != iter->fd)
- close(iter->cur_fd);
- iter->cur_fd = fd;
-
- return 0;
-}
-
-static int append_stack(struct btrfs_iter *iter, uint64_t tree_id, size_t path_len)
-{
- struct btrfs_stack *entry;
-
- if (iter->stack_len >= iter->stack_capacity) {
- size_t new_capacity = iter->stack_capacity * 2;
- struct btrfs_stack *new_search_stack;
-#ifdef HAVE_REALLOCARRAY
- new_search_stack = reallocarray(iter->search_stack, new_capacity,
- sizeof(*iter->search_stack));
-#else
- new_search_stack = realloc(iter->search_stack, new_capacity * sizeof(*iter->search_stack));
-#endif
- if (!new_search_stack)
- return -ENOMEM;
-
- iter->stack_capacity = new_capacity;
- iter->search_stack = new_search_stack;
- }
-
- entry = &iter->search_stack[iter->stack_len];
-
- memset(entry, 0, sizeof(*entry));
- entry->path_len = path_len;
- entry->tree_id = tree_id;
-
- if (iter->stack_len) {
- struct btrfs_stack *top;
- char *path;
- int fd;
-
- top = top_stack_entry(iter);
- path = &iter->cur_path[top->path_len];
- if (*path == '/')
- path++;
- fd = openat(iter->cur_fd, path, O_RDONLY);
- if (fd == -1)
- return -errno;
-
- close(iter->cur_fd);
- iter->cur_fd = fd;
- }
-
- iter->stack_len++;
-
- return 0;
-}
-
-static int btrfs_iterator_start(int fd, uint64_t top, struct btrfs_iter **ret)
-{
- struct btrfs_iter *iter;
- int err;
-
- iter = malloc(sizeof(*iter));
- if (!iter)
- return -ENOMEM;
-
- iter->fd = fd;
- iter->cur_fd = fd;
-
- iter->stack_len = 0;
- iter->stack_capacity = 4;
- iter->search_stack = malloc(sizeof(*iter->search_stack) *
- iter->stack_capacity);
- if (!iter->search_stack) {
- err = -ENOMEM;
- goto out_iter;
- }
-
- iter->cur_path_capacity = 256;
- iter->cur_path = malloc(iter->cur_path_capacity);
- if (!iter->cur_path) {
- err = -ENOMEM;
- goto out_search_stack;
- }
-
- err = append_stack(iter, top, 0);
- if (err)
- goto out_cur_path;
-
- *ret = iter;
-
- return 0;
-
-out_cur_path:
- free(iter->cur_path);
-out_search_stack:
- free(iter->search_stack);
-out_iter:
- free(iter);
- return err;
-}
-
-static void btrfs_iterator_end(struct btrfs_iter *iter)
-{
- if (iter) {
- free(iter->cur_path);
- free(iter->search_stack);
- if (iter->cur_fd != iter->fd)
- close(iter->cur_fd);
- close(iter->fd);
- free(iter);
- }
-}
-
-static int __append_path(struct btrfs_iter *iter, const char *name,
- size_t name_len, const char *dir, size_t dir_len,
- size_t *path_len_ret)
-{
- struct btrfs_stack *top = top_stack_entry(iter);
- size_t path_len;
- char *p;
-
- path_len = top->path_len;
- /*
- * We need a joining slash if we have a current path and a subdirectory.
- */
- if (top->path_len && dir_len)
- path_len++;
- path_len += dir_len;
- /*
- * We need another joining slash if we have a current path and a name,
- * but not if we have a subdirectory, because the lookup ioctl includes
- * a trailing slash.
- */
- if (top->path_len && !dir_len && name_len)
- path_len++;
- path_len += name_len;
-
- /* We need one extra character for the NUL terminator. */
- if (path_len + 1 > iter->cur_path_capacity) {
- char *tmp = realloc(iter->cur_path, path_len + 1);
-
- if (!tmp)
- return -ENOMEM;
- iter->cur_path = tmp;
- iter->cur_path_capacity = path_len + 1;
- }
-
- p = iter->cur_path + top->path_len;
- if (top->path_len && dir_len)
- *p++ = '/';
- memcpy(p, dir, dir_len);
- p += dir_len;
- if (top->path_len && !dir_len && name_len)
- *p++ = '/';
- memcpy(p, name, name_len);
- p += name_len;
- *p = '\0';
-
- *path_len_ret = path_len;
-
- return 0;
-}
-
-static int get_subvolume_path(struct btrfs_iter *iter, uint64_t treeid,
- uint64_t dirid, size_t *path_len_ret)
-{
- struct btrfs_ioctl_ino_lookup_user_args args = {
- .treeid = treeid,
- .dirid = dirid,
- };
- int ret;
-
- ret = ioctl(iter->cur_fd, BTRFS_IOC_INO_LOOKUP_USER, &args);
- if (ret == -1)
- return -1;
-
- return __append_path(iter, args.name, strlen(args.name), args.path,
- strlen(args.path), path_len_ret);
-}
-
-static int btrfs_iterator_next(struct btrfs_iter *iter, char **path_ret,
- uint64_t *id_ret)
-{
- struct btrfs_stack *top;
- uint64_t treeid, dirid;
- size_t path_len;
- int ret, err;
-
- for (;;) {
- for (;;) {
- if (iter->stack_len == 0)
- return 1;
-
- top = top_stack_entry(iter);
- if (top->items_pos < top->rootref_args.num_items) {
- break;
- } else {
- ret = ioctl(iter->cur_fd,
- BTRFS_IOC_GET_SUBVOL_ROOTREF,
- &top->rootref_args);
- if (ret == -1 && errno != EOVERFLOW)
- return -1;
- top->items_pos = 0;
-
- if (top->rootref_args.num_items == 0) {
- err = pop_stack(iter);
- if (err)
- return err;
- }
- }
- }
-
- treeid = top->rootref_args.rootref[top->items_pos].treeid;
- dirid = top->rootref_args.rootref[top->items_pos].dirid;
- top->items_pos++;
- err = get_subvolume_path(iter, treeid, dirid, &path_len);
- if (err) {
- /* Skip the subvolume if we can't access it. */
- if (errno == EACCES)
- continue;
- return err;
- }
-
- err = append_stack(iter, treeid, path_len);
- if (err) {
- /*
- * Skip the subvolume if it does not exist (which can
- * happen if there is another filesystem mounted over a
- * parent directory) or we don't have permission to
- * access it.
- */
- if (errno == ENOENT || errno == EACCES)
- continue;
- return err;
- }
-
- top = top_stack_entry(iter);
- goto out;
- }
-
-out:
- if (path_ret) {
- *path_ret = malloc(top->path_len + 1);
- if (!*path_ret)
- return -ENOMEM;
- memcpy(*path_ret, iter->cur_path, top->path_len);
- (*path_ret)[top->path_len] = '\0';
- }
- if (id_ret)
- *id_ret = top->tree_id;
- return 0;
-}
-
-#define BTRFS_SUBVOLUME1 "subvol1"
-#define BTRFS_SUBVOLUME1_SNAPSHOT1 "subvol1_snapshot1"
-#define BTRFS_SUBVOLUME1_SNAPSHOT1_RO "subvol1_snapshot1_ro"
-#define BTRFS_SUBVOLUME1_RENAME "subvol1_rename"
-#define BTRFS_SUBVOLUME2 "subvol2"
-
-static int btrfs_subvolumes_fsids_mapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_up())
- die("failure: raise caps");
-
- /*
- * The caller's fsids now have mappings in the idmapped mount so
- * any file creation must succeed.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: check ownership");
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: check ownership");
-
- if (!caps_down())
- die("failure: lower caps");
-
- /*
- * The filesystem is not mounted with user_subvol_rm_allowed so
- * subvolume deletion must fail.
- */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
- if (errno != EPERM)
- die("failure: errno");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: check ownership");
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_fsids_mapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /* The caller's fsids now have mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: check ownership");
-
- /* remove subvolume */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_fsids_unmapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
-
- /* create directory for rename test */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- /* change ownership of all files to uid 0 */
- if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- if (!switch_fsids(0, 0)) {
- log_stderr("failure: switch_fsids");
- goto out;
- }
-
- /*
- * The caller's fsids don't have a mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
- if (errno != EOVERFLOW) {
- log_stderr("failure: errno");
- goto out;
- }
-
- /* try to rename a subvolume */
- if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
- BTRFS_SUBVOLUME1_RENAME)) {
- log_stderr("failure: renameat");
- goto out;
- }
- if (errno != EOVERFLOW) {
- log_stderr("failure: errno");
- goto out;
- }
-
- /* The caller is privileged over the inode so file deletion must work. */
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_fsids_unmapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- /* create directory for rename test */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- /* change ownership of all files to uid 0 */
- if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- userns_fd = get_userns_fd(0, 30000, 10000);
- if (userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_userns(userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- /*
- * The caller's fsids don't have a mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
- die("failure: btrfs_create_subvolume");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /* try to rename a subvolume */
- if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
- BTRFS_SUBVOLUME1_RENAME))
- die("failure: renameat");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /*
- * The caller is not privileged over the inode so subvolume
- * deletion must fail.
- */
-
- /* remove subvolume */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
- safe_close(userns_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_mapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
-
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_up())
- die("failure: raise caps");
-
- /* The caller's fsids now have mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* create read-only snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- safe_close(subvolume_fd);
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- /* remove read-write snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
- die("failure: btrfs_delete_subvolume");
-
- /* remove read-only snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
- die("failure: btrfs_delete_subvolume");
-
- /* create directory */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* create read-only snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove read-write snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove read-only snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_mapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
-
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- /* create read-only snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* remove read-write snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* remove read-only snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_unmapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* create directory for rename test */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- /* change ownership of all files to uid 0 */
- if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
- sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
-
- if (!switch_fsids(0, 0)) {
- log_stderr("failure: switch_fsids");
- goto out;
- }
-
- /*
- * The caller's fsids don't have a mappings in the idmapped
- * mount so any file creation must fail.
- */
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor
- * which we can't use with ioctl(). So let's reopen it as a
- * proper file descriptor.
- */
- tree_fd = openat(open_tree_fd, ".",
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0)
- die("failure: openat");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create directory */
- if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
- die("failure: btrfs_create_subvolume");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /* create read-write snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /* create read-only snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /* try to rename a directory */
- if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
- BTRFS_SUBVOLUME1_RENAME))
- die("failure: renameat");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- if (!caps_down())
- die("failure: caps_down");
-
- /* create read-write snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
- if (errno != EPERM)
- die("failure: errno");
-
- /* create read-only snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
- if (errno != EPERM)
- die("failure: errno");
-
- /*
- * The caller is not privileged over the inode so subvolume
- * deletion must fail.
- */
-
- /* remove directory */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
- if (errno != EPERM)
- die("failure: errno");
-
- if (!caps_up())
- die("failure: caps_down");
-
- /*
- * The caller is privileged over the inode so subvolume
- * deletion must work.
- */
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_unmapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF,
- userns_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* create directory for rename test */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- /* change ownership of all files to uid 0 */
- if (fchownat(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- userns_fd = get_userns_fd(0, 30000, 10000);
- if (userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr,
- sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor
- * which we can't use with ioctl(). So let's reopen it as a
- * proper file descriptor.
- */
- tree_fd = openat(open_tree_fd, ".",
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_userns(userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- /*
- * The caller's fsids don't have a mappings in the idmapped
- * mount so any file creation must fail.
- */
-
- /* create directory */
- if (!btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME2))
- die("failure: btrfs_create_subvolume");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /* create read-write snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
- if (errno != EPERM)
- die("failure: errno");
-
- /* create read-only snapshot */
- if (!btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
- if (errno != EPERM)
- die("failure: errno");
-
- /* try to rename a directory */
- if (!renameat(open_tree_fd, BTRFS_SUBVOLUME1, open_tree_fd,
- BTRFS_SUBVOLUME1_RENAME))
- die("failure: renameat");
- if (errno != EOVERFLOW)
- die("failure: errno");
-
- /*
- * The caller is not privileged over the inode so subvolume
- * deletion must fail.
- */
-
- /* remove directory */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
- if (errno != EPERM)
- die("failure: errno");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(subvolume_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /*
- * The caller's fsids now have mappings in the idmapped mount so
- * any file creation must succedd.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: check ownership");
-
- /*
- * The scratch device is mounted with user_subvol_rm_allowed so
- * subvolume deletion must succeed.
- */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /* The caller's fsids now have mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: check ownership");
-
- /*
- * The scratch device is mounted with user_subvol_rm_allowed so
- * subvolume deletion must succeed.
- */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
-
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /*
- * The caller's fsids now have mappings in the idmapped mount so
- * any file creation must succeed.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- /* create read-only snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- safe_close(subvolume_fd);
-
- /* remove subvolume */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- /* remove read-write snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
- die("failure: btrfs_delete_subvolume");
-
- /* remove read-only snapshot */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
- die("failure: btrfs_delete_subvolume");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- safe_close(subvolume_fd);
-
- /* remove read-only snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_mnt_scratch_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
-
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- /* create read-only snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- BTRFS_SUBVOL_RDONLY))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_delete_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- /* remove read-write snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1))
- die("failure: btrfs_delete_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- /* remove read-only snapshot */
- if (!btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
- die("failure: btrfs_delete_subvolume");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- safe_close(subvolume_fd);
-
- /* remove read-only snapshot */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1_RO))
- die("failure: btrfs_delete_subvolume");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_delete_by_spec_id(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF;
- uint64_t subvolume_id1 = -EINVAL, subvolume_id2 = -EINVAL;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "A")) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_mnt_scratch_fd, "B")) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- subvolume_fd = openat(info->t_mnt_scratch_fd, "B", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(subvolume_fd, "C")) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- safe_close(subvolume_fd);
-
- subvolume_fd = openat(info->t_mnt_scratch_fd, "A", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id1)) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
- subvolume_fd = openat(info->t_mnt_scratch_fd, "B/C", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fd, &subvolume_id2)) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
- if (sys_mount(info->t_device_scratch, info->t_mountpoint, "btrfs", 0, "subvol=B/C")) {
- log_stderr("failure: mount");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(-EBADF, info->t_mountpoint,
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- /*
- * The subvolume isn't exposed in the idmapped mount so
- * delation via spec id must fail.
- */
- if (!btrfs_delete_subvolume_id(tree_fd, subvolume_id1))
- die("failure: btrfs_delete_subvolume_id");
- if (errno != EOPNOTSUPP)
- die("failure: errno");
-
- if (btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id1))
- die("failure: btrfs_delete_subvolume_id");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
- sys_umount2(info->t_mountpoint, MNT_DETACH);
- btrfs_delete_subvolume_id(info->t_mnt_scratch_fd, subvolume_id2);
- btrfs_delete_subvolume(info->t_mnt_scratch_fd, "B");
-
- return fret;
-}
-
-static int btrfs_subvolumes_setflags_fsids_mapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
- bool read_only = false;
-
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /* The caller's fsids now have mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, true))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (!read_only)
- die("failure: not read_only");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_setflags_fsids_mapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
- bool read_only = false;
-
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /* The caller's fsids now have mappings in the idmapped mount so
- * any file creation must fail.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, true))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (!read_only)
- die("failure: not read_only");
-
- if (btrfs_set_subvolume_ro(subvolume_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_setflags_fsids_unmapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
- bool read_only = false;
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (!switch_fsids(0, 0))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /*
- * The caller's fsids don't have mappings in the idmapped mount
- * so any file creation must fail.
- */
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (!btrfs_set_subvolume_ro(subvolume_fd, true))
- die("failure: btrfs_set_subvolume_ro");
- if (errno != EPERM)
- die("failure: errno");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_subvolumes_setflags_fsids_unmapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- userns_fd = get_userns_fd(0, 30000, 10000);
- if (userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int subvolume_fd = -EBADF;
- bool read_only = false;
-
- /*
- * The caller's fsids don't have mappings in the idmapped mount
- * so any file creation must fail.
- */
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- if (!switch_userns(userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- if (btrfs_get_subvolume_ro(subvolume_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (!btrfs_set_subvolume_ro(subvolume_fd, true))
- die("failure: btrfs_set_subvolume_ro");
- if (errno != EPERM)
- die("failure: errno");
-
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
- safe_close(userns_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_setflags_fsids_mapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int snapshot_fd = -EBADF, subvolume_fd = -EBADF;
- bool read_only = false;
-
- if (!switch_fsids(10000, 10000))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /*
- * The caller's fsids now have mappings in the idmapped mount
- * so any file creation must succeed.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000))
- die("failure: expected_uid_gid");
-
- snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (snapshot_fd < 0)
- die("failure: openat");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (btrfs_set_subvolume_ro(snapshot_fd, true))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (!read_only)
- die("failure: not read_only");
-
- if (btrfs_set_subvolume_ro(snapshot_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- safe_close(snapshot_fd);
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_setflags_fsids_mapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int snapshot_fd = -EBADF, subvolume_fd = -EBADF;
- bool read_only = false;
-
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- /*
- * The caller's fsids now have mappings in the idmapped mount so
- * any file creation must succeed.
- */
-
- /* create subvolume */
- if (btrfs_create_subvolume(tree_fd, BTRFS_SUBVOLUME1))
- die("failure: btrfs_create_subvolume");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- subvolume_fd = openat(tree_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0)
- die("failure: openat");
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, tree_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0))
- die("failure: btrfs_create_snapshot");
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0))
- die("failure: expected_uid_gid");
-
- snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (snapshot_fd < 0)
- die("failure: openat");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (btrfs_set_subvolume_ro(snapshot_fd, true))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (!read_only)
- die("failure: not read_only");
-
- if (btrfs_set_subvolume_ro(snapshot_fd, false))
- die("failure: btrfs_set_subvolume_ro");
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- safe_close(snapshot_fd);
- safe_close(subvolume_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_setflags_fsids_unmapped(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) {
- log_stderr("failure: btrfs_create_snapshot");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int snapshot_fd = -EBADF;
- bool read_only = false;
-
- snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (snapshot_fd < 0)
- die("failure: openat");
-
- if (!switch_fsids(0, 0))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: raise caps");
-
- /*
- * The caller's fsids don't have mappings in the idmapped mount
- * so any file creation must fail.
- */
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (!btrfs_set_subvolume_ro(snapshot_fd, true))
- die("failure: btrfs_set_subvolume_ro");
- if (errno != EPERM)
- die("failure: errno");
-
- safe_close(snapshot_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(subvolume_fd);
- safe_close(tree_fd);
-
- return fret;
-}
-
-static int btrfs_snapshots_setflags_fsids_unmapped_userns(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF, subvolume_fd = -EBADF, tree_fd = -EBADF,
- userns_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
-
- if (!caps_supported())
- return 0;
-
- /* Changing mount properties on a detached mount. */
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- /* Changing mount properties on a detached mount. */
- userns_fd = get_userns_fd(0, 30000, 10000);
- if (userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(info->t_dir1_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- subvolume_fd = openat(info->t_dir1_fd, BTRFS_SUBVOLUME1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create read-write snapshot */
- if (btrfs_create_snapshot(subvolume_fd, info->t_dir1_fd,
- BTRFS_SUBVOLUME1_SNAPSHOT1, 0)) {
- log_stderr("failure: btrfs_create_snapshot");
- goto out;
- }
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 0, 0)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- if (!expected_uid_gid(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1, 0, 10000, 10000)) {
- log_stderr("failure: expected_uid_gid");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- int snapshot_fd = -EBADF;
- bool read_only = false;
-
- /*
- * The caller's fsids don't have mappings in the idmapped mount
- * so any file creation must fail.
- */
-
- snapshot_fd = openat(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1,
- O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (snapshot_fd < 0)
- die("failure: openat");
-
-
- if (!switch_userns(userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- if (!expected_uid_gid(info->t_dir1_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- if (!expected_uid_gid(open_tree_fd, BTRFS_SUBVOLUME1, 0,
- info->t_overflowuid, info->t_overflowgid))
- die("failure: expected_uid_gid");
-
- /*
- * The caller's fsids don't have mappings in the idmapped mount
- * so any file creation must fail.
- */
-
- if (btrfs_get_subvolume_ro(snapshot_fd, &read_only))
- die("failure: btrfs_get_subvolume_ro");
-
- if (read_only)
- die("failure: read_only");
-
- if (!btrfs_set_subvolume_ro(snapshot_fd, true))
- die("failure: btrfs_set_subvolume_ro");
- if (errno != EPERM)
- die("failure: errno");
-
- safe_close(snapshot_fd);
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- /* remove directory */
- if (btrfs_delete_subvolume(tree_fd, BTRFS_SUBVOLUME1_SNAPSHOT1)) {
- log_stderr("failure: btrfs_delete_subvolume");
- goto out;
- }
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(attr.userns_fd);
- safe_close(open_tree_fd);
- safe_close(subvolume_fd);
- safe_close(tree_fd);
- safe_close(userns_fd);
-
- return fret;
-}
-
-#define BTRFS_SUBVOLUME_SUBVOL1 "subvol1"
-#define BTRFS_SUBVOLUME_SUBVOL2 "subvol2"
-#define BTRFS_SUBVOLUME_SUBVOL3 "subvol3"
-#define BTRFS_SUBVOLUME_SUBVOL4 "subvol4"
-
-#define BTRFS_SUBVOLUME_SUBVOL1_ID 0
-#define BTRFS_SUBVOLUME_SUBVOL2_ID 1
-#define BTRFS_SUBVOLUME_SUBVOL3_ID 2
-#define BTRFS_SUBVOLUME_SUBVOL4_ID 3
-
-#define BTRFS_SUBVOLUME_DIR1 "dir1"
-#define BTRFS_SUBVOLUME_DIR2 "dir2"
-
-#define BTRFS_SUBVOLUME_MNT "mnt_subvolume1"
-
-#define BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3 "subvol1/subvol3"
-#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2 "subvol1/dir1/dir2"
-#define BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4 "subvol1/dir1/dir2/subvol4"
-
-/*
- * We create the following mount layout to test lookup:
- *
- * |-/mnt/test /dev/loop0 btrfs rw,relatime,space_cache,subvolid=5,subvol=/
- * | |-/mnt/test/mnt1 /dev/loop1[/subvol1] btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=268,subvol=/subvol1
- * '-/mnt/scratch /dev/loop1 btrfs rw,relatime,space_cache,user_subvol_rm_allowed,subvolid=5,subvol=/
- */
-static int btrfs_subvolume_lookup_user(const struct vfstest_info *info)
-{
- int fret = -1, i;
- int dir1_fd = -EBADF, dir2_fd = -EBADF, mnt_fd = -EBADF,
- open_tree_fd = -EBADF, tree_fd = -EBADF, userns_fd = -EBADF;
- int subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID + 1];
- uint64_t subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID + 1];
- uint64_t subvolid = -EINVAL;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- };
- pid_t pid;
- struct btrfs_iter *iter;
-
- if (!caps_supported())
- return 0;
-
- for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++)
- subvolume_fds[i] = -EBADF;
-
- for (i = 0; i < ARRAY_SIZE(subvolume_ids); i++)
- subvolume_ids[i] = -EINVAL;
-
- if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL1)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (btrfs_create_subvolume(info->t_mnt_scratch_fd, BTRFS_SUBVOLUME_SUBVOL2)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] = openat(info->t_mnt_scratch_fd,
- BTRFS_SUBVOLUME_SUBVOL1,
- O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID] < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- /* create subvolume */
- if (btrfs_create_subvolume(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_SUBVOL3)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (mkdirat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- dir1_fd = openat(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID], BTRFS_SUBVOLUME_DIR1,
- O_CLOEXEC | O_DIRECTORY);
- if (dir1_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (mkdirat(dir1_fd, BTRFS_SUBVOLUME_DIR2, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- dir2_fd = openat(dir1_fd, BTRFS_SUBVOLUME_DIR2, O_CLOEXEC | O_DIRECTORY);
- if (dir2_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_create_subvolume(dir2_fd, BTRFS_SUBVOLUME_SUBVOL4)) {
- log_stderr("failure: btrfs_create_subvolume");
- goto out;
- }
-
- if (mkdirat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT);
- if (sys_mount(info->t_device_scratch, t_buf, "btrfs", 0,
- "subvol=" BTRFS_SUBVOLUME_SUBVOL1)) {
- log_stderr("failure: mount");
- goto out;
- }
-
- mnt_fd = openat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, O_CLOEXEC | O_DIRECTORY);
- if (mnt_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (chown_r(info->t_mnt_scratch_fd, ".", 1000, 1000)) {
- log_stderr("failure: chown_r");
- goto out;
- }
-
- subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] = openat(info->t_mnt_scratch_fd,
- BTRFS_SUBVOLUME_SUBVOL2,
- O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID] < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL1_ID],
- &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL1_ID])) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL2_ID],
- &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL2_ID])) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
- subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] = openat(info->t_mnt_scratch_fd,
- BTRFS_SUBVOLUME_SUBVOL1xSUBVOL3,
- O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID] < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID],
- &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
- subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] = openat(info->t_mnt_scratch_fd,
- BTRFS_SUBVOLUME_SUBVOL1xDIR1xDIR2xSUBVOL4,
- O_CLOEXEC | O_DIRECTORY);
- if (subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID] < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- if (btrfs_get_subvolume_id(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID],
- &subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])) {
- log_stderr("failure: btrfs_get_subvolume_id");
- goto out;
- }
-
-
- if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL3_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) {
- log_stderr("failure: fchmod");
- goto out;
- }
-
- if (fchmod(subvolume_fds[BTRFS_SUBVOLUME_SUBVOL4_ID], S_IRUSR | S_IWUSR | S_IXUSR), 0) {
- log_stderr("failure: fchmod");
- goto out;
- }
-
- attr.userns_fd = get_userns_fd(0, 10000, 10000);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(mnt_fd, "",
- AT_EMPTY_PATH |
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- /*
- * The open_tree() syscall returns an O_PATH file descriptor which we
- * can't use with ioctl(). So let's reopen it as a proper file
- * descriptor.
- */
- tree_fd = openat(open_tree_fd, ".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
- if (tree_fd < 0) {
- log_stderr("failure: openat");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- bool subvolume3_found = false, subvolume4_found = false;
-
- if (!switch_fsids(11000, 11000))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: lower caps");
-
- if (btrfs_iterator_start(tree_fd, 0, &iter))
- die("failure: btrfs_iterator_start");
-
- for (;;) {
- char *subvol_path = NULL;
- int ret;
-
- ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
- if (ret == 1)
- break;
- else if (ret)
- die("failure: btrfs_iterator_next");
-
- if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] &&
- subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
- die("failure: subvolume id %llu->%s",
- (long long unsigned)subvolid, subvol_path);
-
- if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])
- subvolume3_found = true;
-
- if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
- subvolume4_found = true;
-
- free(subvol_path);
- }
- btrfs_iterator_end(iter);
-
- if (!subvolume3_found || !subvolume4_found)
- die("failure: subvolume id");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- bool subvolume3_found = false, subvolume4_found = false;
-
- if (!switch_userns(attr.userns_fd, 0, 0, false))
- die("failure: switch_userns");
-
- if (btrfs_iterator_start(tree_fd, 0, &iter))
- die("failure: btrfs_iterator_start");
-
- for (;;) {
- char *subvol_path = NULL;
- int ret;
-
- ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
- if (ret == 1)
- break;
- else if (ret)
- die("failure: btrfs_iterator_next");
-
- if (subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID] &&
- subvolid != subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
- die("failure: subvolume id %llu->%s",
- (long long unsigned)subvolid, subvol_path);
-
- if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL3_ID])
- subvolume3_found = true;
-
- if (subvolid == subvolume_ids[BTRFS_SUBVOLUME_SUBVOL4_ID])
- subvolume4_found = true;
-
- free(subvol_path);
- }
- btrfs_iterator_end(iter);
-
- if (!subvolume3_found || !subvolume4_found)
- die("failure: subvolume id");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- bool subvolume_found = false;
-
- if (!switch_fsids(0, 0))
- die("failure: switch fsids");
-
- if (!caps_down())
- die("failure: lower caps");
-
- if (btrfs_iterator_start(tree_fd, 0, &iter))
- die("failure: btrfs_iterator_start");
-
- for (;;) {
- char *subvol_path = NULL;
- int ret;
-
- ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
- if (ret == 1)
- break;
- else if (ret)
- die("failure: btrfs_iterator_next");
-
- free(subvol_path);
-
- subvolume_found = true;
- break;
- }
- btrfs_iterator_end(iter);
-
- if (subvolume_found)
- die("failure: subvolume id");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- userns_fd = get_userns_fd(0, 30000, 10000);
- if (userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- bool subvolume_found = false;
-
- if (!switch_userns(userns_fd, 0, 0, true))
- die("failure: switch_userns");
-
- if (btrfs_iterator_start(tree_fd, 0, &iter))
- die("failure: btrfs_iterator_start");
-
- for (;;) {
- char *subvol_path = NULL;
- int ret;
-
- ret = btrfs_iterator_next(iter, &subvol_path, &subvolid);
- if (ret == 1)
- break;
- else if (ret)
- die("failure: btrfs_iterator_next");
-
- free(subvol_path);
-
- subvolume_found = true;
- break;
- }
- btrfs_iterator_end(iter);
-
- if (subvolume_found)
- die("failure: subvolume id");
-
- exit(EXIT_SUCCESS);
- }
- if (wait_for_pid(pid))
- goto out;
-
- fret = 0;
- log_debug("Ran test");
-out:
- safe_close(dir1_fd);
- safe_close(dir2_fd);
- safe_close(open_tree_fd);
- safe_close(tree_fd);
- safe_close(userns_fd);
- for (i = 0; i < ARRAY_SIZE(subvolume_fds); i++)
- safe_close(subvolume_fds[i]);
- snprintf(t_buf, sizeof(t_buf), "%s/%s", info->t_mountpoint, BTRFS_SUBVOLUME_MNT);
- sys_umount2(t_buf, MNT_DETACH);
- unlinkat(info->t_mnt_fd, BTRFS_SUBVOLUME_MNT, AT_REMOVEDIR);
-
- return fret;
-}
-
-#define USER1 "fsgqa"
-#define USER2 "fsgqa2"
-
-/**
- * lookup_ids - lookup uid and gid for a username
- * @name: [in] name of the user
- * @uid: [out] pointer to the user-ID
- * @gid: [out] pointer to the group-ID
- *
- * Lookup the uid and gid of a user.
- *
- * Return: On success, true is returned.
- * On error, false is returned.
- */
-static bool lookup_ids(const char *name, uid_t *uid, gid_t *gid)
-{
- bool bret = false;
- struct passwd *pwentp = NULL;
- struct passwd pwent;
- char *buf;
- ssize_t bufsize;
- int ret;
-
- bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (bufsize < 0)
- bufsize = 1024;
-
- buf = malloc(bufsize);
- if (!buf)
- return bret;
-
- ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp);
- if (!ret && pwentp) {
- *uid = pwent.pw_uid;
- *gid = pwent.pw_gid;
- bret = true;
- }
-
- free(buf);
- return bret;
-}
-
-/**
- * setattr_fix_968219708108 - test for commit 968219708108 ("fs: handle circular mappings correctly")
- *
- * Test that ->setattr() works correctly for idmapped mounts with circular
- * idmappings such as:
- *
- * b:1000:1001:1
- * b:1001:1000:1
- *
- * Assume a directory /source with two files:
- *
- * /source/file1 | 1000:1000
- * /source/file2 | 1001:1001
- *
- * and we create an idmapped mount of /source at /target with an idmapped of:
- *
- * mnt_userns: 1000:1001:1
- * 1001:1000:1
- *
- * In the idmapped mount file1 will be owned by uid 1001 and file2 by uid 1000:
- *
- * /target/file1 | 1001:1001
- * /target/file2 | 1000:1000
- *
- * Because in essence the idmapped mount switches ownership for {g,u}id 1000
- * and {g,u}id 1001.
- *
- * 1. A user with fs{g,u}id 1000 must be allowed to setattr /target/file2 from
- * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
- * 2. A user with fs{g,u}id 1001 must be allowed to setattr /target/file1 from
- * {g,u}id 1001 in the idmapped mount to {g,u}id 1001.
- * 3. A user with fs{g,u}id 1000 must fail to setattr /target/file1 from
- * {g,u}id 1001 in the idmapped mount to {g,u}id 1000.
- * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
- * {g,u}id of the file.
- * 4. A user with fs{g,u}id 1001 must fail to setattr /target/file2 from
- * {g,u}id 1000 in the idmapped mount to {g,u}id 1000.
- * This must fail with EPERM. The caller's fs{g,u}id doesn't match the
- * {g,u}id of the file.
- * 5. Both, a user with fs{g,u}id 1000 and a user with fs{g,u}id 1001, must
- * fail to setattr /target/file1 owned by {g,u}id 1001 in the idmapped mount
- * and /target/file2 owned by {g,u}id 1000 in the idmapped mount to any
- * {g,u}id apart from {g,u}id 1000 or 1001 with EINVAL.
- * Only {g,u}id 1000 and 1001 have a mapping in the idmapped mount. Other
- * {g,u}id are unmapped.
- */
-static int setattr_fix_968219708108(const struct vfstest_info *info)
-{
- int fret = -1;
- int open_tree_fd = -EBADF;
- struct mount_attr attr = {
- .attr_set = MOUNT_ATTR_IDMAP,
- .userns_fd = -EBADF,
- };
- int ret;
- uid_t user1_uid, user2_uid;
- gid_t user1_gid, user2_gid;
- pid_t pid;
- struct list idmap;
- struct list *it_cur, *it_next;
-
- if (!caps_supported())
- return 0;
-
- list_init(&idmap);
-
- if (!lookup_ids(USER1, &user1_uid, &user1_gid)) {
- log_stderr("failure: lookup_user");
- goto out;
- }
-
- if (!lookup_ids(USER2, &user2_uid, &user2_gid)) {
- log_stderr("failure: lookup_user");
- goto out;
- }
-
- log_debug("Found " USER1 " with uid(%d) and gid(%d) and " USER2 " with uid(%d) and gid(%d)",
- user1_uid, user1_gid, user2_uid, user2_gid);
-
- if (mkdirat(info->t_dir1_fd, DIR1, 0777)) {
- log_stderr("failure: mkdirat");
- goto out;
- }
-
- if (mknodat(info->t_dir1_fd, DIR1 "/" FILE1, S_IFREG | 0644, 0)) {
- log_stderr("failure: mknodat");
- goto out;
- }
-
- if (chown_r(info->t_mnt_fd, T_DIR1, user1_uid, user1_gid)) {
- log_stderr("failure: chown_r");
- goto out;
- }
-
- if (mknodat(info->t_dir1_fd, DIR1 "/" FILE2, S_IFREG | 0644, 0)) {
- log_stderr("failure: mknodat");
- goto out;
- }
-
- if (fchownat(info->t_dir1_fd, DIR1 "/" FILE2, user2_uid, user2_gid, AT_SYMLINK_NOFOLLOW)) {
- log_stderr("failure: fchownat");
- goto out;
- }
-
- print_r(info->t_mnt_fd, T_DIR1);
-
- /* u:1000:1001:1 */
- ret = add_map_entry(&idmap, user1_uid, user2_uid, 1, ID_TYPE_UID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* u:1001:1000:1 */
- ret = add_map_entry(&idmap, user2_uid, user1_uid, 1, ID_TYPE_UID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* g:1000:1001:1 */
- ret = add_map_entry(&idmap, user1_gid, user2_gid, 1, ID_TYPE_GID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- /* g:1001:1000:1 */
- ret = add_map_entry(&idmap, user2_gid, user1_gid, 1, ID_TYPE_GID);
- if (ret) {
- log_stderr("failure: add_map_entry");
- goto out;
- }
-
- attr.userns_fd = get_userns_fd_from_idmap(&idmap);
- if (attr.userns_fd < 0) {
- log_stderr("failure: get_userns_fd");
- goto out;
- }
-
- open_tree_fd = sys_open_tree(info->t_dir1_fd, DIR1,
- AT_NO_AUTOMOUNT |
- AT_SYMLINK_NOFOLLOW |
- OPEN_TREE_CLOEXEC |
- OPEN_TREE_CLONE |
- AT_RECURSIVE);
- if (open_tree_fd < 0) {
- log_stderr("failure: sys_open_tree");
- goto out;
- }
-
- if (sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr))) {
- log_stderr("failure: sys_mount_setattr");
- goto out;
- }
-
- print_r(open_tree_fd, "");
-
- pid = fork();
- if (pid < 0) {
- log_stderr("failure: fork");
- goto out;
- }
- if (pid == 0) {
- /* switch to {g,u}id 1001 */
- if (!switch_resids(user2_uid, user2_gid))
- die("failure: switch_resids");
-
- /* drop all capabilities */
- if (!caps_down())
- die("failure: caps_down");
-
- /*
- * The {g,u}id 0 is not mapped in this idmapped mount so this
- * needs to fail with EINVAL.
- */
- if (!fchownat(open_tree_fd, FILE1, 0, 0, AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
- if (errno != EINVAL)
- die("failure: errno");
-
- /*
- * A user with fs{g,u}id 1001 must be allowed to change
- * ownership of /target/file1 owned by {g,u}id 1001 in this
- * idmapped mount to {g,u}id 1001.
- */
- if (fchownat(open_tree_fd, FILE1, user2_uid, user2_gid,
- AT_SYMLINK_NOFOLLOW))
- die("failure: change ownership");
-
- /* Verify that the ownership is still {g,u}id 1001. */
- if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
- user2_uid, user2_gid))
- die("failure: check ownership");
+ /* Verify that the ownership is still {g,u}id 1001. */
+ if (!expected_uid_gid(open_tree_fd, FILE1, AT_SYMLINK_NOFOLLOW,
+ user2_uid, user2_gid))
+ die("failure: check ownership");
/*
* A user with fs{g,u}id 1001 must not be allowed to change
.nr_tests = ARRAY_SIZE(t_nested_userns),
};
-static const struct test_struct t_btrfs[] = {
- { btrfs_subvolumes_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids", },
- { btrfs_subvolumes_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with mapped fsids inside user namespace", },
- { btrfs_subvolumes_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option", },
- { btrfs_subvolumes_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion with user_subvol_rm_allowed mount option inside user namespace", },
- { btrfs_subvolumes_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids", },
- { btrfs_subvolumes_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolumes with unmapped fsids inside user namespace", },
- { btrfs_snapshots_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids", },
- { btrfs_snapshots_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with mapped fsids inside user namespace", },
- { btrfs_snapshots_fsids_mapped_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option", },
- { btrfs_snapshots_fsids_mapped_userns_user_subvol_rm_allowed, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots deletion with user_subvol_rm_allowed mount option inside user namespace", },
- { btrfs_snapshots_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids", },
- { btrfs_snapshots_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots with unmapped fsids inside user namespace", },
- { btrfs_delete_by_spec_id, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume deletion by spec id", },
- { btrfs_subvolumes_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids", },
- { btrfs_subvolumes_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with mapped fsids inside user namespace", },
- { btrfs_subvolumes_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids", },
- { btrfs_subvolumes_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test subvolume flags with unmapped fsids inside user namespace", },
- { btrfs_snapshots_setflags_fsids_mapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids", },
- { btrfs_snapshots_setflags_fsids_mapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with mapped fsids inside user namespace", },
- { btrfs_snapshots_setflags_fsids_unmapped, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids", },
- { btrfs_snapshots_setflags_fsids_unmapped_userns, T_REQUIRE_IDMAPPED_MOUNTS, "test snapshots flags with unmapped fsids inside user namespace", },
- { btrfs_subvolume_lookup_user, T_REQUIRE_IDMAPPED_MOUNTS, "test unprivileged subvolume lookup", },
-};
-
-static const struct test_suite s_btrfs = {
- .tests = t_btrfs,
- .nr_tests = ARRAY_SIZE(t_btrfs),
-};
-
/* Test for commit 968219708108 ("fs: handle circular mappings correctly"). */
static const struct test_struct t_setattr_fix_968219708108[] = {
{ setattr_fix_968219708108, T_REQUIRE_IDMAPPED_MOUNTS, "test that setattr works correctly", },
if (test_nested_userns && !run_suite(&info, &s_nested_userns))
goto out;
- if (test_btrfs && !run_suite(&info, &s_btrfs))
+ if (test_btrfs && !run_suite(&info, &s_btrfs_idmapped_mounts))
goto out;
if (test_setattr_fix_968219708108 &&