mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	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:
		
							parent
							
								
									9da2b242e2
								
							
						
					
					
						commit
						7ac8b88ee6
					
				
					 1 changed files with 33 additions and 30 deletions
				
			
		| 
						 | 
					@ -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;
 | 
				
			||||||
 | 
								if (ref->key_for_search.offset == key.offset - data_offset)
 | 
				
			||||||
				count++;
 | 
									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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue