#!/bin/bash
# FS QA Test No. xfs/014
#
# Test the behavior of XFS dynamic speculative preallocation at ENOSPC and
# EDQUOT conditions. Speculative preallocation allocates post-EOF space to files
# as they are extended. This test creates conditions where an fs is near a space
# limit with lingering, relatively significant preallocations and verifies that
# new writers reclaim said preallocations rather than prematurely fail with
# ENOSPC/EDQUOT.
#
#-----------------------------------------------------------------------
# Copyright (c) 2014 Red Hat, Inc.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
#-----------------------------------------------------------------------
#

seq=`basename $0`
seqres=$RESULT_DIR/$seq
echo "QA output created by $seq"

here=`pwd`
tmp=/tmp/$$
status=1	# failure is the default!

# get standard environment, filters and checks
. ./common/rc
. ./common/filter
. ./common/quota

_cleanup()
{
	cd /
	umount $LOOP_MNT 2>/dev/null
	_scratch_unmount 2>/dev/null
	rm -f $tmp.*
}
trap "_cleanup; exit \$status" 0 1 2 3 15

# Create a file using a repeated open, extending write and close pattern. This
# causes the preallocation to persist after the file is closed. Preallocation
# will not be reclaimed unless the inode is evicted or we hit an allocation
# failure.
_spec_prealloc_file()
{
	file=$1

	rm -f $file

	# a few file extending open-write-close cycles should be enough to
	# trigger the fs to retain preallocation. write 256k in 32k intervals to
	# be sure
	for i in $(seq 0 32768 262144); do
		$XFS_IO_PROG -f -c "pwrite $i 32k" $file >> $seqres.full
	done

	# write a 4k aligned amount of data to keep the calculations simple
	$XFS_IO_PROG -c "pwrite 0 128m" $file >> $seqres.full

	size=`stat -c "%s" $file`
	blocks=`stat -c "%b" $file`
	blocksize=`stat -c "%B" $file`

	prealloc_size=$((blocks * blocksize - size))
	if [ $prealloc_size -eq 0 ]; then
		echo "Warning: No speculative preallocation for $file." \
			"Check use of the allocsize= mount option."
	fi

	# keep a running total of how much preallocation we've created
	TOTAL_PREALLOC=$((TOTAL_PREALLOC + prealloc_size))
}

_consume_free_space()
{
	dir=$1

	# allocate all but 10MB of available space
	freesp=`$DF_PROG -m $dir | $AWK_PROG '/^\// { print $5 - 10 }'`
	$XFS_IO_PROG -f -c "falloc 0 ${freesp}M" $dir/spc
}

# Create several files with preallocation and consume the remaining free space
# via fallocate to the put the fs at ENOSPC. Create a set of background writers
# to write into ENOSPC and cause the preallocation to be reclaimed and
# reallocated to the new writers.
_test_enospc()
{
	dir=$1

	rm -rf $dir/*

	TOTAL_PREALLOC=0
	for i in $(seq 0 3); do
		_spec_prealloc_file $dir/pre$i
	done

	_consume_free_space $dir

	# consume 1/2 of the current preallocation across the set of 4 writers
	write_size=$((TOTAL_PREALLOC / 2 / 4))
	for i in $(seq 0 3); do
		$XFS_IO_PROG -f -c "pwrite 0 $write_size" $dir/file.$i \
			>> $seqres.full &
	done

	wait
}

# Create preallocations accounted by both user and group quotas. Set the
# associated quota hard limits to put them at EDQUOT. Verify that a new writer
# reclaims the preallocated space and proceeds without error.
_test_edquot()
{
	dir=$1

	rm -rf $dir/*

	TOTAL_PREALLOC=0
	_spec_prealloc_file $dir/user
	chown $qa_user $dir/user

	_spec_prealloc_file $dir/group
	chgrp $qa_group $dir/group

	# writing to a file under both quotas means both will be reclaimed on
	# allocation failure
	touch $dir/file
	chown $qa_user $dir/file
	chgrp $qa_group $dir/file

	# put both quotas at EDQUOT
	blks=`$XFS_QUOTA_PROG -xc "quota -u $qa_user" $dir | \
		tail -n 1 | awk '{ print $2 }'`
	$XFS_QUOTA_PROG -xc "limit -u bhard=${blks}k $qa_user" $dir
	blks=`$XFS_QUOTA_PROG -xc "quota -g $qa_group" $dir | \
		tail -n 1 | awk '{ print $2 }'`
	$XFS_QUOTA_PROG -xc "limit -g bhard=${blks}k $qa_group" $dir

	# each quota has a single file worth of preallocation to reclaim. leave
	# some wiggle room and write to 1/3 the total.
	write_size=$((TOTAL_PREALLOC / 3))
	$XFS_IO_PROG -c "pwrite 0 $write_size" $dir/file >> $seqres.full
}

# real QA test starts here
_supported_fs xfs
_supported_os Linux

_require_scratch
_require_xfs_io_command "falloc"
_require_loop
_require_quota
_require_user
_require_group

rm -f $seqres.full

echo "Silence is golden."

_scratch_mkfs_xfs >> $seqres.full 2>&1
_scratch_mount

# make sure the background eofblocks scanner doesn't interfere
orig_sp_time=`cat /proc/sys/fs/xfs/speculative_prealloc_lifetime`
echo 9999 > /proc/sys/fs/xfs/speculative_prealloc_lifetime

LOOP_FILE=$SCRATCH_MNT/$seq.fs
LOOP_MNT=$SCRATCH_MNT/$seq.mnt

$MKFS_XFS_PROG -d "file=1,name=$LOOP_FILE,size=10g" >> $seqres.full 2>&1

mkdir -p $LOOP_MNT
mount -t xfs -o loop,uquota,gquota $LOOP_FILE $LOOP_MNT || \
	_fail "Failed to mount loop fs."

_test_enospc $LOOP_MNT
_test_edquot $LOOP_MNT

umount $LOOP_MNT

echo $orig_sp_time > /proc/sys/fs/xfs/speculative_prealloc_lifetime

_scratch_unmount

status=0
exit
