##/bin/bash
# SPDX-License-Identifier: GPL-2.0+
# Copyright (c) 2025 Oracle.  All Rights Reserved.
#
# Routines for testing atomic writes.

export STATX_WRITE_ATOMIC=0x10000

_get_atomic_write_unit_min()
{
	$XFS_IO_PROG -c "statx -r -m $STATX_WRITE_ATOMIC" $1 | \
        grep atomic_write_unit_min | grep -o '[0-9]\+'
}

_get_atomic_write_unit_max()
{
	$XFS_IO_PROG -c "statx -r -m $STATX_WRITE_ATOMIC" $1 | \
        grep -w atomic_write_unit_max | grep -o '[0-9]\+'
}

_get_atomic_write_unit_max_opt()
{
	$XFS_IO_PROG -c "statx -r -m $STATX_WRITE_ATOMIC" $1 | \
        grep -w atomic_write_unit_max_opt | grep -o '[0-9]\+'
}

_get_atomic_write_segments_max()
{
	$XFS_IO_PROG -c "statx -r -m $STATX_WRITE_ATOMIC" $1 | \
        grep -w atomic_write_segments_max | grep -o '[0-9]\+'
}

_require_scratch_write_atomic_multi_fsblock()
{
	_require_scratch

	_scratch_mkfs > /dev/null 2>&1 || \
		_notrun "cannot format scratch device for atomic write checks"
	_try_scratch_mount || \
		_notrun "cannot mount scratch device for atomic write checks"

	local testfile=$SCRATCH_MNT/testfile
	touch $testfile

	local bsize=$(_get_file_block_size $SCRATCH_MNT)
	local awu_max_fs=$(_get_atomic_write_unit_max $testfile)

	_scratch_unmount

	if [ -z "$awu_max_fs" -o $awu_max_fs -lt $((bsize * 2)) ];then
		_notrun "multi-block atomic writes not supported by this filesystem"
	fi
}

_require_scratch_write_atomic()
{
	_require_scratch

	local awu_min_bdev=$(_get_atomic_write_unit_min $SCRATCH_DEV)
	local awu_max_bdev=$(_get_atomic_write_unit_max $SCRATCH_DEV)

	if [ -z "$awu_min_bdev" -o -z "$awu_max_bdev" ] || \
		[ $awu_min_bdev -eq 0 -a $awu_max_bdev -eq 0 ];then
		_notrun "write atomic not supported by this block device"
	fi

	_scratch_mkfs > /dev/null 2>&1 || \
		_notrun "cannot format scratch device for atomic write checks"
	_try_scratch_mount || \
		_notrun "cannot mount scratch device for atomic write checks"

	local testfile=$SCRATCH_MNT/testfile
	touch $testfile

	local awu_min_fs=$(_get_atomic_write_unit_min $testfile)
	local awu_max_fs=$(_get_atomic_write_unit_max $testfile)

	_scratch_unmount

	if [ -z "$awu_min_fs" -o -z "$awu_max_fs" ] || \
		[ $awu_min_fs -eq 0 -a $awu_max_fs -eq 0 ];then
		_notrun "write atomic not supported by this filesystem"
	fi
}

# Check for xfs_io commands required to run _test_atomic_file_writes
_require_atomic_write_test_commands()
{
	_require_xfs_io_command "falloc"
	_require_xfs_io_command "fpunch"
	_require_xfs_io_command pwrite -A
}

_test_atomic_file_writes()
{
    local bsize="$1"
    local testfile="$2"
    local bytes_written
    local testfile_cp="$testfile.copy"

    # Check that we can perform an atomic write of len = FS block size
    bytes_written=$($XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $bsize" $testfile | \
        grep wrote | awk -F'[/ ]' '{print $2}')
    test $bytes_written -eq $bsize || echo "atomic write len=$bsize failed"

    # Check that we can perform an atomic single-block cow write
    if cp --reflink=always $testfile $testfile_cp 2>> $seqres.full; then
        bytes_written=$($XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $bsize" $testfile_cp | \
            grep wrote | awk -F'[/ ]' '{print $2}')
        test $bytes_written -eq $bsize || echo "atomic write on reflinked file failed"
    fi

    # Check that we can perform an atomic write on an unwritten block
    $XFS_IO_PROG -c "falloc $bsize $bsize" $testfile
    bytes_written=$($XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize $bsize $bsize" $testfile | \
        grep wrote | awk -F'[/ ]' '{print $2}')
    test $bytes_written -eq $bsize || echo "atomic write to unwritten block failed"

    # Check that we can perform an atomic write on a sparse hole
    $XFS_IO_PROG -c "fpunch 0 $bsize" $testfile
    bytes_written=$($XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $bsize" $testfile | \
        grep wrote | awk -F'[/ ]' '{print $2}')
    test $bytes_written -eq $bsize || echo "atomic write to sparse hole failed"

    # Check that we can perform an atomic write on a fully mapped block
    bytes_written=$($XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $bsize" $testfile | \
        grep wrote | awk -F'[/ ]' '{print $2}')
    test $bytes_written -eq $bsize || echo "atomic write to mapped block failed"

    # Reject atomic write if len is out of bounds
    $XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $((bsize - 1))" $testfile 2>> $seqres.full && \
        echo "atomic write len=$((bsize - 1)) should fail"
    $XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 0 $((bsize + 1))" $testfile 2>> $seqres.full && \
        echo "atomic write len=$((bsize + 1)) should fail"

    # Reject atomic write when iovecs > 1
    $XFS_IO_PROG -dc "pwrite -A -D -V2 -b $bsize 0 $bsize" $testfile 2>> $seqres.full && \
        echo "atomic write only supports iovec count of 1"

    # Reject atomic write when not using direct I/O
    $XFS_IO_PROG -c "pwrite -A -V1 -b $bsize 0 $bsize" $testfile 2>> $seqres.full && \
        echo "atomic write requires direct I/O"

    # Reject atomic write when offset % bsize != 0
    $XFS_IO_PROG -dc "pwrite -A -D -V1 -b $bsize 1 $bsize" $testfile 2>> $seqres.full && \
        echo "atomic write requires offset to be aligned to bsize"
}

_simple_atomic_write() {
	local pos=$1
	local count=$2
	local file=$3
	local directio=$4

	echo "testing pos=$pos count=$count file=$file directio=$directio" >> $seqres.full
	$XFS_IO_PROG $directio -c "pwrite -b $count -V 1 -A -D $pos $count" $file >> $seqres.full
}
