• Robbie Ko's avatar
    Btrfs: incremental send, do not issue invalid rmdir operations · 01914101
    Robbie Ko authored
    When both the parent and send snapshots have a directory inode with the
    same number but different generations (therefore they are different
    inodes) and both have an entry with the same name, an incremental send
    stream will contain an invalid rmdir operation that refers to the
    orphanized name of the inode from the parent snapshot.
    
    The following example scenario shows how this happens.
    
    Parent snapshot:
    
     .
     |---- d259_old/               (ino 259, gen 9)
     |         |---- d1/           (ino 258, gen 9)
     |
     |---- f                       (ino 257, gen 9)
    
    Send snapshot:
    
     .
     |---- d258/                   (ino 258, gen 7)
     |---- d259/                   (ino 259, gen 7)
             |---- d1/             (ino 257, gen 7)
    
    When the kernel is processing inode 258 it notices that in both snapshots
    there is an inode numbered 259 that is a parent of an inode 258. However
    it ignores the fact that the inodes numbered 259 have different generations
    in both snapshots, which means they are effectively different inodes.
    Then it checks that both inodes 259 have a dentry named "d1" and because
    of that it issues a rmdir operation with orphanized name of the inode 258
    from the parent snapshot. This happens at send.c:process_record_refs(),
    which calls send.c:did_overwrite_first_ref() that returns true and because
    of that later on at process_recorded_refs() such rmdir operation is issued
    because the inode being currently processed (258) is a directory and it
    was deleted in the send snapshot (and replaced with another inode that has
    the same number and is a directory too).
    Fix this issue by comparing the generations of parent directory inodes
    that have the same number and make send.c:did_overwrite_first_ref() when
    the generations are different.
    
    The following steps reproduce the problem.
    
     $ mkfs.btrfs -f /dev/sdb
     $ mount /dev/sdb /mnt
     $ touch /mnt/f
     $ mkdir /mnt/d1
     $ mkdir /mnt/d259_old
     $ mv /mnt/d1 /mnt/d259_old/d1
     $ btrfs subvolume snapshot -r /mnt /mnt/snap1
     $ btrfs send /mnt/snap1 -f /tmp/1.snap
     $ umount /mnt
    
     $ mkfs.btrfs -f /dev/sdc
     $ mount /dev/sdc /mnt
     $ mkdir /mnt/d1
     $ mkdir /mnt/dir258
     $ mkdir /mnt/dir259
     $ mv /mnt/d1 /mnt/dir259/d1
     $ btrfs subvolume snapshot -r /mnt /mnt/snap2
     $ btrfs receive /mnt/ -f /tmp/1.snap
     # Take note that once the filesystem is created, its current
     # generation has value 7 so the inodes from the second snapshot all have
     # a generation value of 7. And after receiving the first snapshot
     # the filesystem is at a generation value of 10, because the call to
     # create the second snapshot bumps the generation to 8 (the snapshot
     # creation ioctl does a transaction commit), the receive command calls
     # the snapshot creation ioctl to create the first snapshot, which bumps
     # the filesystem's generation to 9, and finally when the receive
     # operation finishes it calls an ioctl to transition the first snapshot
     # (snap1) from RW mode to RO mode, which does another transaction commit
     # and bumps the filesystem's generation to 10. This means all the inodes
     # in the first snapshot (snap1) have a generation value of 9.
     $ rm -f /tmp/1.snap
     $ btrfs send /mnt/snap1 -f /tmp/1.snap
     $ btrfs send -p /mnt/snap1 /mnt/snap2 -f /tmp/2.snap
     $ umount /mnt
    
     $ mkfs.btrfs -f /dev/sdd
     $ mount /dev/sdd /mnt
     $ btrfs receive /mnt -f /tmp/1.snap
     $ btrfs receive -vv /mnt -f /tmp/2.snap
     receiving snapshot mysnap2 uuid=9c03962f-f620-0047-9f98-32e5a87116d9, ctransid=7 parent_uuid=d17a6e3f-14e5-df4f-be39-a7951a5399aa, parent_ctransid=9
     utimes
     unlink f
     mkdir o257-7-0
     mkdir o259-7-0
     rename o257-7-0 -> o259-7-0/d1
     chown o259-7-0/d1 - uid=0, gid=0
     chmod o259-7-0/d1 - mode=0755
     utimes o259-7-0/d1
     rmdir o258-9-0
     ERROR: rmdir o258-9-0 failed: No such file or directory
    Signed-off-by: default avatarRobbie Ko <robbieko@synology.com>
    Reviewed-by: default avatarFilipe Manana <fdmanana@suse.com>
    [Rewrote changelog to be more precise and clear]
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
    01914101
send.c 153 KB