#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2018 Huawei Technologies Co., Ltd. All Rights Reserved.
#
# FS QA Test No. 438
#
# Test for XFS umount hang problem caused by the unceasing push
# of dquot log item in AIL. Because xfs_qm_dqflush_done() will
# not be invoked, so each time xfsaild initiates the push,
# the push will return early after checking xfs_dqflock_nowait().
#
# xfs_qm_dqflush_done() should be invoked by xfs_buf_do_callbacks().
# However after the first write and the retried write of dquota buffer
# get the same IO error, XFS will let xfsaild to restart the write and
# xfs_buf_do_callbacks() will not be inovked.
#
# This test emulates the write error by using dm-flakey. The log
# area of the XFS filesystem is excluded from the range covered by
# dm-flakey, so the XFS will not be shutdown prematurely.
#
# Fixed by upstream commit 373b058 ("xfs: Properly retry failed dquot
# items in case of error during buffer writeback")
. ./common/preamble
_begin_fstest auto quick quota freeze

# Override the default cleanup function.
_cleanup()
{
	# Make sure $SCRATCH_MNT is unfreezed
	xfs_freeze -u $SCRATCH_MNT 2>/dev/null
	[ -z "${interval}" ] || \
		sysctl -w fs.xfs.xfssyncd_centisecs=${interval} >/dev/null 2>&1
	cd /
	rm -f $tmp.*
	_unmount_flakey >/dev/null 2>&1
	_cleanup_flakey > /dev/null 2>&1
}

# inject IO write error for the XFS filesystem except its log section
make_xfs_scratch_flakey_table()
{
	local tgt=flakey
	local opt="0 1 1 error_writes"
	local dev=${SCRATCH_DEV}
	local dev_sz=$(blockdev --getsz $dev)

	# If using an external log device, just making the writing of
	# entire data/metadata area fail forever.
	if [ "${USE_EXTERNAL}" = "yes" -a ! -z "$SCRATCH_LOGDEV" ]; then
		echo "0 ${dev_sz} $tgt $dev 0 $opt"
		return
	fi

	local blk_sz=$(_scratch_xfs_get_sb_field blocksize)
	local log_ofs=$(_scratch_xfs_get_sb_field logstart)
	local log_sz=$(_scratch_xfs_get_sb_field logblocks)
	local table=""
	local ofs=0
	local sz

	log_ofs=$(_scratch_xfs_db -r -c "convert fsb ${log_ofs} bb" | \
			  $AWK_PROG '{gsub("[()]", "", $2); print $2}')
	let "log_sz *= blk_sz / 512"

	# Add a flakey target for the area before the log section
	# to make the data/metadata write fail forever
	if [ "$ofs" -lt "${log_ofs}" ]; then
		let "sz = log_ofs - ofs"
		table="$ofs $sz $tgt $dev $ofs $opt"
	fi

	# Add a linear target for the log section, so the log write
	# will work normally
	table="$table\n${log_ofs} ${log_sz} linear $dev ${log_ofs}"

	# Add a flakey target for the area after the log section
	# to make the data/metadata write fail forever
	let "ofs = log_ofs + log_sz"
	if [ "$ofs" -lt "${dev_sz}" ]; then
		let "sz = dev_sz - ofs"
		table="$table\n$ofs $sz $tgt $dev $ofs $opt"
	fi

	echo -e $table
}

# Import common functions.
. ./common/dmflakey
. ./common/quota

_supported_fs xfs

# due to the injection of write IO error, the fs will be inconsistent
_require_scratch_nocheck
_require_flakey_with_error_writes
_require_user
_require_xfs_quota
_require_freeze

echo "Silence is golden"

_scratch_mkfs > $seqres.full 2>&1

# no error will be injected
_init_flakey
$DMSETUP_PROG info >> $seqres.full
$DMSETUP_PROG table >> $seqres.full

# save the old value for _cleanup()
interval=$(sysctl -n fs.xfs.xfssyncd_centisecs 2>/dev/null)
# shorten the time waiting for the push of ail items
sysctl -w fs.xfs.xfssyncd_centisecs=100 >> $seqres.full 2>&1

_qmount_option "usrquota"
_mount_flakey

# We need to set the quota limitation twice, and inject the write error
# after the second setting. If we try to inject the write error after
# the first setting, the initialization of the dquota buffer will get
# IO error and also be retried, and during the umount process the
# write will be ended, and xfs_qm_dqflush_done() will be inovked, and
# the umount will exit normally.
$XFS_QUOTA_PROG -x -c "limit -u isoft=500 $qa_user" $SCRATCH_MNT
$XFS_QUOTA_PROG -x -c "report -ih" $SCRATCH_MNT >> $seqres.full

# ensure the initialization of the dquota buffer is done
xfs_freeze -f $SCRATCH_MNT
xfs_freeze -u $SCRATCH_MNT

# inject write IO error
FLAKEY_TABLE_ERROR=$(make_xfs_scratch_flakey_table)
_load_flakey_table ${FLAKEY_ERROR_WRITES}
$DMSETUP_PROG info >> $seqres.full
$DMSETUP_PROG table >> $seqres.full

# update the dquota buffer
$XFS_QUOTA_PROG -x -c "limit -u isoft=400 $qa_user" $SCRATCH_MNT
$XFS_QUOTA_PROG -x -c "report -ih" $SCRATCH_MNT >> $seqres.full

sync

# wait for the push of the dquota log item in AIL and
# the completion of the retried write of dquota buffer
sleep 2

_unmount_flakey

_cleanup_flakey

# success, all done
status=0
exit
