btrfs: backref, only collect file extent items matching backref offset

When resolving one backref of type EXTENT_DATA_REF, we collect all
references that simply reference the EXTENT_ITEM even though their
(file_pos - file_extent_item::offset) are not the same as the
btrfs_extent_data_ref::offset we are searching for.

This patch adds additional check so that we only collect references whose
(file_pos - file_extent_item::offset) == btrfs_extent_data_ref::offset.

Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: ethanwu <ethanwu@synology.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
ethanwu 2020-02-07 17:38:15 +08:00 committed by David Sterba
parent 9da2b242e2
commit 7ac8b88ee6
1 changed files with 33 additions and 30 deletions

View File

@ -347,33 +347,10 @@ static int add_prelim_ref(const struct btrfs_fs_info *fs_info,
return -ENOMEM; return -ENOMEM;
ref->root_id = root_id; ref->root_id = root_id;
if (key) { if (key)
ref->key_for_search = *key; ref->key_for_search = *key;
/* else
* We can often find data backrefs with an offset that is too
* large (>= LLONG_MAX, maximum allowed file offset) due to
* underflows when subtracting a file's offset with the data
* offset of its corresponding extent data item. This can
* happen for example in the clone ioctl.
* So if we detect such case we set the search key's offset to
* zero to make sure we will find the matching file extent item
* at add_all_parents(), otherwise we will miss it because the
* offset taken form the backref is much larger then the offset
* of the file extent item. This can make us scan a very large
* number of file extent items, but at least it will not make
* us miss any.
* This is an ugly workaround for a behaviour that should have
* never existed, but it does and a fix for the clone ioctl
* would touch a lot of places, cause backwards incompatibility
* and would not fix the problem for extents cloned with older
* kernels.
*/
if (ref->key_for_search.type == BTRFS_EXTENT_DATA_KEY &&
ref->key_for_search.offset >= LLONG_MAX)
ref->key_for_search.offset = 0;
} else {
memset(&ref->key_for_search, 0, sizeof(ref->key_for_search)); memset(&ref->key_for_search, 0, sizeof(ref->key_for_search));
}
ref->inode_list = NULL; ref->inode_list = NULL;
ref->level = level; ref->level = level;
@ -424,6 +401,7 @@ static int add_all_parents(struct btrfs_root *root, struct btrfs_path *path,
u64 disk_byte; u64 disk_byte;
u64 wanted_disk_byte = ref->wanted_disk_byte; u64 wanted_disk_byte = ref->wanted_disk_byte;
u64 count = 0; u64 count = 0;
u64 data_offset;
if (level != 0) { if (level != 0) {
eb = path->nodes[level]; eb = path->nodes[level];
@ -457,11 +435,15 @@ static int add_all_parents(struct btrfs_root *root, struct btrfs_path *path,
fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item); fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);
disk_byte = btrfs_file_extent_disk_bytenr(eb, fi); disk_byte = btrfs_file_extent_disk_bytenr(eb, fi);
data_offset = btrfs_file_extent_offset(eb, fi);
if (disk_byte == wanted_disk_byte) { if (disk_byte == wanted_disk_byte) {
eie = NULL; eie = NULL;
old = NULL; old = NULL;
count++; if (ref->key_for_search.offset == key.offset - data_offset)
count++;
else
goto next;
if (extent_item_pos) { if (extent_item_pos) {
ret = check_extent_in_eb(&key, eb, fi, ret = check_extent_in_eb(&key, eb, fi,
*extent_item_pos, *extent_item_pos,
@ -513,6 +495,7 @@ static int resolve_indirect_ref(struct btrfs_fs_info *fs_info,
int root_level; int root_level;
int level = ref->level; int level = ref->level;
int index; int index;
struct btrfs_key search_key = ref->key_for_search;
root_key.objectid = ref->root_id; root_key.objectid = ref->root_id;
root_key.type = BTRFS_ROOT_ITEM_KEY; root_key.type = BTRFS_ROOT_ITEM_KEY;
@ -545,13 +528,33 @@ static int resolve_indirect_ref(struct btrfs_fs_info *fs_info,
goto out; goto out;
} }
/*
* We can often find data backrefs with an offset that is too large
* (>= LLONG_MAX, maximum allowed file offset) due to underflows when
* subtracting a file's offset with the data offset of its
* corresponding extent data item. This can happen for example in the
* clone ioctl.
*
* So if we detect such case we set the search key's offset to zero to
* make sure we will find the matching file extent item at
* add_all_parents(), otherwise we will miss it because the offset
* taken form the backref is much larger then the offset of the file
* extent item. This can make us scan a very large number of file
* extent items, but at least it will not make us miss any.
*
* This is an ugly workaround for a behaviour that should have never
* existed, but it does and a fix for the clone ioctl would touch a lot
* of places, cause backwards incompatibility and would not fix the
* problem for extents cloned with older kernels.
*/
if (search_key.type == BTRFS_EXTENT_DATA_KEY &&
search_key.offset >= LLONG_MAX)
search_key.offset = 0;
path->lowest_level = level; path->lowest_level = level;
if (time_seq == SEQ_LAST) if (time_seq == SEQ_LAST)
ret = btrfs_search_slot(NULL, root, &ref->key_for_search, path, ret = btrfs_search_slot(NULL, root, &search_key, path, 0, 0);
0, 0);
else else
ret = btrfs_search_old_slot(root, &ref->key_for_search, path, ret = btrfs_search_old_slot(root, &search_key, path, time_seq);
time_seq);
/* root node has been locked, we can release @subvol_srcu safely here */ /* root node has been locked, we can release @subvol_srcu safely here */
srcu_read_unlock(&fs_info->subvol_srcu, index); srcu_read_unlock(&fs_info->subvol_srcu, index);