summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2025-07-11 20:48:23 +0100
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2025-08-20 18:36:31 +0200
commit672673af7a78af8269bb1899d9f5a376d8b9fc6f (patch)
treed6df83d72d80da6864dc72c7de0beedaff176562
parent452b9a1e8a2a8681d865afda9d75e944405e482b (diff)
downloadlinux-stable-672673af7a78af8269bb1899d9f5a376d8b9fc6f.tar.gz
linux-stable-672673af7a78af8269bb1899d9f5a376d8b9fc6f.zip
btrfs: don't skip remaining extrefs if dir not found during log replay
commit 24e066ded45b8147b79c7455ac43a5bff7b5f378 upstream. During log replay, at add_inode_ref(), if we have an extref item that contains multiple extrefs and one of them points to a directory that does not exist in the subvolume tree, we are supposed to ignore it and process the remaining extrefs encoded in the extref item, since each extref can point to a different parent inode. However when that happens we just return from the function and ignore the remaining extrefs. The problem has been around since extrefs were introduced, in commit f186373fef00 ("btrfs: extended inode refs"), but it's hard to hit in practice because getting extref items encoding multiple extref requires getting a hash collision when computing the offset of the extref's key. The offset if computed like this: key.offset = btrfs_extref_hash(dir_ino, name->name, name->len); and btrfs_extref_hash() is just a wrapper around crc32c(). Fix this by moving to next iteration of the loop when we don't find the parent directory that an extref points to. Fixes: f186373fef00 ("btrfs: extended inode refs") CC: stable@vger.kernel.org # 6.1+ Reviewed-by: Boris Burkov <boris@bur.io> Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: David Sterba <dsterba@suse.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/btrfs/tree-log.c18
1 files changed, 14 insertions, 4 deletions
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 4086344e1c30..e2298671b24e 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -1397,6 +1397,8 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,
if (log_ref_ver) {
ret = extref_get_fields(eb, ref_ptr, &name,
&ref_index, &parent_objectid);
+ if (ret)
+ goto out;
/*
* parent object can change from one array
* item to another.
@@ -1413,16 +1415,23 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,
* the loop when getting the first
* parent dir.
*/
- if (ret == -ENOENT)
+ if (ret == -ENOENT) {
+ /*
+ * The next extref may refer to
+ * another parent dir that
+ * exists, so continue.
+ */
ret = 0;
+ goto next;
+ }
goto out;
}
}
} else {
ret = ref_get_fields(eb, ref_ptr, &name, &ref_index);
+ if (ret)
+ goto out;
}
- if (ret)
- goto out;
ret = inode_in_dir(root, path, btrfs_ino(dir), btrfs_ino(inode),
ref_index, &name);
@@ -1456,10 +1465,11 @@ static noinline int add_inode_ref(struct btrfs_trans_handle *trans,
}
/* Else, ret == 1, we already have a perfect match, we're done. */
+next:
ref_ptr = (unsigned long)(ref_ptr + ref_struct_size) + name.len;
kfree(name.name);
name.name = NULL;
- if (log_ref_ver) {
+ if (log_ref_ver && dir) {
iput(&dir->vfs_inode);
dir = NULL;
}