btrfs: fix backref walking not returning all inode refs
authorFilipe Manana <fdmanana@suse.com>
Tue, 9 May 2023 11:50:02 +0000 (12:50 +0100)
committerDavid Sterba <dsterba@suse.com>
Tue, 9 May 2023 20:09:11 +0000 (22:09 +0200)
When using the logical to ino ioctl v2, if the flag to ignore offsets of
file extent items (BTRFS_LOGICAL_INO_ARGS_IGNORE_OFFSET) is given, the
backref walking code ends up not returning references for all file offsets
of an inode that point to the given logical bytenr. This happens since
kernel 6.2, commit 6ce6ba534418 ("btrfs: use a single argument for extent
offset in backref walking functions") because:

1) It mistakenly skipped the search for file extent items in a leaf that
   point to the target extent if that flag is given. Instead it should
   only skip the filtering done by check_extent_in_eb() - that is, it
   should not avoid the calls to that function (or find_extent_in_eb(),
   which uses it).

2) It was also not building a list of inode extent elements (struct
   extent_inode_elem) if we have multiple inode references for an extent
   when the ignore offset flag is given to the logical to ino ioctl - it
   would leave a single element, only the last one that was found.

These stem from the confusing old interface for backref walking functions
where we had an extent item offset argument that was a pointer to a u64
and another boolean argument that indicated if the offset should be
ignored, but the pointer could be NULL. That NULL case is used by
relocation, qgroup extent accounting and fiemap, simply to avoid building
the inode extent list for each reference, as it's not necessary for those
use cases and therefore avoids memory allocations and some computations.

Fix this by adding a boolean argument to the backref walk context
structure to indicate that the inode extent list should not be built,
make relocation set that argument to true and fix the backref walking
logic to skip the calls to check_extent_in_eb() and find_extent_in_eb()
only if this new argument is true, instead of 'ignore_extent_item_pos'
being true.

A test case for fstests will be added soon, to provide cover not only
for these cases but to the logical to ino ioctl in general as well, as
currently we do not have a test case for it.

Reported-by: Vladimir Panteleev <git@vladimir.panteleev.md>
Link: https://lore.kernel.org/linux-btrfs/CAHhfkvwo=nmzrJSqZ2qMfF-rZB-ab6ahHnCD_sq9h4o8v+M7QQ@mail.gmail.com/
Fixes: 6ce6ba534418 ("btrfs: use a single argument for extent offset in backref walking functions")
CC: stable@vger.kernel.org # 6.2+
Tested-by: Vladimir Panteleev <git@vladimir.panteleev.md>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/backref.c
fs/btrfs/backref.h
fs/btrfs/relocation.c

index e54f0884802a0cb2ac3592a71d348e80e5e01e62..79336fa853db363c5543d2b880a9290a99ce41ff 100644 (file)
@@ -45,7 +45,8 @@ static int check_extent_in_eb(struct btrfs_backref_walk_ctx *ctx,
        int root_count;
        bool cached;
 
-       if (!btrfs_file_extent_compression(eb, fi) &&
+       if (!ctx->ignore_extent_item_pos &&
+           !btrfs_file_extent_compression(eb, fi) &&
            !btrfs_file_extent_encryption(eb, fi) &&
            !btrfs_file_extent_other_encoding(eb, fi)) {
                u64 data_offset;
@@ -552,7 +553,7 @@ static int add_all_parents(struct btrfs_backref_walk_ctx *ctx,
                                count++;
                        else
                                goto next;
-                       if (!ctx->ignore_extent_item_pos) {
+                       if (!ctx->skip_inode_ref_list) {
                                ret = check_extent_in_eb(ctx, &key, eb, fi, &eie);
                                if (ret == BTRFS_ITERATE_EXTENT_INODES_STOP ||
                                    ret < 0)
@@ -564,7 +565,7 @@ static int add_all_parents(struct btrfs_backref_walk_ctx *ctx,
                                                  eie, (void **)&old, GFP_NOFS);
                        if (ret < 0)
                                break;
-                       if (!ret && !ctx->ignore_extent_item_pos) {
+                       if (!ret && !ctx->skip_inode_ref_list) {
                                while (old->next)
                                        old = old->next;
                                old->next = eie;
@@ -1606,7 +1607,7 @@ again:
                                goto out;
                }
                if (ref->count && ref->parent) {
-                       if (!ctx->ignore_extent_item_pos && !ref->inode_list &&
+                       if (!ctx->skip_inode_ref_list && !ref->inode_list &&
                            ref->level == 0) {
                                struct btrfs_tree_parent_check check = { 0 };
                                struct extent_buffer *eb;
@@ -1647,7 +1648,7 @@ again:
                                                  (void **)&eie, GFP_NOFS);
                        if (ret < 0)
                                goto out;
-                       if (!ret && !ctx->ignore_extent_item_pos) {
+                       if (!ret && !ctx->skip_inode_ref_list) {
                                /*
                                 * We've recorded that parent, so we must extend
                                 * its inode list here.
@@ -1743,7 +1744,7 @@ int btrfs_find_all_leafs(struct btrfs_backref_walk_ctx *ctx)
 static int btrfs_find_all_roots_safe(struct btrfs_backref_walk_ctx *ctx)
 {
        const u64 orig_bytenr = ctx->bytenr;
-       const bool orig_ignore_extent_item_pos = ctx->ignore_extent_item_pos;
+       const bool orig_skip_inode_ref_list = ctx->skip_inode_ref_list;
        bool roots_ulist_allocated = false;
        struct ulist_iterator uiter;
        int ret = 0;
@@ -1764,7 +1765,7 @@ static int btrfs_find_all_roots_safe(struct btrfs_backref_walk_ctx *ctx)
                roots_ulist_allocated = true;
        }
 
-       ctx->ignore_extent_item_pos = true;
+       ctx->skip_inode_ref_list = true;
 
        ULIST_ITER_INIT(&uiter);
        while (1) {
@@ -1789,7 +1790,7 @@ static int btrfs_find_all_roots_safe(struct btrfs_backref_walk_ctx *ctx)
        ulist_free(ctx->refs);
        ctx->refs = NULL;
        ctx->bytenr = orig_bytenr;
-       ctx->ignore_extent_item_pos = orig_ignore_extent_item_pos;
+       ctx->skip_inode_ref_list = orig_skip_inode_ref_list;
 
        return ret;
 }
@@ -1912,7 +1913,7 @@ int btrfs_is_data_extent_shared(struct btrfs_inode *inode, u64 bytenr,
                goto out_trans;
        }
 
-       walk_ctx.ignore_extent_item_pos = true;
+       walk_ctx.skip_inode_ref_list = true;
        walk_ctx.trans = trans;
        walk_ctx.fs_info = fs_info;
        walk_ctx.refs = &ctx->refs;
index ef6bbea3f45621f1f0b87e5c3331c200c7a9b388..1616e3e3f1e4193c7fc531737e65da47698fc049 100644 (file)
@@ -60,6 +60,12 @@ struct btrfs_backref_walk_ctx {
         * @extent_item_pos is ignored.
         */
        bool ignore_extent_item_pos;
+       /*
+        * If true and bytenr corresponds to a data extent, then the inode list
+        * (each member describing inode number, file offset and root) is not
+        * added to each reference added to the @refs ulist.
+        */
+       bool skip_inode_ref_list;
        /* A valid transaction handle or NULL. */
        struct btrfs_trans_handle *trans;
        /*
index 09b1988d179107c4bfe71556a6290cb157e81b18..59a06499c647ebb5ce35046763f35b9ac9626a5e 100644 (file)
@@ -3422,7 +3422,7 @@ int add_data_references(struct reloc_control *rc,
        btrfs_release_path(path);
 
        ctx.bytenr = extent_key->objectid;
-       ctx.ignore_extent_item_pos = true;
+       ctx.skip_inode_ref_list = true;
        ctx.fs_info = rc->extent_root->fs_info;
 
        ret = btrfs_find_all_leafs(&ctx);