• Filipe Manana's avatar
    Btrfs: send, fix corner case for reference overwrite detection · b786f16a
    Filipe Manana authored
    When the inode given to did_overwrite_ref() matches the current progress
    and has a reference that collides with the reference of other inode that
    has the same number as the current progress, we were always telling our
    caller that the inode's reference was overwritten, which is incorrect
    because the other inode might be a new inode (different generation number)
    in which case we must return false from did_overwrite_ref() so that its
    callers don't use an orphanized path for the inode (as it will never be
    orphanized, instead it will be unlinked and the new inode created later).
    The following test case for fstests reproduces the issue:
      seq=`basename $0`
      echo "QA output created by $seq"
      status=1	# failure is the default!
      trap "_cleanup; exit \$status" 0 1 2 3 15
          rm -fr $send_files_dir
          rm -f $tmp.*
      # get standard environment, filters and checks
      . ./common/rc
      . ./common/filter
      # real QA test starts here
      _supported_fs btrfs
      _supported_os Linux
      rm -f $seqres.full
      rm -fr $send_files_dir
      mkdir $send_files_dir
      _scratch_mkfs >>$seqres.full 2>&1
      # Create our test file with a single extent of 64K.
      mkdir -p $SCRATCH_MNT/foo
      $XFS_IO_PROG -f -c "pwrite -S 0xaa 0 64K" $SCRATCH_MNT/foo/bar \
          | _filter_xfs_io
      _run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT \
      _run_btrfs_util_prog subvolume snapshot $SCRATCH_MNT \
      echo "File digest before being replaced:"
      md5sum $SCRATCH_MNT/mysnap1/foo/bar | _filter_scratch
      # Remove the file and then create a new one in the same location with
      # the same name but with different content. This new file ends up
      # getting the same inode number as the previous one, because that inode
      # number was the highest inode number used by the snapshot's root and
      # therefore when attempting to find the a new inode number for the new
      # file, we end up reusing the same inode number. This happens because
      # currently btrfs uses the highest inode number summed by 1 for the
      # first inode created once a snapshot's root is loaded (done at
      # fs/btrfs/inode-map.c:btrfs_find_free_objectid in the linux kernel
      # tree).
      # Having these two different files in the snapshots with the same inode
      # number (but different generation numbers) caused the btrfs send code
      # to emit an incorrect path for the file when issuing an unlink
      # operation because it failed to realize they were different files.
      rm -f $SCRATCH_MNT/mysnap2/foo/bar
      $XFS_IO_PROG -f -c "pwrite -S 0xbb 0 96K" \
          $SCRATCH_MNT/mysnap2/foo/bar | _filter_xfs_io
      _run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT/mysnap2 \
      _run_btrfs_util_prog send $SCRATCH_MNT/mysnap1 -f $send_files_dir/1.snap
      _run_btrfs_util_prog send -p $SCRATCH_MNT/mysnap1 \
          $SCRATCH_MNT/mysnap2_ro -f $send_files_dir/2.snap
      echo "File digest in the original filesystem after being replaced:"
      md5sum $SCRATCH_MNT/mysnap2_ro/foo/bar | _filter_scratch
      # Now recreate the filesystem by receiving both send streams and verify
      # we get the same file contents that the original filesystem had.
      _scratch_mkfs >>$seqres.full 2>&1
      _run_btrfs_util_prog receive -vv $SCRATCH_MNT -f $send_files_dir/1.snap
      _run_btrfs_util_prog receive -vv $SCRATCH_MNT -f $send_files_dir/2.snap
      echo "File digest in the new filesystem:"
      # Must match the digest from the new file.
      md5sum $SCRATCH_MNT/mysnap2_ro/foo/bar | _filter_scratch
    Reported-by: default avatarMartin Raiber <martin@urbackup.org>
    Fixes: 8b191a68 ("Btrfs: incremental send, check if orphanized dir inode needs delayed rename")
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
send.c 141 KB