]> git.apps.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
vfstest: split out btrfs idmapped mounts test
authorChristian Brauner <brauner@kernel.org>
Thu, 12 May 2022 16:52:48 +0000 (18:52 +0200)
committerZorro Lang <zlang@kernel.org>
Sun, 15 May 2022 01:05:04 +0000 (09:05 +0800)
Split the btrfs specific idmapped mounts test into a separate source
file.

Acked-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Christian Brauner (Microsoft) <brauner@kernel.org>
Signed-off-by: Zorro Lang <zlang@kernel.org>
src/vfs/Makefile
src/vfs/btrfs-idmapped-mounts.c [new file with mode: 0644]
src/vfs/btrfs-idmapped-mounts.h [new file with mode: 0644]
src/vfs/vfstest.c

index fa0f1621fe666e396f4656af2dce3659d497c597..1b0b364b2dec135b40a47c3e7f932c55f6e1f05d 100644 (file)
@@ -4,10 +4,10 @@ TOPDIR = ../..
 include $(TOPDIR)/include/builddefs
 
 TARGETS = vfstest mount-idmapped
-CFILES_VFSTEST = vfstest.c idmapped-mounts.c utils.c
+CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c
 CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
 
-HFILES = missing.h utils.h idmapped-mounts.h
+HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h
 LLDLIBS += -pthread
 LDIRT = $(TARGETS)
 
diff --git a/src/vfs/btrfs-idmapped-mounts.c b/src/vfs/btrfs-idmapped-mounts.c
new file mode 100644 (file)
index 0000000..7c8f73b
--- /dev/null
@@ -0,0 +1,3854 @@
+// 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),
+};
diff --git a/src/vfs/btrfs-idmapped-mounts.h b/src/vfs/btrfs-idmapped-mounts.h
new file mode 100644 (file)
index 0000000..ed71464
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __BTRFS_IDMAPPED_MOUNTS_H
+#define __BTRFS_IDMAPPED_MOUNTS_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "utils.h"
+
+extern const struct test_suite s_btrfs_idmapped_mounts;
+
+#endif /* __BTRFS_IDMAPPED_MOUNTS_H */
+
index d90eaa7a9f27278c9175a1f5dbbdb44820d3e28a..f9399b26a4c340c0dd9df477514d017712d8c3bd 100644 (file)
 #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"
@@ -2646,4025 +2635,242 @@ out:
        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
@@ -7017,36 +3223,6 @@ static const struct test_suite s_nested_userns = {
        .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",                                                            },
@@ -7272,7 +3448,7 @@ int main(int argc, char *argv[])
        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 &&