##/bin/bash
#
# Copyright (c) 2007 Silicon Graphics, 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
#
#
# common functions for excersizing hole punches with extent size hints etc.

# source dmap_scratch_mount etc.
. ./common.dmapi

_spawn_test_file() {
	echo "# spawning test file with $*"
	local blksize=$1
	local file_size=`expr $2 \* $blksize`
	local extent_size_hint=`expr $3 \* $blksize`
	local test_file=$4
	local reserve_space=$5

	if [ $extent_size_hint -ne 0 ]; then
		echo "+ setting extent size hint to $extent_size_hint"
		$XFS_IO_PROG -f \
		-c "extsize $extent_size_hint" \
		$test_file
	fi
	# print extent size hint for $test_file
	$XFS_IO_PROG -f \
	-c "extsize" \
	$test_file

	if [ "$reserve_space" == "noresv" ]; then
		echo "+ not using resvsp at file creation"
		$XFS_IO_PROG -f \
		-c "truncate $file_size" \
		$test_file
	else
		$XFS_IO_PROG -f \
		-c "truncate $file_size" \
		-c "resvsp 0 $file_size" \
		$test_file
	fi
}

_do_punch() {
	echo "# punching with $*"
	# punch or bite the ear off $test_file to create a hole
	local blksize=$1
	local punch_offset=`expr $2 \* $blksize`
	local punch_size=`expr $3 \* $blksize`
	local punch_type=$4		# u for unresvsp, d for dm_punch
	local test_file=$5

	if [ "$punch_type" == "u" ]; then
		echo "+ hole punch using unresvsp"
		$XFS_IO_PROG -f \
		-c "unresvsp $punch_offset $punch_size" \
		$test_file
	fi
	if [ "$punch_type" == "d" ]; then
		echo "+ hole punch using dmapi punch_hole"
		${DMAPI_QASUITE1_DIR}cmd/punch_hole -o $punch_offset -l $punch_size \
			${SCRATCH_MNT}/$test_file
	fi
}

_do_write() {
	echo "# writing with $*"
	local blksize=$1
	local write_offset=`expr $2 \* $blksize`
	local write_size=`expr $3 \* $blksize`
	local test_file=$4

	$XFS_IO_PROG -f \
	-c "pwrite $write_offset $write_size" \
	$test_file >/dev/null
}

_do_bmap() {
	echo "# showing file state $*"
	local test_file=$1

	$XFS_IO_PROG -f \
	-c "bmap -vvp" \
	$test_file
}

_test_punch() {
	echo "# testing $* ..."
	local blksize=$1
	# all points and sizes below are in terms of filesystem blocks
	local extsize_hint_blks=$2		# extent size hint in FS blocks, 0=do not set
	local file_size_blks=$3			# the file size in blocks
	local punch_points_blks=( $4 )	# array of places to punch holes in the file
	local punch_sizes_blks=( $5 )	# array of size of each punch in blocks
	local punch_types=( $6	)		# array of u=unresvsp or d=dm_punch
	local write_points_blks=( $7 )	# array of places to pwrite in the file
	local write_sizes_blks=( $8 )	# array of size of each write

	local punch_write_order=( $9 )	# array of punch/write operation order
									# e.g. "w p w w p" means: do 1st write...
									# then 1st punch, 2nd & 3rd write, 2nd punch
	local resvsp=${10}				# if "noresv" then don't resvsp on file create
	local filename=punch_test_file

	cd /
	umount $SCRATCH_MNT >/dev/null 2>&1

	_scratch_mkfs_xfs -bsize=$blksize >/dev/null 2>&1 \
		|| _fail "mkfs failed"

	local this_punch_type=""
	local dmap_punch_used=0
	for this_punch_type in "${punch_types[@]}"; do
		[ "$this_punch_type" == "d" ] && dmap_punch_used=1
	done
	if [ $dmap_punch_used -ne 0 ]; then
		# a punch type of dm_punch has been specified, do a dmapi mount
		echo "+ mounting with dmapi enabled"
		_dmapi_scratch_mount
	else
		# only unresvsp punch type is used, just do a normal mount
		_scratch_mount || _fail "mount failed"
	fi

	cd $SCRATCH_MNT

	# check a size is specified for each punch
	[ ${#punch_points_blks[*]} -eq ${#punch_sizes_blks[*]} ] \
		|| _fail "num punch points given does not equal num punch sizes"

	# check a type is specified for each punch
	[ ${#punch_points_blks[*]} -eq ${#punch_types[*]} ] \
		|| _fail "num punch points given does not equal num punch types"

	# check a size is specified for each write
	[ ${#write_points_blks[*]} -eq ${#write_sizes_blks[*]} ] \
		|| _fail "num write points given does not equal num write sizes"

	# check punch_write_order operations match number of punches + writes
	local total_pw_operations=`expr ${#punch_points_blks[*]} + ${#write_points_blks[*]}`
	[ $total_pw_operations -eq ${#punch_write_order[*]} ] \
		|| _fail "punch_write_order ops doesn't match number of punches + writes"

	# create the file and setup extent size hint
	_spawn_test_file $blksize $file_size_blks $extsize_hint_blks $filename $resvsp

	# do the writes and punches
	local operation=""
	local punch_index=0
	local write_index=0
	for operation in "${punch_write_order[@]}"; do
		if [ "$operation" == "p" ]; then
			_do_punch $blksize ${punch_points_blks[$punch_index]} \
				${punch_sizes_blks[$punch_index]} ${punch_types[$punch_index]} \
				$filename
			punch_index=`expr $punch_index + 1`
		fi
		if [ "$operation" == "w" ]; then
			_do_write $blksize ${write_points_blks[$write_index]} \
				${write_sizes_blks[$write_index]} $filename
			write_index=`expr $write_index + 1`
		fi
		sync
		_do_bmap $filename		# print out the state of the file
	done
}

_coalesce_extents()
{
	awk -F: '
	{
		range = $2;
		type = $3;

		split(range, bounds, "[\\[ \\.\\]]");
		low = bounds[3];
		high = bounds[5];

		if (type != prev_type) {
			if (prev_type != "")
				printf("%u]:%s\n", low - 1, prev_type);
			printf("%u: [%u..", out_count++, low);
			prev_type = type;
		}
	}
	END {
		if (prev_type != "")
			printf("%u]:%s\n", high, prev_type);
	}'
}

_filter_fiemap()
{
	awk --posix '
		$3 ~ /hole/ {
			print $1, $2, $3;
			next;
		}
		$5 ~ /0x[[:xdigit:]]*8[[:xdigit:]]{2}/ {
			print $1, $2, "unwritten";
			next;
		}
		$5 ~ /0x[[:xdigit:]]+/ {
			print $1, $2, "data";
		}' |
	_coalesce_extents
}

# Filters fiemap output to only print the 
# file offset column and whether or not
# it is an extent or a hole
_filter_hole_fiemap()
{
	awk --posix '
		$3 ~ /hole/ {
			print $1, $2, $3; 
			next;
		}   
		$5 ~ /0x[[:xdigit:]]+/ {
			print $1, $2, "extent";
		}' |
	_coalesce_extents
}


# Prints the md5 checksum of a given file
_md5_checksum()
{
	md5sum $1 | cut -d ' ' -f1
}

_filter_bmap()
{
	awk '
		$3 ~ /hole/ {
			print $1, $2, $3;
			next;
		}
		$7 ~ /10000/ {
			print $1, $2, "unwritten";
			next;
		}
		$7 ~ /00000/ {
			print $1, $2, "data"
		}' |
	_coalesce_extents
}

die_now()
{
	status=1
	exit
}

# test the different corner cases for zeroing a range:
#
#	1. into a hole
#	2. into allocated space
#	3. into unwritten space
#	4. hole -> data
#	5. hole -> unwritten
#	6. data -> hole
#	7. data -> unwritten
#	8. unwritten -> hole
#	9. unwritten -> data
#	10. hole -> data -> hole
#	11. data -> hole -> data
#	12. unwritten -> data -> unwritten
#	13. data -> unwritten -> data
#	14. data -> hole @ EOF
#	15. data -> hole @ 0
#	16. data -> cache cold ->hole
#	17. data -> hole in single block file
#
# Test file is removed, created and sync'd between tests.
#
# Use -k flag to keep the file between tests.  This will
# test the handling of pre-existing holes.
#
# Use the -d flag to not sync the file between tests.
# This will test the handling of delayed extents
#
_test_generic_punch()
{

	remove_testfile=1
	sync_cmd="-c fsync"
	OPTIND=1
	while getopts 'dk' OPTION
	do
		case $OPTION in
		k)	remove_testfile=
		;;
		d)	sync_cmd=
		;;
		?)	echo Invalid flag
		exit 1
		;;
		esac
	done
	shift $(($OPTIND - 1))

	alloc_cmd=$1
	punch_cmd=$2
	zero_cmd=$3	#if not testing zero just set to punch
	map_cmd=$4
	filter_cmd=$5
	testfile=$6
	xfs_io_opt=$7	#needs to be -F if not testing xfs

	echo "	1. into a hole"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	2. into allocated space"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 20k" $sync_cmd \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	3. into unwritten space"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 20k" \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	4. hole -> data"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 8k 8k" $sync_cmd \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	5. hole -> unwritten"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 8k 8k" \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	6. data -> hole"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 8k" $sync_cmd \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	7. data -> unwritten"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 8k" $sync_cmd \
		-c "$alloc_cmd 8k 8k" \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	8. unwritten -> hole"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 8k" \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	9. unwritten -> data"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 8k" \
		-c "pwrite 8k 8k" $sync_cmd \
		-c "$zero_cmd 4k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	10. hole -> data -> hole"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 8k 4k" $sync_cmd \
		-c "$zero_cmd 4k 12k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	11. data -> hole -> data"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 20k" \
		-c "pwrite 0 8k" \
		-c "pwrite 12k 8k" $sync_cmd \
		-c "$punch_cmd 8k 4k" \
		-c "$zero_cmd 4k 12k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	12. unwritten -> data -> unwritten"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 20k" \
		-c "pwrite 8k 4k" $sync_cmd \
		-c "$zero_cmd 4k 12k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	13. data -> unwritten -> data"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "$alloc_cmd 0 20k" \
		-c "pwrite 0k 8k" $sync_cmd \
		-c "pwrite 12k 8k" -c "fsync" \
		-c "$zero_cmd 4k 12k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	14. data -> hole @ EOF"
	rm -f $testfile
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 20k" $sync_cmd \
		-c "$zero_cmd 12k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	15. data -> hole @ 0"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 20k" $sync_cmd \
		-c "$zero_cmd 0k 8k" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

	echo "	16. data -> cache cold ->hole"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
		rm -f $testfile.2
	else
		cp $testfile $testfile.2
	fi
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 8k 12k" -c "fsync" $testfile.2 \
		> /dev/null
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate 20k" \
		-c "pwrite 0 20k" $sync_cmd \
		-c "$zero_cmd 0k 8k" \
		-c "fadvise -d" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	diff $testfile $testfile.2
	[ $? -ne 0 ] && die_now
	rm -f $testfile.2
	_md5_checksum $testfile

	echo "	17. data -> hole in single block file"
	if [ "$remove_testfile" ]; then
		rm -f $testfile
	fi
	block_size=`stat -f $TEST_DEV | grep "Block size" | cut -d " " -f3`
	$XFS_IO_PROG $xfs_io_opt -f -c "truncate $block_size" \
		-c "pwrite 0 $block_size" $sync_cmd \
		-c "$zero_cmd 128 128" \
		-c "$map_cmd -v" $testfile | $filter_cmd
	[ $? -ne 0 ] && die_now
	_md5_checksum $testfile

}
