blob: 6945ebc00ea289256eb2a6b0c77ecceb04b69764 [file] [log] [blame]
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2024 SUSE Linux Products GmbH. All Rights Reserved.
#
# FS QA Test 312
#
# Test a scenario of a compressed send stream that triggered a bug in the extent
# map merging code introduced in the merge window for 6.11.
#
. ./common/preamble
_begin_fstest auto quick send compress
_cleanup()
{
cd /
rm -fr $tmp.*
rm -fr $send_files_dir
}
. ./common/filter
_require_btrfs_send_version 2
_require_test
_require_scratch
_fixed_by_kernel_commit de9f46cb0044 \
"btrfs: fix corrupt read due to bad offset of a compressed extent map"
send_files_dir=$TEST_DIR/btrfs-test-$seq
rm -fr $send_files_dir
mkdir $send_files_dir
first_stream="$send_files_dir/1.send"
second_stream="$send_files_dir/2.send"
_scratch_mkfs >> $seqres.full 2>&1 || _fail "first mkfs failed"
_scratch_mount -o compress
# Create a compressed extent for the range [108K, 144K[. Since it's a
# non-aligned start offset, the first 3K of the extent are filled with zeroes.
# The i_size is set to 141K.
$XFS_IO_PROG -f -c "pwrite -S 0xab 111K 30K" $SCRATCH_MNT/foo >> $seqres.full
$BTRFS_UTIL_PROG subvolume snapshot -r $SCRATCH_MNT $SCRATCH_MNT/snap1 >> $seqres.full
# Overwrite a part of the extent we created before.
# This will make the send stream include an encoded write (compressed) for the
# file range [120K, 128K[.
$XFS_IO_PROG -c "pwrite -S 0xcd 120K 8K" $SCRATCH_MNT/foo >> $seqres.full
# Now do write after those extents and leaving a hole in between.
# This results in expanding the last block of the extent we first created, that
# is, in filling with zeroes the file range [141K, 144K[ (3072 bytes), which
# belongs to the block in the range [140K, 144K[.
#
# When the destination filesystem receives from the send stream a write for that
# range ([140K, 144K[) it does a btrfs_get_extent() call to find the extent map
# containing the offset 140K. There's no loaded extent map covering that range
# so it will lookg at the subvolume tree to find a file extent item covering the
# range and then finds the file extent item covering the range [108K, 144K[ which
# corresponds to the first extent written to the file, before snapshoting.
#
# Note that at this point in time the destination filesystem processed an encoded
# write for the range [120K, 128K[, which created a compressed extent map for
# that range and a corresponding ordered extent, which has not yet completed when
# it received the write command for the [140K, 144K[ range, so the corresponding
# file extent item is not yet in the subvolume tree - that only happens when the
# ordered extent completes, at btrfs_finish_one_ordered().
#
# So having found a file extent item for the range [108K, 144K[ where 140K falls
# into, it tries to add a compressed extent map for that range to the inode's
# extent map tree with a call to btrfs_add_extent_mapping() done at
# btrfs_get_extent(). That finds there's a loaded overlapping extent map for the
# range [120K, 128K[ (the extent from the previous encoded write) and then calls
# into merge_extent_mapping().
#
# The merging ended adjusting the extent map we attempted to insert, covering
# the range [108K, 144K[, to cover instead the range [128K, 144K[ (length 16K)
# instead, since there's an existing extent map for the range [120K, 128K[ and
# we are looking for a range starting at 140K (and ending at 144K). However it
# didn't adjust the extent map's offset from 0 to 20K, resulting in future reads
# reading the incorrect range from the underlying extent (108K to 124K, 16K of
# length, instead of the 128K to 144K range).
#
# Note that for the incorrect extent map, and therefore read corruption, to
# happen, we depend on specific timings - the ordered extent for the encoded
# write for the range [120K, 128K[ must not complete before the destination
# of the send stream receives the write command for the range [140K, 144K[.
#
$XFS_IO_PROG -c "pwrite -S 0xef 160K 4K" $SCRATCH_MNT/foo >> $seqres.full
$BTRFS_UTIL_PROG subvolume snapshot -r $SCRATCH_MNT $SCRATCH_MNT/snap2 >> $seqres.full
echo "Checksums in the original filesystem:"
echo "$(md5sum $SCRATCH_MNT/snap1/foo | _filter_scratch)"
echo "$(md5sum $SCRATCH_MNT/snap2/foo | _filter_scratch)"
$BTRFS_UTIL_PROG send --compressed-data -q -f $first_stream $SCRATCH_MNT/snap1
$BTRFS_UTIL_PROG send --compressed-data -q -f $second_stream \
-p $SCRATCH_MNT/snap1 $SCRATCH_MNT/snap2
_scratch_unmount
_scratch_mkfs >> $seqres.full 2>&1 || _fail "second mkfs failed"
_scratch_mount
$BTRFS_UTIL_PROG receive -q -f $first_stream $SCRATCH_MNT
$BTRFS_UTIL_PROG receive -q -f $second_stream $SCRATCH_MNT
echo "Checksums in the new filesystem:"
echo "$(md5sum $SCRATCH_MNT/snap1/foo | _filter_scratch)"
echo "$(md5sum $SCRATCH_MNT/snap2/foo | _filter_scratch)"
# success, all done
status=0
exit