diff options
Diffstat (limited to 'fs/btrfs/send.c')
| -rw-r--r-- | fs/btrfs/send.c | 124 | 
1 files changed, 106 insertions, 18 deletions
| diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index c10e4c70f02d..20d3300bd268 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -3521,7 +3521,40 @@ out:  }  /* - * Check if ino ino1 is an ancestor of inode ino2 in the given root. + * Check if inode ino2, or any of its ancestors, is inode ino1. + * Return 1 if true, 0 if false and < 0 on error. + */ +static int check_ino_in_path(struct btrfs_root *root, +			     const u64 ino1, +			     const u64 ino1_gen, +			     const u64 ino2, +			     const u64 ino2_gen, +			     struct fs_path *fs_path) +{ +	u64 ino = ino2; + +	if (ino1 == ino2) +		return ino1_gen == ino2_gen; + +	while (ino > BTRFS_FIRST_FREE_OBJECTID) { +		u64 parent; +		u64 parent_gen; +		int ret; + +		fs_path_reset(fs_path); +		ret = get_first_ref(root, ino, &parent, &parent_gen, fs_path); +		if (ret < 0) +			return ret; +		if (parent == ino1) +			return parent_gen == ino1_gen; +		ino = parent; +	} +	return 0; +} + +/* + * Check if ino ino1 is an ancestor of inode ino2 in the given root for any + * possible path (in case ino2 is not a directory and has multiple hard links).   * Return 1 if true, 0 if false and < 0 on error.   */  static int is_ancestor(struct btrfs_root *root, @@ -3530,36 +3563,91 @@ static int is_ancestor(struct btrfs_root *root,  		       const u64 ino2,  		       struct fs_path *fs_path)  { -	u64 ino = ino2; -	bool free_path = false; +	bool free_fs_path = false;  	int ret = 0; +	struct btrfs_path *path = NULL; +	struct btrfs_key key;  	if (!fs_path) {  		fs_path = fs_path_alloc();  		if (!fs_path)  			return -ENOMEM; -		free_path = true; +		free_fs_path = true;  	} -	while (ino > BTRFS_FIRST_FREE_OBJECTID) { -		u64 parent; -		u64 parent_gen; +	path = alloc_path_for_send(); +	if (!path) { +		ret = -ENOMEM; +		goto out; +	} -		fs_path_reset(fs_path); -		ret = get_first_ref(root, ino, &parent, &parent_gen, fs_path); -		if (ret < 0) { -			if (ret == -ENOENT && ino == ino2) -				ret = 0; -			goto out; +	key.objectid = ino2; +	key.type = BTRFS_INODE_REF_KEY; +	key.offset = 0; + +	ret = btrfs_search_slot(NULL, root, &key, path, 0, 0); +	if (ret < 0) +		goto out; + +	while (true) { +		struct extent_buffer *leaf = path->nodes[0]; +		int slot = path->slots[0]; +		u32 cur_offset = 0; +		u32 item_size; + +		if (slot >= btrfs_header_nritems(leaf)) { +			ret = btrfs_next_leaf(root, path); +			if (ret < 0) +				goto out; +			if (ret > 0) +				break; +			continue;  		} -		if (parent == ino1) { -			ret = parent_gen == ino1_gen ? 1 : 0; -			goto out; + +		btrfs_item_key_to_cpu(leaf, &key, slot); +		if (key.objectid != ino2) +			break; +		if (key.type != BTRFS_INODE_REF_KEY && +		    key.type != BTRFS_INODE_EXTREF_KEY) +			break; + +		item_size = btrfs_item_size_nr(leaf, slot); +		while (cur_offset < item_size) { +			u64 parent; +			u64 parent_gen; + +			if (key.type == BTRFS_INODE_EXTREF_KEY) { +				unsigned long ptr; +				struct btrfs_inode_extref *extref; + +				ptr = btrfs_item_ptr_offset(leaf, slot); +				extref = (struct btrfs_inode_extref *) +					(ptr + cur_offset); +				parent = btrfs_inode_extref_parent(leaf, +								   extref); +				cur_offset += sizeof(*extref); +				cur_offset += btrfs_inode_extref_name_len(leaf, +								  extref); +			} else { +				parent = key.offset; +				cur_offset = item_size; +			} + +			ret = get_inode_info(root, parent, NULL, &parent_gen, +					     NULL, NULL, NULL, NULL); +			if (ret < 0) +				goto out; +			ret = check_ino_in_path(root, ino1, ino1_gen, +						parent, parent_gen, fs_path); +			if (ret) +				goto out;  		} -		ino = parent; +		path->slots[0]++;  	} +	ret = 0;   out: -	if (free_path) +	btrfs_free_path(path); +	if (free_fs_path)  		fs_path_free(fs_path);  	return ret;  } |