new file mode 100755
@@ -0,0 +1,183 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2021 Oracle. All Rights Reserved.
+#
+# FS QA Test 922
+#
+# 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
+
+_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
+
+# Modify as appropriate.
+_supported_fs generic
+_require_test
+
+# 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))"
+
+truncate -s $ddsize $ddfile
+truncate -s $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 | \
+ egrep -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 -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 -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 -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
new file mode 100644
@@ -0,0 +1,2 @@
+QA output created by 922
+Silence is golden