• Filipe Manana's avatar
    Btrfs: fix send failure when root has deleted files still open · 46b2f459
    Filipe Manana authored
    The more common use case of send involves creating a RO snapshot and then
    use it for a send operation. In this case it's not possible to have inodes
    in the snapshot that have a link count of zero (inode with an orphan item)
    since during snapshot creation we do the orphan cleanup. However, other
    less common use cases for send can end up seeing inodes with a link count
    of zero and in this case the send operation fails with a ENOENT error
    because any attempt to generate a path for the inode, with the purpose
    of creating it or updating it at the receiver, fails since there are no
    inode reference items. One use case it to use a regular subvolume for
    a send operation after turning it to RO mode or turning a RW snapshot
    into RO mode and then using it for a send operation. In both cases, if a
    file gets all its hard links deleted while there is an open file
    descriptor before turning the subvolume/snapshot into RO mode, the send
    operation will encounter an inode with a link count of zero and then
    fail with errno ENOENT.
    
    Example using a full send with a subvolume:
    
      $ mkfs.btrfs -f /dev/sdb
      $ mount /dev/sdb /mnt
    
      $ btrfs subvolume create /mnt/sv1
      $ touch /mnt/sv1/foo
      $ touch /mnt/sv1/bar
    
      # keep an open file descriptor on file bar
      $ exec 73</mnt/sv1/bar
      $ unlink /mnt/sv1/bar
    
      # Turn the subvolume to RO mode and use it for a full send, while
      # holding the open file descriptor.
      $ btrfs property set /mnt/sv1 ro true
    
      $ btrfs send -f /tmp/full.send /mnt/sv1
      At subvol /mnt/sv1
      ERROR: send ioctl failed with -2: No such file or directory
    
    Example using an incremental send with snapshots:
    
      $ mkfs.btrfs -f /dev/sdb
      $ mount /dev/sdb /mnt
    
      $ btrfs subvolume create /mnt/sv1
      $ touch /mnt/sv1/foo
      $ touch /mnt/sv1/bar
    
      $ btrfs subvolume snapshot -r /mnt/sv1 /mnt/snap1
    
      $ echo "hello world" >> /mnt/sv1/bar
    
      $ btrfs subvolume snapshot -r /mnt/sv1 /mnt/snap2
    
      # Turn the second snapshot to RW mode and delete file foo while
      # holding an open file descriptor on it.
      $ btrfs property set /mnt/snap2 ro false
      $ exec 73</mnt/snap2/foo
      $ unlink /mnt/snap2/foo
    
      # Set the second snapshot back to RO mode and do an incremental send.
      $ btrfs property set /mnt/snap2 ro true
    
      $ btrfs send -f /tmp/inc.send -p /mnt/snap1 /mnt/snap2
      At subvol /mnt/snap2
      ERROR: send ioctl failed with -2: No such file or directory
    
    So fix this by ignoring inodes with a link count of zero if we are either
    doing a full send or if they do not exist in the parent snapshot (they
    are new in the send snapshot), and unlink all paths found in the parent
    snapshot when doing an incremental send (and ignoring all other inode
    items, such as xattrs and extents).
    
    A test case for fstests follows soon.
    
    CC: stable@vger.kernel.org # 4.4+
    Reported-by: default avatarMartin Wilck <martin.wilck@suse.com>
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
    Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    46b2f459
send.c 163 KB