#
# Common btrfs specific functions
#

_btrfs_get_subvolid()
{
	mnt=$1
	name=$2

	$BTRFS_UTIL_PROG sub list $mnt | egrep "\s$name$" | awk '{ print $2 }'
}

# _require_btrfs_command <command> [<subcommand>|<option>]
# We check for btrfs and (optionally) features of the btrfs command
# This function support both subfunction like "inspect-internal dump-tree" and
# options like "check --qgroup-report", and also subfunction options like
# "subvolume delete --subvolid"
_require_btrfs_command()
{
	local cmd=$1
	local param=$2
	local param_arg=$3
	local safe_param

	_require_command "$BTRFS_UTIL_PROG" btrfs
	if [ -z "$1" ]; then
		return 1;
	fi
	$BTRFS_UTIL_PROG $cmd --help &> /dev/null
	[ $? -eq 0 ] || _notrun "$BTRFS_UTIL_PROG too old (must support $cmd)"

	test -z "$param" && return

	# If $param is an option, replace leading "-"s for grep
	if [ ${param:0:1} == "-" ]; then
		safe_param=$(echo $param | sed 's/^-*//')
		$BTRFS_UTIL_PROG $cmd --help | grep -wq $safe_param || \
			_notrun "$BTRFS_UTIL_PROG too old (must support $cmd $param)"
		return
	fi

	$BTRFS_UTIL_PROG $cmd $param --help &> /dev/null
	[ $? -eq 0 ] || _notrun "$BTRFS_UTIL_PROG too old (must support $cmd $param)"

	test -z "$param_arg" && return

	# replace leading "-"s for grep
	safe_param=$(echo $param_arg | sed 's/^-*//')
	$BTRFS_UTIL_PROG $cmd $param --help | grep -wq $safe_param || \
		_notrun "$BTRFS_UTIL_PROG too old (must support $cmd $param $param_arg)"
}

# Require extra check on btrfs qgroup numbers
_require_btrfs_qgroup_report()
{
	_require_btrfs_command check --qgroup-report
	touch ${RESULT_DIR}/require_scratch.require_qgroup_report
}

_require_btrfs_dump_super()
{
	if [ ! -x "$BTRFS_SHOW_SUPER_PROG" ]; then
		_require_command "$BTRFS_UTIL_PROG" btrfs
		if ! $BTRFS_UTIL_PROG inspect-internal dump-super --help >& /dev/null; then
			_notrun "Missing btrfs-show-super or inspect-internal dump-super"
		fi
		BTRFS_SHOW_SUPER_PROG="$BTRFS_UTIL_PROG inspect-internal dump-super"
	fi
}

_run_btrfs_util_prog()
{
	run_check $BTRFS_UTIL_PROG $*
}

_require_btrfs_mkfs_feature()
{
	if [ -z $1 ]; then
		echo "Missing feature name argument for _require_btrfs_mkfs_feature"
		exit 1
	fi
	feat=$1
	$MKFS_BTRFS_PROG -O list-all 2>&1 | \
		grep '^[ \t]*'"$feat"'\b' > /dev/null 2>&1
	[ $? -eq 0 ] || \
		_notrun "Feature $feat not supported in the available version of mkfs.btrfs"
}

_require_btrfs_fs_feature()
{
	if [ -z $1 ]; then
		echo "Missing feature name argument for _require_btrfs_fs_feature"
		exit 1
	fi
	feat=$1
	modprobe btrfs > /dev/null 2>&1
	[ -e /sys/fs/btrfs/features/$feat ] || \
		_notrun "Feature $feat not supported by the available btrfs version"

	if [ $feat = "raid56" ]; then
		# Zoned btrfs only supports SINGLE profile
		_require_non_zoned_device "${SCRATCH_DEV}"
	fi
}

_require_btrfs_fs_sysfs()
{
	modprobe btrfs > /dev/null 2>&1
	[ -e /sys/fs/btrfs/features ] || \
		_notrun "Sysfs not supported by the available btrfs version"

}

_check_btrfs_filesystem()
{
	device=$1

	# If type is set, we're mounted
	type=`_fs_type $device`
	ok=1

	if [ "$type" = "$FSTYP" ]; then
		# mounted ...
		mountpoint=`_umount_or_remount_ro $device`
	fi

	if [ -f ${RESULT_DIR}/require_scratch.require_qgroup_report ]; then
		$BTRFS_UTIL_PROG check $device --qgroup-report > $tmp.qgroup_report 2>&1
		if grep -qE "Counts for qgroup.*are different" $tmp.qgroup_report ; then
			_log_err "_check_btrfs_filesystem: filesystem on $device has wrong qgroup numbers"
			echo "*** qgroup_report.$FSTYP output ***"	>>$seqres.full
			cat $tmp.qgroup_report				>>$seqres.full
			echo "*** qgroup_report.$FSTYP output ***"	>>$seqres.full
		fi
		rm -f $tmp.qgroup_report
	fi

	$BTRFS_UTIL_PROG check $device >$tmp.fsck 2>&1
	if [ $? -ne 0 ]; then
		_log_err "_check_btrfs_filesystem: filesystem on $device is inconsistent"
		echo "*** fsck.$FSTYP output ***"	>>$seqres.full
		cat $tmp.fsck				>>$seqres.full
		echo "*** end fsck.$FSTYP output"	>>$seqres.full

		ok=0
	fi
	rm -f $tmp.fsck

	if [ $ok -eq 0 ]; then
		echo "*** mount output ***"		>>$seqres.full
		_mount					>>$seqres.full
		echo "*** end mount output"		>>$seqres.full
	elif [ "$type" = "$FSTYP" ]; then
		# was mounted ...
		_mount_or_remount_rw "$MOUNT_OPTIONS" $device $mountpoint
		ok=$?
	fi

	if [ $ok -eq 0 ]; then
		status=1
		if [ "$iam" != "check" ]; then
			exit 1
		fi
		return 1
	fi

	return 0
}

_require_btrfs_dev_del_by_devid()
{
	$BTRFS_UTIL_PROG device delete --help | egrep devid > /dev/null 2>&1
	[ $? -eq 0 ] || _notrun "$BTRFS_UTIL_PROG too old "\
			"(must support 'btrfs device delete <devid> /<mnt>')"
}

# get btrfs profile configs being tested
#
# A set of pre-set profile configs are exported via _btrfs_profile_configs
# array. Default configs can be overridden by setting BTRFS_PROFILE_CONFIGS
# var in the format "metadata_profile:data_profile", multiple configs can be
# seperated by space, e.g.
# export BTRFS_PROFILE_CONFIGS="raid0:raid0 raid1:raid1 dup:single"
_btrfs_get_profile_configs()
{
	if [ "$FSTYP" != "btrfs" ]; then
		return
	fi

	if [ -z "$BTRFS_PROFILE_CONFIGS" ]; then
		# Default configurations to test.
		local configs=(
			"single:single"
			"dup:single"
			"raid0:raid0"
			"raid1:raid0"
			"raid1:raid1"
			"raid10:raid10"
			"raid5:raid5"
			"raid6:raid6"
		)
	else
		# User-provided configurations.
		local configs=(${BTRFS_PROFILE_CONFIGS[@]})
	fi

	_btrfs_profile_configs=()
	for cfg in "${configs[@]}"; do
		local supported=true
		local profiles=(${cfg/:/ })
		if [ "$1" == "replace" ]; then
			# We can't do replace with these profiles because they
			# imply only one device ($SCRATCH_DEV), and we need to
			# keep $SCRATCH_DEV around for _scratch_mount
			# and _check_scratch_fs.
			local unsupported=(
				"dup"
			)
		elif [ "$1" == "replace-missing" ]; then
			# We can't replace missing devices with these profiles
			# because there isn't enough redundancy.
			local unsupported=(
				"single"
				"dup"
				"raid0"
			)
		else
			local unsupported=()
		fi

		if _scratch_btrfs_is_zoned; then
			# Zoned btrfs only supports SINGLE profile
			unsupported+=(
				"dup"
				"raid0"
				"raid1"
				"raid1c3"
				"raid1c4"
				"raid10"
				"raid5"
				"raid6"
			)
		fi

		for unsupp in "${unsupported[@]}"; do
			if [ "${profiles[0]}" == "$unsupp" -o "${profiles[1]}" == "$unsupp" ]; then
			     if [ -z "$BTRFS_PROFILE_CONFIGS" ]; then
				     # For the default config, just omit it.
				     supported=false
			     else
				     # For user-provided config, don't run the test.
				     _notrun "Profile $unsupp not supported for $1"
			     fi
			fi
		done
		if "$supported"; then
			_btrfs_profile_configs+=("-m ${profiles[0]} -d ${profiles[1]}")
		fi
	done
	export _btrfs_profile_configs
}

# stress btrfs by running balance operation in a loop
_btrfs_stress_balance()
{
	local options=$@
	while true; do
		_run_btrfs_balance_start $options >> $seqres.full
	done
}

# stress btrfs by creating/mounting/umounting/deleting subvolume in a loop
_btrfs_stress_subvolume()
{
	local btrfs_dev=$1
	local btrfs_mnt=$2
	local subvol_name=$3
	local subvol_mnt=$4
	local stop_file=$5

	mkdir -p $subvol_mnt
	while [ ! -e $stop_file ]; do
		$BTRFS_UTIL_PROG subvolume create $btrfs_mnt/$subvol_name
		$MOUNT_PROG -o subvol=$subvol_name $btrfs_dev $subvol_mnt
		$UMOUNT_PROG $subvol_mnt
		$BTRFS_UTIL_PROG subvolume delete $btrfs_mnt/$subvol_name
	done
}

# stress btrfs by running scrub in a loop
_btrfs_stress_scrub()
{
	local btrfs_mnt=$1
	while true; do
		$BTRFS_UTIL_PROG scrub start -B $btrfs_mnt
	done
}

# stress btrfs by defragmenting every file/dir in a loop and compress file
# contents while defragmenting if second argument is not "nocompress"
_btrfs_stress_defrag()
{
	local btrfs_mnt=$1
	local compress=$2

	while true; do
		if [ "$compress" == "nocompress" ]; then
			find $btrfs_mnt \( -type f -o -type d \) -exec \
			$BTRFS_UTIL_PROG filesystem defrag {} \;
		else
			find $btrfs_mnt \( -type f -o -type d \) -exec \
			$BTRFS_UTIL_PROG filesystem defrag -clzo {} \;
			find $btrfs_mnt \( -type f -o -type d \) -exec \
			$BTRFS_UTIL_PROG filesystem defrag -czlib {} \;
		fi
	done
}

# stress btrfs by remounting it with different compression algorithms in a loop
# run this with fsstress running at background could exercise the compression
# code path and ensure no race when switching compression algorithm with constant
# I/O activity.
_btrfs_stress_remount_compress()
{
	local btrfs_mnt=$1
	while true; do
		for algo in no zlib lzo; do
			$MOUNT_PROG -o remount,compress=$algo $btrfs_mnt
		done
	done
}

# stress btrfs by replacing devices in a loop
# Note that at least 3 devices are needed in SCRATCH_DEV_POOL and the last
# device should be free(not used by btrfs)
_btrfs_stress_replace()
{
	local btrfs_mnt=$1

	# The device number in SCRATCH_DEV_POOL should be at least 3,
	# one is SCRATCH_DEV, one is to be replaced, one is free device
	# we won't replace SCRATCH_DEV, see below for reason
	if [ "`echo $SCRATCH_DEV_POOL | wc -w`" -lt 3 ]; then
		echo "_btrfs_stress_replace requires at least 3 devices in SCRATCH_DEV_POOL"
		return
	fi

	# take the last device as the first free_dev
	local free_dev="`echo $SCRATCH_DEV_POOL | $AWK_PROG '{print $NF}'`"

	# free_dev should be really free
	if $BTRFS_UTIL_PROG filesystem show $btrfs_mnt | grep -q "$free_dev"; then
		echo "_btrfs_stress_replace: $free_dev is used by btrfs"
		return
	fi

	# dev_pool is device list being currently used by btrfs (excluding SCRATCH_DEV)
	# and can be replaced. We don't replace SCRATCH_DEV because it will be used in
	# _scratch_mount and _check_scratch_fs etc.
	local dev_pool=`echo $SCRATCH_DEV_POOL | sed -e "s# *$SCRATCH_DEV *##" \
			-e "s# *$free_dev *##"`

	# set the first device in dev_pool as the first src_dev to be replaced
	local src_dev=`echo $dev_pool | $AWK_PROG '{print $1}'`

	echo "dev_pool=$dev_pool"
	echo "free_dev=$free_dev, src_dev=$src_dev"
	while true; do
		echo "Replacing $src_dev with $free_dev"
		$BTRFS_UTIL_PROG replace start -fB $src_dev $free_dev $btrfs_mnt
		if [ $? -ne 0 ]; then
			# don't update src_dev and free_dev if replace failed
			continue
		fi
		dev_pool="$dev_pool $free_dev"
		dev_pool=`echo $dev_pool | sed -e "s# *$src_dev *##"`
		free_dev=$src_dev
		src_dev=`echo $dev_pool | $AWK_PROG '{print $1}'`
	done
}

# find the right option to force output in bytes, older versions of btrfs-progs
# print that by default, newer print human readable numbers with unit suffix
_btrfs_qgroup_units()
{
	$BTRFS_UTIL_PROG qgroup show --help 2>&1 | grep -q -- --raw && echo "--raw"
}

_btrfs_compression_algos()
{
	echo zlib
	for feature in /sys/fs/btrfs/features/compress_*; do
		echo "${feature#/sys/fs/btrfs/features/compress_}"
	done
}

# run btrfs balance start with required --full-balance if available.
_run_btrfs_balance_start()
{
	local bal_opt=""

	$BTRFS_UTIL_PROG balance start --help | grep -q "full-balance"
	(( $? == 0 )) && bal_opt="--full-balance"

	$BTRFS_UTIL_PROG balance start $bal_opt $*
}

#return the sector size of the btrfs scratch fs
_scratch_btrfs_sectorsize()
{
	$BTRFS_UTIL_PROG inspect-internal dump-super $SCRATCH_DEV |\
		grep sectorsize | awk '{print $2}'
}

_btrfs_supports_forget()
{
	$BTRFS_UTIL_PROG device scan --help | grep -wq forget && \
		$BTRFS_UTIL_PROG device scan --forget > /dev/null 2>&1
}

_require_btrfs_forget_or_module_loadable()
{
	_btrfs_supports_forget && return

	_require_loadable_fs_module "btrfs"
}

_btrfs_forget_or_module_reload()
{
	_btrfs_supports_forget && return

	_reload_fs_module "btrfs"
}

# Test cases which utilized _btrfs_forget_or_module_reload() must call this
# to make sure TEST_DEV can still be mounted. As TEST_DEV can be part of a
# multi-device btrfs.
_btrfs_rescan_devices()
{
	$BTRFS_UTIL_PROG device scan &> /dev/null
}

_scratch_btrfs_is_zoned()
{
	[ `_zone_type ${SCRATCH_DEV}` != "none" ] && return 0
	return 1
}

_require_btrfs_sysfs_fsid()
{
	local fsid

	fsid=$($BTRFS_UTIL_PROG filesystem show $TEST_DIR |grep uuid: |\
	       awk '{print $NF}')

	# Check if the kernel has sysfs fsid support.
	# Following kernel patch adds it:
	#   btrfs: sysfs add devinfo/fsid to retrieve fsid from the device
	test -f /sys/fs/btrfs/$fsid/devinfo/1/fsid ||\
		_notrun "Need btrfs sysfs fsid support"
}
