Skip to content

Commit f959492

Browse files
fdmananamasoncl
authored andcommitted
Btrfs: send, fix more issues related to directory renames
This is a continuation of the previous changes titled: Btrfs: fix incremental send's decision to delay a dir move/rename Btrfs: part 2, fix incremental send's decision to delay a dir move/rename There's a few more cases where a directory rename/move must be delayed which was previously overlooked. If our immediate ancestor has a lower inode number than ours and it doesn't have a delayed rename/move operation associated to it, it doesn't mean there isn't any non-direct ancestor of our current inode that needs to be renamed/moved before our current inode (i.e. with a higher inode number than ours). So we can't stop the search if our immediate ancestor has a lower inode number than ours, we need to navigate the directory hierarchy upwards until we hit the root or: 1) find an ancestor with an higher inode number that was renamed/moved in the send root too (or already has a pending rename/move registered); 2) find an ancestor that is a new directory (higher inode number than ours and exists only in the send root). Reproducer for case 1) $ mkfs.btrfs -f /dev/sdd $ mount /dev/sdd /mnt $ mkdir -p /mnt/a/b $ mkdir -p /mnt/a/c/d $ mkdir /mnt/a/b/e $ mkdir /mnt/a/c/d/f $ mv /mnt/a/b /mnt/a/c/d/2b $ mkdir /mnt/a/x $ mkdir /mnt/a/y $ btrfs subvolume snapshot -r /mnt /mnt/snap1 $ btrfs send /mnt/snap1 -f /tmp/base.send $ mv /mnt/a/x /mnt/a/y $ mv /mnt/a/c/d/2b/e /mnt/a/c/d/2b/2e $ mv /mnt/a/c/d /mnt/a/h/2d $ mv /mnt/a/c /mnt/a/h/2d/2b/2c $ btrfs subvolume snapshot -r /mnt /mnt/snap2 $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/incremental.send Simple reproducer for case 2) $ mkfs.btrfs -f /dev/sdd $ mount /dev/sdd /mnt $ mkdir -p /mnt/a/b $ mkdir /mnt/a/c $ mv /mnt/a/b /mnt/a/c/b2 $ mkdir /mnt/a/e $ btrfs subvolume snapshot -r /mnt /mnt/snap1 $ btrfs send /mnt/snap1 -f /tmp/base.send $ mv /mnt/a/c/b2 /mnt/a/e/b3 $ mkdir /mnt/a/e/b3/f $ mkdir /mnt/a/h $ mv /mnt/a/c /mnt/a/e/b3/f/c2 $ mv /mnt/a/e /mnt/a/h/e2 $ btrfs subvolume snapshot -r /mnt /mnt/snap2 $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/incremental.send Another simple reproducer for case 2) $ mkfs.btrfs -f /dev/sdd $ mount /dev/sdd /mnt $ mkdir -p /mnt/a/b $ mkdir /mnt/a/c $ mkdir /mnt/a/b/d $ mkdir /mnt/a/c/e $ btrfs subvolume snapshot -r /mnt /mnt/snap1 $ btrfs send /mnt/snap1 -f /tmp/base.send $ mkdir /mnt/a/b/d/f $ mkdir /mnt/a/b/g $ mv /mnt/a/c/e /mnt/a/b/g/e2 $ mv /mnt/a/c /mnt/a/b/d/f/c2 $ mv /mnt/a/b/d/f /mnt/a/b/g/e2/f2 $ btrfs subvolume snapshot -r /mnt /mnt/snap2 $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/incremental.send More complex reproducer for case 2) $ mkfs.btrfs -f /dev/sdd $ mount /dev/sdd /mnt $ mkdir -p /mnt/a/b $ mkdir -p /mnt/a/c/d $ mkdir /mnt/a/b/e $ mkdir /mnt/a/c/d/f $ mv /mnt/a/b /mnt/a/c/d/2b $ mkdir /mnt/a/x $ mkdir /mnt/a/y $ btrfs subvolume snapshot -r /mnt /mnt/snap1 $ btrfs send /mnt/snap1 -f /tmp/base.send $ mv /mnt/a/x /mnt/a/y $ mv /mnt/a/c/d/2b/e /mnt/a/c/d/2b/2e $ mv /mnt/a/c/d /mnt/a/h/2d $ mv /mnt/a/c /mnt/a/h/2d/2b/2c $ btrfs subvolume snapshot -r /mnt /mnt/snap2 $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/incremental.send For both cases the incremental send would enter an infinite loop when building path strings. While solving these cases, this change also re-implements the code to detect when directory moves/renames should be delayed. Instead of dealing with several specific cases separately, it's now more generic handling all cases with a simple detection algorithm and if when applying a delayed move/rename there's a path loop detected, it further delays the move/rename registering a new ancestor inode as the dependency inode (so our rename happens after that ancestor is renamed). Tests for these cases is being added to xfstests too. Signed-off-by: Filipe David Borba Manana <[email protected]> Signed-off-by: Chris Mason <[email protected]>
1 parent a10c407 commit f959492

File tree

1 file changed

+96
-94
lines changed

1 file changed

+96
-94
lines changed

fs/btrfs/send.c

Lines changed: 96 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,7 +2940,9 @@ static void free_waiting_dir_move(struct send_ctx *sctx,
29402940
static int add_pending_dir_move(struct send_ctx *sctx,
29412941
u64 ino,
29422942
u64 ino_gen,
2943-
u64 parent_ino)
2943+
u64 parent_ino,
2944+
struct list_head *new_refs,
2945+
struct list_head *deleted_refs)
29442946
{
29452947
struct rb_node **p = &sctx->pending_dir_moves.rb_node;
29462948
struct rb_node *parent = NULL;
@@ -2972,12 +2974,12 @@ static int add_pending_dir_move(struct send_ctx *sctx,
29722974
}
29732975
}
29742976

2975-
list_for_each_entry(cur, &sctx->deleted_refs, list) {
2977+
list_for_each_entry(cur, deleted_refs, list) {
29762978
ret = dup_ref(cur, &pm->update_refs);
29772979
if (ret < 0)
29782980
goto out;
29792981
}
2980-
list_for_each_entry(cur, &sctx->new_refs, list) {
2982+
list_for_each_entry(cur, new_refs, list) {
29812983
ret = dup_ref(cur, &pm->update_refs);
29822984
if (ret < 0)
29832985
goto out;
@@ -3020,6 +3022,48 @@ static struct pending_dir_move *get_pending_dir_moves(struct send_ctx *sctx,
30203022
return NULL;
30213023
}
30223024

3025+
static int path_loop(struct send_ctx *sctx, struct fs_path *name,
3026+
u64 ino, u64 gen, u64 *ancestor_ino)
3027+
{
3028+
int ret = 0;
3029+
u64 parent_inode = 0;
3030+
u64 parent_gen = 0;
3031+
u64 start_ino = ino;
3032+
3033+
*ancestor_ino = 0;
3034+
while (ino != BTRFS_FIRST_FREE_OBJECTID) {
3035+
fs_path_reset(name);
3036+
3037+
if (is_waiting_for_rm(sctx, ino))
3038+
break;
3039+
if (is_waiting_for_move(sctx, ino)) {
3040+
if (*ancestor_ino == 0)
3041+
*ancestor_ino = ino;
3042+
ret = get_first_ref(sctx->parent_root, ino,
3043+
&parent_inode, &parent_gen, name);
3044+
} else {
3045+
ret = __get_cur_name_and_parent(sctx, ino, gen,
3046+
&parent_inode,
3047+
&parent_gen, name);
3048+
if (ret > 0) {
3049+
ret = 0;
3050+
break;
3051+
}
3052+
}
3053+
if (ret < 0)
3054+
break;
3055+
if (parent_inode == start_ino) {
3056+
ret = 1;
3057+
if (*ancestor_ino == 0)
3058+
*ancestor_ino = ino;
3059+
break;
3060+
}
3061+
ino = parent_inode;
3062+
gen = parent_gen;
3063+
}
3064+
return ret;
3065+
}
3066+
30233067
static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
30243068
{
30253069
struct fs_path *from_path = NULL;
@@ -3031,6 +3075,7 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
30313075
struct waiting_dir_move *dm = NULL;
30323076
u64 rmdir_ino = 0;
30333077
int ret;
3078+
u64 ancestor = 0;
30343079

30353080
name = fs_path_alloc();
30363081
from_path = fs_path_alloc();
@@ -3057,11 +3102,25 @@ static int apply_dir_move(struct send_ctx *sctx, struct pending_dir_move *pm)
30573102
if (ret < 0)
30583103
goto out;
30593104

3105+
sctx->send_progress = sctx->cur_ino + 1;
3106+
ret = path_loop(sctx, name, pm->ino, pm->gen, &ancestor);
3107+
if (ret) {
3108+
LIST_HEAD(deleted_refs);
3109+
ASSERT(ancestor > BTRFS_FIRST_FREE_OBJECTID);
3110+
ret = add_pending_dir_move(sctx, pm->ino, pm->gen, ancestor,
3111+
&pm->update_refs, &deleted_refs);
3112+
if (ret < 0)
3113+
goto out;
3114+
if (rmdir_ino) {
3115+
dm = get_waiting_dir_move(sctx, pm->ino);
3116+
ASSERT(dm);
3117+
dm->rmdir_ino = rmdir_ino;
3118+
}
3119+
goto out;
3120+
}
30603121
fs_path_reset(name);
30613122
to_path = name;
30623123
name = NULL;
3063-
3064-
sctx->send_progress = sctx->cur_ino + 1;
30653124
ret = get_cur_path(sctx, pm->ino, pm->gen, to_path);
30663125
if (ret < 0)
30673126
goto out;
@@ -3185,127 +3244,74 @@ static int apply_children_dir_moves(struct send_ctx *sctx)
31853244
static int wait_for_parent_move(struct send_ctx *sctx,
31863245
struct recorded_ref *parent_ref)
31873246
{
3188-
int ret;
3247+
int ret = 0;
31893248
u64 ino = parent_ref->dir;
31903249
u64 parent_ino_before, parent_ino_after;
3191-
u64 old_gen;
31923250
struct fs_path *path_before = NULL;
31933251
struct fs_path *path_after = NULL;
31943252
int len1, len2;
3195-
int register_upper_dirs;
3196-
u64 gen;
3197-
3198-
if (is_waiting_for_move(sctx, ino))
3199-
return 1;
3200-
3201-
if (parent_ref->dir <= sctx->cur_ino)
3202-
return 0;
3203-
3204-
ret = get_inode_info(sctx->parent_root, ino, NULL, &old_gen,
3205-
NULL, NULL, NULL, NULL);
3206-
if (ret == -ENOENT)
3207-
return 0;
3208-
else if (ret < 0)
3209-
return ret;
3210-
3211-
if (parent_ref->dir_gen != old_gen)
3212-
return 0;
3213-
3214-
path_before = fs_path_alloc();
3215-
if (!path_before)
3216-
return -ENOMEM;
3217-
3218-
ret = get_first_ref(sctx->parent_root, ino, &parent_ino_before,
3219-
NULL, path_before);
3220-
if (ret == -ENOENT) {
3221-
ret = 0;
3222-
goto out;
3223-
} else if (ret < 0) {
3224-
goto out;
3225-
}
32263253

32273254
path_after = fs_path_alloc();
3228-
if (!path_after) {
3255+
path_before = fs_path_alloc();
3256+
if (!path_after || !path_before) {
32293257
ret = -ENOMEM;
32303258
goto out;
32313259
}
32323260

3233-
ret = get_first_ref(sctx->send_root, ino, &parent_ino_after,
3234-
&gen, path_after);
3235-
if (ret == -ENOENT) {
3236-
ret = 0;
3237-
goto out;
3238-
} else if (ret < 0) {
3239-
goto out;
3240-
}
3241-
3242-
len1 = fs_path_len(path_before);
3243-
len2 = fs_path_len(path_after);
3244-
if (parent_ino_before != parent_ino_after || len1 != len2 ||
3245-
memcmp(path_before->start, path_after->start, len1)) {
3246-
ret = 1;
3247-
goto out;
3248-
}
3249-
ret = 0;
3250-
32513261
/*
3252-
* Ok, our new most direct ancestor has a higher inode number but
3253-
* wasn't moved/renamed. So maybe some of the new ancestors higher in
3254-
* the hierarchy have an higher inode number too *and* were renamed
3255-
* or moved - in this case we need to wait for the ancestor's rename
3256-
* or move operation before we can do the move/rename for the current
3257-
* inode.
3262+
* Our current directory inode may not yet be renamed/moved because some
3263+
* ancestor (immediate or not) has to be renamed/moved first. So find if
3264+
* such ancestor exists and make sure our own rename/move happens after
3265+
* that ancestor is processed.
32583266
*/
3259-
register_upper_dirs = 0;
3260-
ino = parent_ino_after;
3261-
again:
3262-
while ((ret == 0 || register_upper_dirs) && ino > sctx->cur_ino) {
3263-
u64 parent_gen;
3267+
while (ino > BTRFS_FIRST_FREE_OBJECTID) {
3268+
if (is_waiting_for_move(sctx, ino)) {
3269+
ret = 1;
3270+
break;
3271+
}
32643272

32653273
fs_path_reset(path_before);
32663274
fs_path_reset(path_after);
32673275

32683276
ret = get_first_ref(sctx->send_root, ino, &parent_ino_after,
3269-
&parent_gen, path_after);
3277+
NULL, path_after);
32703278
if (ret < 0)
32713279
goto out;
32723280
ret = get_first_ref(sctx->parent_root, ino, &parent_ino_before,
32733281
NULL, path_before);
3274-
if (ret == -ENOENT) {
3275-
ret = 0;
3276-
break;
3277-
} else if (ret < 0) {
3282+
if (ret < 0 && ret != -ENOENT) {
32783283
goto out;
3284+
} else if (ret == -ENOENT) {
3285+
ret = 1;
3286+
break;
32793287
}
32803288

32813289
len1 = fs_path_len(path_before);
32823290
len2 = fs_path_len(path_after);
3283-
if (parent_ino_before != parent_ino_after || len1 != len2 ||
3284-
memcmp(path_before->start, path_after->start, len1)) {
3291+
if (ino > sctx->cur_ino &&
3292+
(parent_ino_before != parent_ino_after || len1 != len2 ||
3293+
memcmp(path_before->start, path_after->start, len1))) {
32853294
ret = 1;
3286-
if (register_upper_dirs) {
3287-
break;
3288-
} else {
3289-
register_upper_dirs = 1;
3290-
ino = parent_ref->dir;
3291-
gen = parent_ref->dir_gen;
3292-
goto again;
3293-
}
3294-
} else if (register_upper_dirs) {
3295-
ret = add_pending_dir_move(sctx, ino, gen,
3296-
parent_ino_after);
3297-
if (ret < 0 && ret != -EEXIST)
3298-
goto out;
3295+
break;
32993296
}
3300-
33013297
ino = parent_ino_after;
3302-
gen = parent_gen;
33033298
}
33043299

33053300
out:
33063301
fs_path_free(path_before);
33073302
fs_path_free(path_after);
33083303

3304+
if (ret == 1) {
3305+
ret = add_pending_dir_move(sctx,
3306+
sctx->cur_ino,
3307+
sctx->cur_inode_gen,
3308+
ino,
3309+
&sctx->new_refs,
3310+
&sctx->deleted_refs);
3311+
if (!ret)
3312+
ret = 1;
3313+
}
3314+
33093315
return ret;
33103316
}
33113317

@@ -3466,10 +3472,6 @@ verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino);
34663472
if (ret < 0)
34673473
goto out;
34683474
if (ret) {
3469-
ret = add_pending_dir_move(sctx,
3470-
sctx->cur_ino,
3471-
sctx->cur_inode_gen,
3472-
cur->dir);
34733475
*pending_move = 1;
34743476
} else {
34753477
ret = send_rename(sctx, valid_path,

0 commit comments

Comments
 (0)