#! /bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2021 Oracle. All Rights Reserved. # # FS QA Test No. 185 # # Regression test for commits: # # c02f6529864a ("xfs: make xfs_rtalloc_query_range input parameters const") # 9ab72f222774 ("xfs: fix off-by-one error when the last rt extent is in use") # 7e1826e05ba6 ("xfs: make fsmap backend function key parameters const") # # These commits fix a bug in fsmap where the data device fsmap function would # corrupt the high key passed to the rt fsmap function if the data device # number is smaller than the rt device number and the data device itself is # smaller than the rt device. # . ./common/preamble _begin_fstest auto fsmap prealloc punch _cleanup() { cd / rm -r -f $tmp.* _scratch_unmount test -n "$ddloop" && _destroy_loop_device "$ddloop" test -n "$rtloop" && _destroy_loop_device "$rtloop" test -n "$ddfile" && rm -f "$ddfile" test -n "$rtfile" && rm -f "$rtfile" test -n "$old_use_external" && USE_EXTERNAL="$old_use_external" } # real QA test starts here _supported_fs xfs _require_test _require_loop _require_xfs_io_command "falloc" _require_xfs_io_command "fpunch" _require_xfs_io_command "fsmap" # Synthesize data and rt volumes that as needed to trigger the bug ddfile=$TEST_DIR/data rtfile=$TEST_DIR/rt rm -f "$rtfile" "$ddfile" ddsize="$((100 * 1048576))" rtsize="$((200 * 1048576))" $XFS_IO_PROG -f -c "truncate $ddsize" $ddfile $XFS_IO_PROG -f -c "truncate $rtsize" $rtfile ddloop="$(_create_loop_device $ddfile)" rtloop="$(_create_loop_device $rtfile)" test -z "$ddloop" && _fail "Could not create data loop device" test -z "$rtloop" && _fail "Could not create rt loop device" # dataloop must have a smaller device number than rtloop because fsmap sorts # the output by device number ddmajor=$(stat -c '%t' "$ddloop") rtmajor=$(stat -c '%t' "$rtloop") test $ddmajor -le $rtmajor || \ _notrun "Data loopdev major $ddmajor larger than rt major $rtmajor" ddminor=$(stat -c '%T' "$ddloop") rtminor=$(stat -c '%T' "$rtloop") test $ddmajor -le $rtmajor || \ _notrun "Data loopdev minor $ddminor larger than rt minor $rtminor" # Inject our custom-built devices as an rt-capable scratch device. # We avoid touching "require_scratch" so that post-test fsck will not try to # run on our synthesized scratch device. old_use_external="$USE_EXTERNAL" USE_EXTERNAL=yes SCRATCH_RTDEV="$rtloop" SCRATCH_DEV="$ddloop" _scratch_mkfs >> $seqres.full _try_scratch_mount >> $seqres.full || \ _notrun "mount with injected rt device failed" # Create a file that we'll use to seed fsmap entries for the rt device, # and force the root directory to create only data device files rtfile="$SCRATCH_MNT/rtfile" $XFS_IO_PROG -R -f -c 'stat -v' $rtfile | grep -q 'fsxattr.*xflags.*realtime' || \ _notrun "could not create realtime file" $XFS_IO_PROG -c 'chattr -t' $SCRATCH_MNT rtextsize="$(stat -c '%o' $rtfile)" # Make sure the data device is smaller than the rt device by at least a few # realtime extents. ddbytes="$(stat -f -c '%S * %b' $SCRATCH_MNT | bc)" rtbytes="$(stat -f -c '%S * %b' $rtfile | bc)" test "$ddbytes" -lt "$((rtbytes + (10 * rtextsize) ))" || \ echo "data device ($ddbytes) has more bytes than rt ($rtbytes)" # Craft the layout of the sole realtime file in such a way that the only # allocated space on the realtime device is at a physical disk address that is # higher than the size of the data device. For realtime files this is really # easy because fallocate for the first rt file always starts allocating at # physical offset zero. alloc_rtx="$((rtbytes / rtextsize))" $XFS_IO_PROG -c "falloc 0 $((alloc_rtx * rtextsize))" $rtfile expected_end="$(( (alloc_rtx * rtextsize - 1) / 512 ))" # Print extent mapping of rtfile in format: # file_offset file_end physical_offset physical_end rtfile_exts() { $XFS_IO_PROG -c 'bmap -elp' $rtfile | \ grep -E -v '(^/|EXT:|hole)' | \ tr ':.[]' ' ' | \ while read junk foff fend physoff physend junk; do echo "$foff $fend $physoff $physend" done } # Make sure that we actually got the entire device. rtfile_exts | $AWK_PROG -v end=$expected_end ' { if ($3 != 0) printf("%s: unexpected physical offset %s, wanted 0\n", $0, $3); if ($4 != end) printf("%s: unexpected physical end %s, wanted %d\n", $0, $4, end); }' # Now punch out a range that is slightly larger than the size of the data # device. punch_bytes="$((ddsize + rtextsize))" punch_rtx="$((punch_bytes / rtextsize))" $XFS_IO_PROG -c "fpunch 0 $((punch_rtx * rtextsize))" $rtfile expected_offset="$((punch_rtx * rtextsize / 512))" echo "rtfile should have physical extent from $expected_offset to $expected_end sectors" >> $seqres.full # Make sure that the realtime file now has only one extent at the end of the # realtime device rtfile_exts | $AWK_PROG -v offset=$expected_offset -v end=$expected_end ' { if ($3 != offset) printf("%s: unexpected physical offset %s, wanted %d\n", $0, $3, offset); if ($4 != end) printf("%s: unexpected physical end %s, wanted %d\n", $0, $4, end); }' $XFS_IO_PROG -c 'bmap -elpv' $rtfile >> $seqres.full $XFS_IO_PROG -c 'fsmap' $SCRATCH_MNT >> $seqres.full fsmap() { $XFS_IO_PROG -c 'fsmap' $SCRATCH_MNT | \ grep -v 'EXT:' | \ tr ':.[]' ' ' | \ while read junk major minor physoff physend junk; do echo "$major:$minor $physoff $physend" done } # Check the fsmap output contains a record for the realtime device at a # physical offset higher than end of the data device and corresponding to the # beginning of the non-punched area. fsmap | $AWK_PROG -v dev="$rtmajor:$rtminor" -v offset=$expected_offset -v end=$expected_end ' BEGIN { found_start = 0; found_end = 0; } { if ($1 == dev && $2 >= offset) { found_start = 1; if ($3 == end) { found_end = 1; } } } END { if (found_start == 0) printf("No fsmap record for rtdev %s above sector %d\n", dev, offset); if (found_end == 0) printf("No fsmap record for rtdev %s ending at sector %d\n", dev, end); }' echo Silence is golden # success, all done status=0 exit