• Filipe Manana's avatar
    Btrfs: send, fix more issues related to directory renames · f959492f
    Filipe Manana authored
    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: default avatarFilipe David Borba Manana <fdmanana@gmail.com>
    Signed-off-by: default avatarChris Mason <clm@fb.com>
    f959492f
send.c 132 KB