diff options
Diffstat (limited to 'fs/btrfs/send.c')
| -rw-r--r-- | fs/btrfs/send.c | 56 | 
1 files changed, 48 insertions, 8 deletions
diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c index 6144e66661f5..96a030d28e09 100644 --- a/fs/btrfs/send.c +++ b/fs/btrfs/send.c @@ -4102,6 +4102,48 @@ out:  	return ret;  } +static int rbtree_check_dir_ref_comp(const void *k, const struct rb_node *node) +{ +	const struct recorded_ref *data = k; +	const struct recorded_ref *ref = rb_entry(node, struct recorded_ref, node); + +	if (data->dir > ref->dir) +		return 1; +	if (data->dir < ref->dir) +		return -1; +	if (data->dir_gen > ref->dir_gen) +		return 1; +	if (data->dir_gen < ref->dir_gen) +		return -1; +	return 0; +} + +static bool rbtree_check_dir_ref_less(struct rb_node *node, const struct rb_node *parent) +{ +	const struct recorded_ref *entry = rb_entry(node, struct recorded_ref, node); + +	return rbtree_check_dir_ref_comp(entry, parent) < 0; +} + +static int record_check_dir_ref_in_tree(struct rb_root *root, +			struct recorded_ref *ref, struct list_head *list) +{ +	struct recorded_ref *tmp_ref; +	int ret; + +	if (rb_find(ref, root, rbtree_check_dir_ref_comp)) +		return 0; + +	ret = dup_ref(ref, list); +	if (ret < 0) +		return ret; + +	tmp_ref = list_last_entry(list, struct recorded_ref, list); +	rb_add(&tmp_ref->node, root, rbtree_check_dir_ref_less); +	tmp_ref->root = root; +	return 0; +} +  static int rename_current_inode(struct send_ctx *sctx,  				struct fs_path *current_path,  				struct fs_path *new_path) @@ -4129,11 +4171,11 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  	struct recorded_ref *cur;  	struct recorded_ref *cur2;  	LIST_HEAD(check_dirs); +	struct rb_root rbtree_check_dirs = RB_ROOT;  	struct fs_path *valid_path = NULL;  	u64 ow_inode = 0;  	u64 ow_gen;  	u64 ow_mode; -	u64 last_dir_ino_rm = 0;  	bool did_overwrite = false;  	bool is_orphan = false;  	bool can_rename = true; @@ -4437,7 +4479,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  					goto out;  			}  		} -		ret = dup_ref(cur, &check_dirs); +		ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);  		if (ret < 0)  			goto out;  	} @@ -4465,7 +4507,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  		}  		list_for_each_entry(cur, &sctx->deleted_refs, list) { -			ret = dup_ref(cur, &check_dirs); +			ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);  			if (ret < 0)  				goto out;  		} @@ -4475,7 +4517,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  		 * We have a moved dir. Add the old parent to check_dirs  		 */  		cur = list_first_entry(&sctx->deleted_refs, struct recorded_ref, list); -		ret = dup_ref(cur, &check_dirs); +		ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);  		if (ret < 0)  			goto out;  	} else if (!S_ISDIR(sctx->cur_inode_mode)) { @@ -4509,7 +4551,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  				if (is_current_inode_path(sctx, cur->full_path))  					fs_path_reset(&sctx->cur_inode_path);  			} -			ret = dup_ref(cur, &check_dirs); +			ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);  			if (ret < 0)  				goto out;  		} @@ -4552,8 +4594,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  			ret = cache_dir_utimes(sctx, cur->dir, cur->dir_gen);  			if (ret < 0)  				goto out; -		} else if (ret == inode_state_did_delete && -			   cur->dir != last_dir_ino_rm) { +		} else if (ret == inode_state_did_delete) {  			ret = can_rmdir(sctx, cur->dir, cur->dir_gen);  			if (ret < 0)  				goto out; @@ -4565,7 +4606,6 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)  				ret = send_rmdir(sctx, valid_path);  				if (ret < 0)  					goto out; -				last_dir_ino_rm = cur->dir;  			}  		}  	}  | 
