• Filipe Manana's avatar
    Btrfs: send, fix file corruption due to incorrect cloning operations · d906d49f
    Filipe Manana authored
    If we have a file that shares an extent with other files, when processing
    the extent item relative to a shared extent, we blindly issue a clone
    operation that will target a length matching the length in the extent item
    and uses as a source some other file the receiver already has and points
    to the same extent. However that range in the other file might not
    exclusively point only to the shared extent, and so using that length
    will result in the receiver getting a file with different data from the
    one in the send snapshot. This issue happened both for incremental and
    full send operations.
    
    So fix this by issuing clone operations with lengths that don't cover
    regions of the source file that point to different extents (or have holes).
    
    The following test case for fstests reproduces the problem.
    
      seq=`basename $0`
      seqres=$RESULT_DIR/$seq
      echo "QA output created by $seq"
    
      tmp=/tmp/$$
      status=1	# failure is the default!
      trap "_cleanup; exit \$status" 0 1 2 3 15
    
      _cleanup()
      {
          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
      _require_scratch
      _need_to_be_root
      _require_cp_reflink
      _require_xfs_io_command "fpunch"
    
      send_files_dir=$TEST_DIR/btrfs-test-$seq
    
      rm -f $seqres.full
      rm -fr $send_files_dir
      mkdir $send_files_dir
    
      _scratch_mkfs >>$seqres.full 2>&1
      _scratch_mount
    
      # Create our test file with a single 100K extent.
      $XFS_IO_PROG -f -c "pwrite -S 0xaa 0K 100K" \
         $SCRATCH_MNT/foo | _filter_xfs_io
    
      # Clone our file into a new file named bar.
      cp --reflink=always $SCRATCH_MNT/foo $SCRATCH_MNT/bar
    
      # Now overwrite parts of our foo file.
      $XFS_IO_PROG -c "pwrite -S 0xbb 50K 10K" \
         -c "pwrite -S 0xcc 90K 10K" \
         -c "fpunch 70K 10k" \
         $SCRATCH_MNT/foo | _filter_xfs_io
    
      _run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT \
         $SCRATCH_MNT/snap
    
      echo "File digests in the original filesystem:"
      md5sum $SCRATCH_MNT/snap/foo | _filter_scratch
      md5sum $SCRATCH_MNT/snap/bar | _filter_scratch
    
      _run_btrfs_util_prog send $SCRATCH_MNT/snap -f $send_files_dir/1.snap
    
      # Now recreate the filesystem by receiving the send stream and verify
      # we get the same file contents that the original filesystem had.
      _scratch_unmount
      _scratch_mkfs >>$seqres.full 2>&1
      _scratch_mount
    
      _run_btrfs_util_prog receive $SCRATCH_MNT -f $send_files_dir/1.snap
    
      # We expect the destination filesystem to have exactly the same file
      # data as the original filesystem.
      # The btrfs send implementation had a bug where it sent a clone
      # operation from file foo into file bar covering the whole [0, 100K[
      # range after creating and writing the file foo. This was incorrect
      # because the file bar now included the updates done to file foo after
      # we cloned foo to bar, breaking the COW nature of reflink copies
      # (cloned extents).
      echo "File digests in the new filesystem:"
      md5sum $SCRATCH_MNT/snap/foo | _filter_scratch
      md5sum $SCRATCH_MNT/snap/bar | _filter_scratch
    
      status=0
      exit
    
    Another test case that reproduces the problem when we have compressed
    extents:
    
      seq=`basename $0`
      seqres=$RESULT_DIR/$seq
      echo "QA output created by $seq"
    
      tmp=/tmp/$$
      status=1	# failure is the default!
      trap "_cleanup; exit \$status" 0 1 2 3 15
    
      _cleanup()
      {
          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
      _require_scratch
      _need_to_be_root
      _require_cp_reflink
    
      send_files_dir=$TEST_DIR/btrfs-test-$seq
    
      rm -f $seqres.full
      rm -fr $send_files_dir
      mkdir $send_files_dir
    
      _scratch_mkfs >>$seqres.full 2>&1
      _scratch_mount "-o compress"
    
      # Create our file with an extent of 100K starting at file offset 0K.
      $XFS_IO_PROG -f -c "pwrite -S 0xaa 0K 100K"       \
                      -c "fsync"                        \
                      $SCRATCH_MNT/foo | _filter_xfs_io
    
      # Rewrite part of the previous extent (its first 40K) and write a new
      # 100K extent starting at file offset 100K.
      $XFS_IO_PROG -c "pwrite -S 0xbb 0K 40K"    \
              -c "pwrite -S 0xcc 100K 100K"      \
              $SCRATCH_MNT/foo | _filter_xfs_io
    
      # Our file foo now has 3 file extent items in its metadata:
      #
      # 1) One covering the file range 0 to 40K;
      # 2) One covering the file range 40K to 100K, which points to the first
      #    extent we wrote to the file and has a data offset field with value
      #    40K (our file no longer uses the first 40K of data from that
      #    extent);
      # 3) One covering the file range 100K to 200K.
    
      # Now clone our file foo into file bar.
      cp --reflink=always $SCRATCH_MNT/foo $SCRATCH_MNT/bar
    
      # Create our snapshot for the send operation.
      _run_btrfs_util_prog subvolume snapshot -r $SCRATCH_MNT \
              $SCRATCH_MNT/snap
    
      echo "File digests in the original filesystem:"
      md5sum $SCRATCH_MNT/snap/foo | _filter_scratch
      md5sum $SCRATCH_MNT/snap/bar | _filter_scratch
    
      _run_btrfs_util_prog send $SCRATCH_MNT/snap -f $send_files_dir/1.snap
    
      # Now recreate the filesystem by receiving the send stream and verify we
      # get the same file contents that the original filesystem had.
      # Btrfs send used to issue a clone operation from foo's range
      # [80K, 140K[ to bar's range [40K, 100K[ when cloning the extent pointed
      # to by foo's second file extent item, this was incorrect because of bad
      # accounting of the file extent item's data offset field. The correct
      # range to clone from should have been [40K, 100K[.
      _scratch_unmount
      _scratch_mkfs >>$seqres.full 2>&1
      _scratch_mount "-o compress"
    
      _run_btrfs_util_prog receive $SCRATCH_MNT -f $send_files_dir/1.snap
    
      echo "File digests in the new filesystem:"
      # Must match the digests we got in the original filesystem.
      md5sum $SCRATCH_MNT/snap/foo | _filter_scratch
      md5sum $SCRATCH_MNT/snap/bar | _filter_scratch
    
      status=0
      exit
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
    d906d49f
send.c 145 KB