#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2021, Oracle.  All Rights Reserved.
#
# FS QA Test No. 187
#
# Regression test for commits:
#
# 9d5e8492eee0 ("xfs: adjust rt allocation minlen when extszhint > rtextsize")
# 676a659b60af ("xfs: retry allocations when locality-based search fails")
#
# The first bug occurs when an extent size hint is set on a realtime file.
# xfs_bmapi_rtalloc adjusts the offset and length of the allocation request to
# try to satisfy the hint, but doesn't adjust minlen to match.  If the
# allocator finds free space that isn't large enough to map even a single block
# of the original request, bmapi_write will return ENOSPC and the write fails
# even though there's plenty of space.
#
# The second bug occurs when an extent size hint is set on a file, we ask to
# allocate blocks in an empty region immediately adjacent to a previous
# allocation, and the nearest available free space isn't anywhere near the
# previous allocation, the near allocator will give up and return ENOSPC, even
# if there's sufficient free realtime extents to satisfy the allocation
# request.
#
# Both bugs can be exploited by the same user call sequence, so here's a
# targeted test that runs in less time than the reproducers that are listed in
# the fix patches themselves.
#
. ./common/preamble
_begin_fstest auto quick rw realtime prealloc punch

# Import common functions.
. ./common/filter

# real QA test starts here
_supported_fs xfs
_require_scratch
_require_realtime
_require_xfs_io_command "falloc"
_require_xfs_io_command "fpunch"
_require_test_program "punch-alternating"

fill_rtdev()
{
	file=$1

	filesize=`_get_available_space $SCRATCH_MNT`
	$XFS_IO_PROG -f -c "truncate $filesize" -c "falloc 0 $filesize" $file

	chunks=20
	chunksizemb=$((filesize / chunks / 1048576))
	seq 1 $chunks | while read f; do
		echo "$((f * chunksizemb)) file size $f / 20"
		$XFS_IO_PROG -fc "falloc -k $(( (f - 1) * chunksizemb))m ${chunksizemb}m" $file
	done

	chunks=100
	chunksizemb=$((filesize / chunks / 1048576))
	seq 80 $chunks | while read f; do
		echo "$((f * chunksizemb)) file size $f / $chunks"
		$XFS_IO_PROG -fc "falloc -k $(( (f - 1) * chunksizemb))m ${chunksizemb}m" $file
	done

	filesizemb=$((filesize / 1048576))
	$XFS_IO_PROG -fc "falloc -k 0 ${filesizemb}m" $file

	# Try again anyway
	avail=`_get_available_space $SCRATCH_MNT`
	$XFS_IO_PROG -fc "pwrite -S 0x65 0 $avail" ${file}
}

echo "Format and mount"
_scratch_mkfs > $seqres.full 2>&1
_scratch_mount >> $seqres.full 2>&1

# This is a test of the rt allocator; force all files to be created realtime
_xfs_force_bdev realtime $SCRATCH_MNT

# Set the extent size hint larger than the realtime extent size.  This is
# necessary to exercise the minlen constraints on the realtime allocator.
fsbsize=$($XFS_IO_PROG -c 'statfs' $SCRATCH_MNT | grep geom.bsize | awk '{print $3}')
rtextsize_blks=$($XFS_IO_PROG -c 'statfs' $SCRATCH_MNT | grep geom.rtextsize | awk '{print $3}')
extsize=$((2 * rtextsize_blks * fsbsize))

echo "rtextsize_blks=$rtextsize_blks extsize=$extsize" >> $seqres.full
$XFS_IO_PROG -c "extsize $extsize" $SCRATCH_MNT

# Compute the geometry of the test files we're going to create.  Realtime
# volumes are simple, which means that we can control the space allocations
# exactly to exploit bugs!
#
# Since this is a test of the near rt allocator, we need to set up the test to
# have a victim file with at least one rt extent allocated to it and enough
# free space to allocate at least one more rt extent at an adjacent file
# offset.  The free space must not be immediately adjacent to the the first
# extent that we allocate to the victim file, and it must not be large enough
# to satisfy the entire allocation request all at once.
#
# Our free space fragmentation strategy is the usual fallocate-and-punch swiss
# cheese file, which means the free space is split into five sections:
#
# The first will be remapped into the victim file.
#
# The second section exists to prevent the free extents from being adjacent to
# the first section.  It will be very large, since we allocate all the rt
# space.
#
# The last three sections will have every other rt extent punched out to create
# some free space.
remap_sz=$((extsize * 2))
required_sz=$((5 * remap_sz))
free_rtspace=$(_get_available_space $SCRATCH_MNT)
if [ $free_rtspace -lt $required_sz ]; then
	_notrun "Insufficient free space on rt volume.  Needed $required_sz, saw $free_rtspace."
fi

# Allocate all the space on the rt volume so that we can control space
# allocations exactly.
fill_rtdev $SCRATCH_MNT/bigfile &>> $seqres.full

# We need at least 4 remap sections to proceed
bigfile_sz=$(stat -c '%s' $SCRATCH_MNT/bigfile)
if [ $bigfile_sz -lt $required_sz ]; then
	_notrun "Free space control file needed $required_sz bytes, got $bigfile_sz."
fi

# Remap the first remap section to a victim file.
$XFS_IO_PROG -c "fpunch 0 $remap_sz" $SCRATCH_MNT/bigfile
$XFS_IO_PROG -f -c "truncate $required_sz" -c "falloc 0 $remap_sz" $SCRATCH_MNT/victim

# Punch out every other extent of the last two sections, to fragment free space.
frag_sz=$((remap_sz * 3))
punch_off=$((bigfile_sz - frag_sz))
$here/src/punch-alternating $SCRATCH_MNT/bigfile -o $((punch_off / fsbsize)) -i $((rtextsize_blks * 2)) -s $rtextsize_blks

# Make sure we have some free rtextents.
free_rtx=$($XFS_IO_PROG -c 'statfs' $SCRATCH_MNT | grep statfs.f_bavail | awk '{print $3}')
if [ $free_rtx -eq 0 ]; then
	echo "Expected fragmented free rt space, found none."
fi

# Try to double the amount of blocks in the victim file.  On a buggy kernel,
# the rt allocator will fail immediately with ENOSPC even though we left enough
# free space for the write will complete fully.
echo "Try to write a bunch of stuff to the fragmented rt space"
$XFS_IO_PROG -c "pwrite -S 0x63 -b $remap_sz $remap_sz $remap_sz" -c stat $SCRATCH_MNT/victim >> $seqres.full

# The victim file should own at least two sections' worth of blocks.
victim_sectors=$(stat -c '%b' $SCRATCH_MNT/victim)
victim_space_usage=$((victim_sectors * 512))
expected_usage=$((remap_sz * 2))

if [ $victim_space_usage -lt $expected_usage ]; then
	echo "Victim file should be using at least $expected_usage bytes, saw $victim_space_usage."
fi

status=0
exit
