#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
#
# FS QA Test 061
#
# This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to
# stress the write and end-io unwritten conversion code paths. We brute force
# this for all possible blocksize and clustersizes and after each iteration we
# ensure the data was not torn or corrupted using fio crc verification.
#
# Note that in this test we use overlapping atomic writes of same io size.
# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and
# SCSI provide this guarantee as an inseparable feature to power-fail
# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the
# write due to racing ioend unwritten conversion.
#
# The value of this test is that we make sure the RWF_ATOMIC is handled
# correctly by ext4 as well as test that the block layer doesn't split or only
# generate multiple bios for an atomic write.

. ./common/preamble
. ./common/atomicwrites

_begin_fstest auto rw stress atomicwrites

_require_scratch_write_atomic
_require_fio_atomic_writes
_require_aiodio

FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
SIZE=$((100*1024*1024))

# Calculate fsblocksize as per bdev atomic write units.
bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV)
bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV)
bs=$(_max 4096 "$bdev_awu_min")

function create_fio_configs()
{
	local bsize=$1
	create_fio_aw_config $bsize
	create_fio_verify_config $bsize
}

function create_fio_verify_config()
{
	local bsize=$1
cat >$fio_verify_config <<EOF
	[aio-dio-aw-verify]
	direct=1
	ioengine=libaio
	rw=read
	bs=$bsize
	fallocate=native
	filename=$SCRATCH_MNT/test-file
	size=$SIZE
	iodepth=$FIO_LOAD
	numjobs=$FIO_LOAD
	atomic=1
	group_reporting=1

	verify_only=1
	verify_state_save=0
	verify=crc32c
	verify_fatal=1
	verify_write_sequence=0
EOF
}

function create_fio_aw_config()
{
	local bsize=$1
cat >$fio_aw_config <<EOF
	[aio-dio-aw]
	direct=1
	ioengine=libaio
	rw=randwrite
	bs=$bsize
	fallocate=native
	filename=$SCRATCH_MNT/test-file
	size=$SIZE
	iodepth=$FIO_LOAD
	numjobs=$FIO_LOAD
	group_reporting=1
	atomic=1

	verify_state_save=0
	verify=crc32c
	do_verify=0

EOF
}

run_test_one() {
	local bs=$1
	local cs=$2
	local iosize=$3

	MKFS_OPTIONS="-O bigalloc -b $bs -C $cs"
	_scratch_mkfs_ext4  >> $seqres.full 2>&1 || return
	if _try_scratch_mount >> $seqres.full 2>&1; then
		echo "== Testing: bs=$bs cs=$cs iosize=$iosize ==" >> $seqres.full

		touch $SCRATCH_MNT/f1
		create_fio_configs $iosize

		cat $fio_aw_config >> $seqres.full
		echo >> $seqres.full
		cat $fio_verify_config >> $seqres.full

		$FIO_PROG $fio_aw_config >> $seqres.full
		ret1=$?

		$FIO_PROG $fio_verify_config >> $seqres.full
		ret2=$?

		_scratch_unmount

		[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
	fi
}

run_test() {
	local bs=$1

	# cluster sizes above 16 x blocksize are experimental so avoid them
	# Also, cap cluster size at 128kb to keep it reasonable for large
	# blocks size
	max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024)))

	# Fuzz for combinations of blocksize, clustersize and
	# iosize that cover most of the cases
	run_test_one $bs $bs $bs
	run_test_one $bs $max_cs $bs
	run_test_one $bs $max_cs $max_cs
	run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs)
}

# Let's create a sample fio config to check whether fio supports all options.
fio_aw_config=$tmp.aw.fio
fio_verify_config=$tmp.verify.fio
fio_out=$tmp.fio.out

create_fio_configs $bs
_require_fio $fio_aw_config

for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do
	run_test $bs
done

# success, all done
echo Silence is golden
status=0
exit
