Skip to content

Commit a29450e

Browse files
tingchangkdave
authored andcommitted
btrfs: send: fix duplicated rmdir operations when using extrefs
Commit 29d6d30 ("Btrfs: send, don't send rmdir for same target multiple times") has fixed an issue that a send stream contained a rmdir operation for the same directory multiple times. After that fix we keep track of the last directory for which we sent a rmdir operation and compare with it before sending a rmdir for the parent inode of a deleted hardlink we are processing. But there is still a corner case that in between rmdir dir operations for the same inode we find deleted hardlinks for other parent inodes, so tracking just the last inode for which we sent a rmdir operation is not enough. Hardlinks of a file in the same directory are stored in the same INODE_REF item, but if the number of hardlinks is too large and can not fit in a leaf, we use INODE_EXTREF items to store them. The key of an INODE_EXTREF item is (inode_id, INODE_EXTREF, hash[name, parent ino]), so between two hardlinks for the same parent directory, we can find others for other parent directories. For example for the reproducer below we get the following (from a btrfs inspect-internal dump-tree output): item 0 key (259 INODE_EXTREF 2309449) itemoff 16257 itemsize 26 index 6925 parent 257 namelen 8 name: foo.6923 item 1 key (259 INODE_EXTREF 2311350) itemoff 16231 itemsize 26 index 6588 parent 258 namelen 8 name: foo.6587 item 2 key (259 INODE_EXTREF 2457395) itemoff 16205 itemsize 26 index 6611 parent 257 namelen 8 name: foo.6609 (...) So tracking the last directory's inode number does not work in this case since we process a link for parent inode 257, then for 258 and then back again for 257, and that second time we process a deleted link for 257 we think we have not yet sent a rmdir operation. Fix this by using a rbtree to keep track of all the directories for which we have already sent rmdir operations, and add those directories to the 'check_dirs' ref list in process_recorded_refs() only if the directory is not yet in the rbtree, otherwise skip it since it means we have already sent a rmdir operation for that directory. The following test script reproduces the problem: $ cat test.sh #!/bin/bash DEV=/dev/sdi MNT=/mnt/sdi mkfs.btrfs -f $DEV mount $DEV $MNT mkdir $MNT/a $MNT/b echo 123 > $MNT/a/foo for ((i = 1; i <= 1000; i++)); do ln $MNT/a/foo $MNT/a/foo.$i ln $MNT/a/foo $MNT/b/foo.$i done btrfs subvolume snapshot -r $MNT $MNT/snap1 btrfs send $MNT/snap1 -f /tmp/base.send rm -r $MNT/a $MNT/b btrfs subvolume snapshot -r $MNT $MNT/snap2 btrfs send -p $MNT/snap1 $MNT/snap2 -f /tmp/incremental.send umount $MNT mkfs.btrfs -f $DEV mount $DEV $MNT btrfs receive $MNT -f /tmp/base.send btrfs receive $MNT -f /tmp/incremental.send rm -f /tmp/base.send /tmp/incremental.send umount $MNT When running it, it fails like this: $ ./test.sh (...) At subvol snap1 At snapshot snap2 ERROR: rmdir o257-9-0 failed: No such file or directory CC: <[email protected]> Reviewed-by: Filipe Manana <[email protected]> Signed-off-by: Ting-Chang Hou <[email protected]> [ Updated changelog ] Signed-off-by: Filipe Manana <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent 3d716a7 commit a29450e

File tree

1 file changed

+48
-8
lines changed

1 file changed

+48
-8
lines changed

fs/btrfs/send.c

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4100,6 +4100,48 @@ static int refresh_ref_path(struct send_ctx *sctx, struct recorded_ref *ref)
41004100
return ret;
41014101
}
41024102

4103+
static int rbtree_check_dir_ref_comp(const void *k, const struct rb_node *node)
4104+
{
4105+
const struct recorded_ref *data = k;
4106+
const struct recorded_ref *ref = rb_entry(node, struct recorded_ref, node);
4107+
4108+
if (data->dir > ref->dir)
4109+
return 1;
4110+
if (data->dir < ref->dir)
4111+
return -1;
4112+
if (data->dir_gen > ref->dir_gen)
4113+
return 1;
4114+
if (data->dir_gen < ref->dir_gen)
4115+
return -1;
4116+
return 0;
4117+
}
4118+
4119+
static bool rbtree_check_dir_ref_less(struct rb_node *node, const struct rb_node *parent)
4120+
{
4121+
const struct recorded_ref *entry = rb_entry(node, struct recorded_ref, node);
4122+
4123+
return rbtree_check_dir_ref_comp(entry, parent) < 0;
4124+
}
4125+
4126+
static int record_check_dir_ref_in_tree(struct rb_root *root,
4127+
struct recorded_ref *ref, struct list_head *list)
4128+
{
4129+
struct recorded_ref *tmp_ref;
4130+
int ret;
4131+
4132+
if (rb_find(ref, root, rbtree_check_dir_ref_comp))
4133+
return 0;
4134+
4135+
ret = dup_ref(ref, list);
4136+
if (ret < 0)
4137+
return ret;
4138+
4139+
tmp_ref = list_last_entry(list, struct recorded_ref, list);
4140+
rb_add(&tmp_ref->node, root, rbtree_check_dir_ref_less);
4141+
tmp_ref->root = root;
4142+
return 0;
4143+
}
4144+
41034145
static int rename_current_inode(struct send_ctx *sctx,
41044146
struct fs_path *current_path,
41054147
struct fs_path *new_path)
@@ -4127,11 +4169,11 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
41274169
struct recorded_ref *cur;
41284170
struct recorded_ref *cur2;
41294171
LIST_HEAD(check_dirs);
4172+
struct rb_root rbtree_check_dirs = RB_ROOT;
41304173
struct fs_path *valid_path = NULL;
41314174
u64 ow_inode = 0;
41324175
u64 ow_gen;
41334176
u64 ow_mode;
4134-
u64 last_dir_ino_rm = 0;
41354177
bool did_overwrite = false;
41364178
bool is_orphan = false;
41374179
bool can_rename = true;
@@ -4435,7 +4477,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
44354477
goto out;
44364478
}
44374479
}
4438-
ret = dup_ref(cur, &check_dirs);
4480+
ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
44394481
if (ret < 0)
44404482
goto out;
44414483
}
@@ -4463,7 +4505,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
44634505
}
44644506

44654507
list_for_each_entry(cur, &sctx->deleted_refs, list) {
4466-
ret = dup_ref(cur, &check_dirs);
4508+
ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
44674509
if (ret < 0)
44684510
goto out;
44694511
}
@@ -4473,7 +4515,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
44734515
* We have a moved dir. Add the old parent to check_dirs
44744516
*/
44754517
cur = list_first_entry(&sctx->deleted_refs, struct recorded_ref, list);
4476-
ret = dup_ref(cur, &check_dirs);
4518+
ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
44774519
if (ret < 0)
44784520
goto out;
44794521
} else if (!S_ISDIR(sctx->cur_inode_mode)) {
@@ -4507,7 +4549,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
45074549
if (is_current_inode_path(sctx, cur->full_path))
45084550
fs_path_reset(&sctx->cur_inode_path);
45094551
}
4510-
ret = dup_ref(cur, &check_dirs);
4552+
ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
45114553
if (ret < 0)
45124554
goto out;
45134555
}
@@ -4550,8 +4592,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
45504592
ret = cache_dir_utimes(sctx, cur->dir, cur->dir_gen);
45514593
if (ret < 0)
45524594
goto out;
4553-
} else if (ret == inode_state_did_delete &&
4554-
cur->dir != last_dir_ino_rm) {
4595+
} else if (ret == inode_state_did_delete) {
45554596
ret = can_rmdir(sctx, cur->dir, cur->dir_gen);
45564597
if (ret < 0)
45574598
goto out;
@@ -4563,7 +4604,6 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
45634604
ret = send_rmdir(sctx, valid_path);
45644605
if (ret < 0)
45654606
goto out;
4566-
last_dir_ino_rm = cur->dir;
45674607
}
45684608
}
45694609
}

0 commit comments

Comments
 (0)