btrfs: new inline ref storing owning subvol of data extents
authorBoris Burkov <boris@bur.io>
Mon, 30 Jan 2023 22:45:55 +0000 (14:45 -0800)
committerDavid Sterba <dsterba@suse.com>
Thu, 12 Oct 2023 14:44:11 +0000 (16:44 +0200)
In order to implement simple quota groups, we need to be able to
associate a data extent with the subvolume that created it. Once you
account for reflink, this information cannot be recovered without
explicitly storing it. Options for storing it are:

- a new key/item
- a new extent inline ref item

The former is backwards compatible, but wastes space, the latter is
incompat, but is efficient in space and reuses the existing inline ref
machinery, while only abusing it a tiny amount -- specifically, the new
item is not a ref, per-se.

Signed-off-by: Boris Burkov <boris@bur.io>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/accessors.h
fs/btrfs/backref.c
fs/btrfs/extent-tree.c
fs/btrfs/print-tree.c
fs/btrfs/ref-verify.c
fs/btrfs/tree-checker.c
include/uapi/linux/btrfs_tree.h

index ce18e261c861860b4ebca1f8e1a1936e80494697..fb1c1a1983f29d88be24201141f7e04972d33a95 100644 (file)
@@ -358,6 +358,9 @@ BTRFS_SETGET_FUNCS(extent_data_ref_count, struct btrfs_extent_data_ref, count, 3
 
 BTRFS_SETGET_FUNCS(shared_data_ref_count, struct btrfs_shared_data_ref, count, 32);
 
+BTRFS_SETGET_FUNCS(extent_owner_ref_root_id, struct btrfs_extent_owner_ref,
+                  root_id, 64);
+
 BTRFS_SETGET_FUNCS(extent_inline_ref_type, struct btrfs_extent_inline_ref,
                   type, 8);
 BTRFS_SETGET_FUNCS(extent_inline_ref_offset, struct btrfs_extent_inline_ref,
@@ -374,6 +377,8 @@ static inline u32 btrfs_extent_inline_ref_size(int type)
        if (type == BTRFS_EXTENT_DATA_REF_KEY)
                return sizeof(struct btrfs_extent_data_ref) +
                       offsetof(struct btrfs_extent_inline_ref, offset);
+       if (type == BTRFS_EXTENT_OWNER_REF_KEY)
+               return sizeof(struct btrfs_extent_inline_ref);
        return 0;
 }
 
index b7d54efb47288e21496de7e74b794aecce6ae856..0cde873bdee2b9ff8cc2030facd425a3cdd8f65d 100644 (file)
@@ -1129,6 +1129,9 @@ static int add_inline_refs(struct btrfs_backref_walk_ctx *ctx,
                                                       count, sc, GFP_NOFS);
                        break;
                }
+               case BTRFS_EXTENT_OWNER_REF_KEY:
+                       ASSERT(btrfs_fs_incompat(ctx->fs_info, SIMPLE_QUOTA));
+                       break;
                default:
                        WARN_ON(1);
                }
index 2dda3ecb49e6efd48b79c6d0e18d48e94e09389e..8bd556c8bca4fc25bce78a54af0d09a9157a54e4 100644 (file)
@@ -345,9 +345,15 @@ int btrfs_get_extent_inline_ref_type(const struct extent_buffer *eb,
                                     struct btrfs_extent_inline_ref *iref,
                                     enum btrfs_inline_ref_type is_data)
 {
+       struct btrfs_fs_info *fs_info = eb->fs_info;
        int type = btrfs_extent_inline_ref_type(eb, iref);
        u64 offset = btrfs_extent_inline_ref_offset(eb, iref);
 
+       if (type == BTRFS_EXTENT_OWNER_REF_KEY) {
+               ASSERT(btrfs_fs_incompat(fs_info, SIMPLE_QUOTA));
+               return type;
+       }
+
        if (type == BTRFS_TREE_BLOCK_REF_KEY ||
            type == BTRFS_SHARED_BLOCK_REF_KEY ||
            type == BTRFS_SHARED_DATA_REF_KEY ||
@@ -356,26 +362,25 @@ int btrfs_get_extent_inline_ref_type(const struct extent_buffer *eb,
                        if (type == BTRFS_TREE_BLOCK_REF_KEY)
                                return type;
                        if (type == BTRFS_SHARED_BLOCK_REF_KEY) {
-                               ASSERT(eb->fs_info);
+                               ASSERT(fs_info);
                                /*
                                 * Every shared one has parent tree block,
                                 * which must be aligned to sector size.
                                 */
-                               if (offset &&
-                                   IS_ALIGNED(offset, eb->fs_info->sectorsize))
+                               if (offset && IS_ALIGNED(offset, fs_info->sectorsize))
                                        return type;
                        }
                } else if (is_data == BTRFS_REF_TYPE_DATA) {
                        if (type == BTRFS_EXTENT_DATA_REF_KEY)
                                return type;
                        if (type == BTRFS_SHARED_DATA_REF_KEY) {
-                               ASSERT(eb->fs_info);
+                               ASSERT(fs_info);
                                /*
                                 * Every shared one has parent tree block,
                                 * which must be aligned to sector size.
                                 */
                                if (offset &&
-                                   IS_ALIGNED(offset, eb->fs_info->sectorsize))
+                                   IS_ALIGNED(offset, fs_info->sectorsize))
                                        return type;
                        }
                } else {
@@ -386,7 +391,7 @@ int btrfs_get_extent_inline_ref_type(const struct extent_buffer *eb,
 
        WARN_ON(1);
        btrfs_print_leaf(eb);
-       btrfs_err(eb->fs_info,
+       btrfs_err(fs_info,
                  "eb %llu iref 0x%lx invalid extent inline ref type %d",
                  eb->start, (unsigned long)iref, type);
 
@@ -887,6 +892,11 @@ again:
        while (ptr < end) {
                iref = (struct btrfs_extent_inline_ref *)ptr;
                type = btrfs_get_extent_inline_ref_type(leaf, iref, needed);
+               if (type == BTRFS_EXTENT_OWNER_REF_KEY) {
+                       ASSERT(btrfs_fs_incompat(fs_info, SIMPLE_QUOTA));
+                       ptr += btrfs_extent_inline_ref_size(type);
+                       continue;
+               }
                if (type == BTRFS_REF_TYPE_INVALID) {
                        ret = -EUCLEAN;
                        goto out;
@@ -1742,6 +1752,8 @@ static int run_one_delayed_ref(struct btrfs_trans_handle *trans,
                 node->type == BTRFS_SHARED_DATA_REF_KEY)
                ret = run_delayed_data_ref(trans, node, extent_op,
                                           insert_reserved);
+       else if (node->type == BTRFS_EXTENT_OWNER_REF_KEY)
+               ret = 0;
        else
                BUG();
        if (ret && insert_reserved)
@@ -2313,6 +2325,7 @@ static noinline int check_committed_ref(struct btrfs_root *root,
        struct btrfs_extent_item *ei;
        struct btrfs_key key;
        u32 item_size;
+       u32 expected_size;
        int type;
        int ret;
 
@@ -2339,10 +2352,22 @@ static noinline int check_committed_ref(struct btrfs_root *root,
        ret = 1;
        item_size = btrfs_item_size(leaf, path->slots[0]);
        ei = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_extent_item);
+       expected_size = sizeof(*ei) + btrfs_extent_inline_ref_size(BTRFS_EXTENT_DATA_REF_KEY);
+
+       /* No inline refs; we need to bail before checking for owner ref. */
+       if (item_size == sizeof(*ei))
+               goto out;
+
+       /* Check for an owner ref; skip over it to the real inline refs. */
+       iref = (struct btrfs_extent_inline_ref *)(ei + 1);
+       type = btrfs_get_extent_inline_ref_type(leaf, iref, BTRFS_REF_TYPE_DATA);
+       if (btrfs_fs_incompat(fs_info, SIMPLE_QUOTA) && type == BTRFS_EXTENT_OWNER_REF_KEY) {
+               expected_size += btrfs_extent_inline_ref_size(BTRFS_EXTENT_OWNER_REF_KEY);
+               iref = (struct btrfs_extent_inline_ref *)(iref + 1);
+       }
 
        /* If extent item has more than 1 inline ref then it's shared */
-       if (item_size != sizeof(*ei) +
-           btrfs_extent_inline_ref_size(BTRFS_EXTENT_DATA_REF_KEY))
+       if (item_size != expected_size)
                goto out;
 
        /*
@@ -2354,8 +2379,6 @@ static noinline int check_committed_ref(struct btrfs_root *root,
             btrfs_root_last_snapshot(&root->root_item)))
                goto out;
 
-       iref = (struct btrfs_extent_inline_ref *)(ei + 1);
-
        /* If this extent has SHARED_DATA_REF then it's shared */
        type = btrfs_get_extent_inline_ref_type(leaf, iref, BTRFS_REF_TYPE_DATA);
        if (type != BTRFS_EXTENT_DATA_REF_KEY)
@@ -4617,18 +4640,23 @@ static int alloc_reserved_file_extent(struct btrfs_trans_handle *trans,
        struct btrfs_root *extent_root;
        int ret;
        struct btrfs_extent_item *extent_item;
+       struct btrfs_extent_owner_ref *oref;
        struct btrfs_extent_inline_ref *iref;
        struct btrfs_path *path;
        struct extent_buffer *leaf;
        int type;
        u32 size;
+       const bool simple_quota = (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE);
 
        if (parent > 0)
                type = BTRFS_SHARED_DATA_REF_KEY;
        else
                type = BTRFS_EXTENT_DATA_REF_KEY;
 
-       size = sizeof(*extent_item) + btrfs_extent_inline_ref_size(type);
+       size = sizeof(*extent_item);
+       if (simple_quota)
+               size += btrfs_extent_inline_ref_size(BTRFS_EXTENT_OWNER_REF_KEY);
+       size += btrfs_extent_inline_ref_size(type);
 
        path = btrfs_alloc_path();
        if (!path)
@@ -4650,7 +4678,14 @@ static int alloc_reserved_file_extent(struct btrfs_trans_handle *trans,
                               flags | BTRFS_EXTENT_FLAG_DATA);
 
        iref = (struct btrfs_extent_inline_ref *)(extent_item + 1);
+       if (simple_quota) {
+               btrfs_set_extent_inline_ref_type(leaf, iref, BTRFS_EXTENT_OWNER_REF_KEY);
+               oref = (struct btrfs_extent_owner_ref *)(&iref->offset);
+               btrfs_set_extent_owner_ref_root_id(leaf, oref, root_objectid);
+               iref = (struct btrfs_extent_inline_ref *)(oref + 1);
+       }
        btrfs_set_extent_inline_ref_type(leaf, iref, type);
+
        if (parent > 0) {
                struct btrfs_shared_data_ref *ref;
                ref = (struct btrfs_shared_data_ref *)(iref + 1);
index 75042a7fcf30ef56718f2c80d5e86bcc404029c1..7e46aa8a0444651a2bc54c123f3a472ab4a29e45 100644 (file)
@@ -83,12 +83,20 @@ static void print_extent_data_ref(const struct extent_buffer *eb,
               btrfs_extent_data_ref_count(eb, ref));
 }
 
+static void print_extent_owner_ref(const struct extent_buffer *eb,
+                                  const struct btrfs_extent_owner_ref *ref)
+{
+       ASSERT(btrfs_fs_incompat(eb->fs_info, SIMPLE_QUOTA));
+       pr_cont("extent data owner root %llu\n", btrfs_extent_owner_ref_root_id(eb, ref));
+}
+
 static void print_extent_item(const struct extent_buffer *eb, int slot, int type)
 {
        struct btrfs_extent_item *ei;
        struct btrfs_extent_inline_ref *iref;
        struct btrfs_extent_data_ref *dref;
        struct btrfs_shared_data_ref *sref;
+       struct btrfs_extent_owner_ref *oref;
        struct btrfs_disk_key key;
        unsigned long end;
        unsigned long ptr;
@@ -164,6 +172,10 @@ static void print_extent_item(const struct extent_buffer *eb, int slot, int type
                        "\t\t\t(parent %llu not aligned to sectorsize %u)\n",
                                     offset, eb->fs_info->sectorsize);
                        break;
+               case BTRFS_EXTENT_OWNER_REF_KEY:
+                       oref = (struct btrfs_extent_owner_ref *)(&iref->offset);
+                       print_extent_owner_ref(eb, oref);
+                       break;
                default:
                        pr_cont("(extent %llu has INVALID ref type %d)\n",
                                  eb->start, type);
index e9e1ebd8dd6afb3991708300e1c60d225ee67497..1f62976bee829a1865faca76f5059f66752300ae 100644 (file)
@@ -485,6 +485,9 @@ static int process_extent_item(struct btrfs_fs_info *fs_info,
                        ret = add_shared_data_ref(fs_info, offset, count,
                                                  key->objectid, key->offset);
                        break;
+               case BTRFS_EXTENT_OWNER_REF_KEY:
+                       WARN_ON(!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA));
+                       break;
                default:
                        btrfs_err(fs_info, "invalid key type in iref");
                        ret = -EINVAL;
index 1547090bf2c09117f0f5277a606e19a0080313c4..1f2c389b0bfa37c8dcdc4c08e1e6b056d9e81510 100644 (file)
@@ -1467,6 +1467,9 @@ static int check_extent_item(struct extent_buffer *leaf,
                        }
                        inline_refs += btrfs_shared_data_ref_count(leaf, sref);
                        break;
+               case BTRFS_EXTENT_OWNER_REF_KEY:
+                       WARN_ON(!btrfs_fs_incompat(fs_info, SIMPLE_QUOTA));
+                       break;
                default:
                        extent_err(leaf, slot, "unknown inline ref type: %u",
                                   inline_type);
index a1d5347f6adbe9853417472bcbdda92433d93498..8b4def6aee52f0810480a05953c70cf6a4c4d32f 100644 (file)
 
 #define BTRFS_SHARED_DATA_REF_KEY      184
 
+/*
+ * Special inline ref key which stores the id of the subvolume which originally
+ * created the extent. This subvolume owns the extent permanently from the
+ * perspective of simple quotas. Needed to know which subvolume to free quota
+ * usage from when the extent is deleted.
+ */
+#define BTRFS_EXTENT_OWNER_REF_KEY     188
+
 /*
  * block groups give us hints into the extent allocation trees.  Which
  * blocks are free etc etc
@@ -816,6 +824,10 @@ struct btrfs_shared_data_ref {
        __le32 count;
 } __attribute__ ((__packed__));
 
+struct btrfs_extent_owner_ref {
+       __le64 root_id;
+} __attribute__ ((__packed__));
+
 struct btrfs_extent_inline_ref {
        __u8 type;
        __le64 offset;