#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2021 Chandan Babu R.  All Rights Reserved.
#
# FS QA Test 533
#
# Verify that XFS does not cause inode fork's extent count to overflow when
# adding/removing directory entries.
. ./common/preamble
_begin_fstest auto quick dir hardlink symlink

# Import common functions.
. ./common/filter
. ./common/inject
. ./common/populate

# real QA test starts here

_supported_fs xfs
_require_scratch
_require_xfs_debug
_require_test_program "punch-alternating"
_require_xfs_io_error_injection "reduce_max_iextents"
_require_xfs_io_error_injection "bmap_alloc_minlen_extent"

_scratch_mkfs_sized $((1024 * 1024 * 1024)) | _filter_mkfs >> $seqres.full 2> $tmp.mkfs
. $tmp.mkfs

# Filesystems with directory block size greater than one FSB will not be tested,
# since "7 (i.e. XFS_DA_NODE_MAXDEPTH + 1 data block + 1 free block) * 2 (fsb
# count) = 14" is greater than the pseudo max extent count limit of 10.
# Extending the pseudo max limit won't help either.  Consider the case where 1
# FSB is 1k in size and 1 dir block is 64k in size (i.e. fsb count = 64). In
# this case, the pseudo max limit has to be greater than 7 * 64 = 448 extents.
if (( $dirbsize > $dbsize )); then
	_notrun "Directory block size ($dirbsize) is larger than FSB size ($dbsize)"
fi

echo "Format and mount fs"
_scratch_mkfs_sized $((1024 * 1024 * 1024)) >> $seqres.full
_scratch_mount >> $seqres.full

# Disable realtime inherit flag (if any) on root directory so that space on data
# device gets fragmented rather than realtime device.
_xfs_force_bdev data $SCRATCH_MNT

echo "Consume free space"
fillerdir=$SCRATCH_MNT/fillerdir
nr_free_blks=$(stat -f -c '%f' $SCRATCH_MNT)
nr_free_blks=$((nr_free_blks * 90 / 100))

_fill_fs $((dbsize * nr_free_blks)) $fillerdir $dbsize 0 >> $seqres.full 2>&1

echo "Create fragmented filesystem"
for dentry in $(ls -1 $fillerdir/); do
	$here/src/punch-alternating $fillerdir/$dentry >> $seqres.full
done

echo "Inject reduce_max_iextents error tag"
_scratch_inject_error reduce_max_iextents 1

echo "Inject bmap_alloc_minlen_extent error tag"
_scratch_inject_error bmap_alloc_minlen_extent 1

dent_len=255

echo "* Create directory entries"

testdir=$SCRATCH_MNT/testdir
mkdir $testdir

nr_dents=$((dbsize * 20 / dent_len))
for i in $(seq 1 $nr_dents); do
	dentry="$(printf "%0255d" $i)"
	touch ${testdir}/$dentry >> $seqres.full 2>&1 || break
done

echo "Verify directory's extent count"
nextents=$(_xfs_get_fsxattr nextents $testdir)
if (( $nextents > 10 )); then
	echo "Extent count overflow check failed: nextents = $nextents"
	exit 1
fi

rm -rf $testdir

echo "* Rename: Populate destination directory"

dstdir=$SCRATCH_MNT/dstdir
mkdir $dstdir

nr_dents=$((dirbsize * 20 / dent_len))

echo "Populate \$dstdir by moving new directory entries"
for i in $(seq 1 $nr_dents); do
	dentry="$(printf "%0255d" $i)"
	dentry=${SCRATCH_MNT}/${dentry}
	touch $dentry || break
	mv $dentry $dstdir >> $seqres.full 2>&1 || break
done

rm $dentry

echo "Verify \$dstdir's extent count"

nextents=$(_xfs_get_fsxattr nextents $dstdir)
if (( $nextents > 10 )); then
	echo "Extent count overflow check failed: nextents = $nextents"
	exit 1
fi

rm -rf $dstdir

echo "* Create multiple hard links to a single file"

testdir=$SCRATCH_MNT/testdir
mkdir $testdir

testfile=$SCRATCH_MNT/testfile
touch $testfile

nr_dents=$((dirbsize * 20 / dent_len))

echo "Create multiple hardlinks"
for i in $(seq 1 $nr_dents); do
	dentry="$(printf "%0255d" $i)"
	ln $testfile ${testdir}/${dentry} >> $seqres.full 2>&1 || break
done

rm $testfile

echo "Verify directory's extent count"
nextents=$(_xfs_get_fsxattr nextents $testdir)
if (( $nextents > 10 )); then
	echo "Extent count overflow check failed: nextents = $nextents"
	exit 1
fi

rm -rf $testdir

echo "* Create multiple symbolic links to a single file"

testdir=$SCRATCH_MNT/testdir
mkdir $testdir

testfile=$SCRATCH_MNT/testfile
touch $testfile

nr_dents=$((dirbsize * 20 / dent_len))

echo "Create multiple symbolic links"
for i in $(seq 1 $nr_dents); do
	dentry="$(printf "%0255d" $i)"
	ln -s $testfile ${testdir}/${dentry} >> $seqres.full 2>&1 || break;
done

rm $testfile

echo "Verify directory's extent count"
nextents=$(_xfs_get_fsxattr nextents $testdir)
if (( $nextents > 10 )); then
	echo "Extent count overflow check failed: nextents = $nextents"
	exit 1
fi

rm -rf $testdir

# success, all done
status=0
exit
