# SPDX-License-Identifier: CDDL-1.0
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#

#
# Copyright (c) 2016 by Delphix. All rights reserved.
# Copyright (c) 2023 by Klara, Inc.
#

. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/cli_root/zpool_import/zpool_import.cfg

#
# Prototype cleanup function for zpool_import tests.
#
function cleanup
{
	# clear any remaining zinjections
	log_must eval "zinject -c all > /dev/null"

	destroy_pool $TESTPOOL1

	log_must rm -f $CPATH $CPATHBKP $CPATHBKP2 $MD5FILE $MD5FILE2

	log_must rm -rf $DEVICE_DIR/*
	typeset i=0
	while (( i < $MAX_NUM )); do
		log_must truncate -s $FILE_SIZE ${DEVICE_DIR}/${DEVICE_FILE}$i
		((i += 1))
	done
	is_linux && set_tunable32 TXG_HISTORY 0
}

#
# Write a bit of data and sync several times.
# This function is intended to be used by zpool rewind tests.
#
function sync_some_data_a_few_times
{
	typeset pool=$1
	typeset -i a_few_times=${2:-10}

	typeset file="/$pool/tmpfile"
	for i in {0..$a_few_times}; do
		dd if=/dev/urandom of=${file}_$i bs=128k count=10
		sync_pool "$pool"
	done

	return 0
}

#
# Just write a moderate amount of data to the pool.
#
function write_some_data
{
	typeset pool=$1
	typeset files10mb=${2:-10}

	typeset ds="$pool/fillerds"
	zfs create $ds || return 1

	# Create 100 MB of data
	typeset file="/$ds/fillerfile"
	for i in {1..$files10mb}; do
		dd if=/dev/urandom of=$file.$i bs=128k count=80 || return 1
	done
}

#
# Create/overwrite a few datasets with files.
# Checksum all the files and store digests in a file.
#
# newdata: overwrite existing files if false.
# hashfile: file where to store xxh128 digests
# datasetname: base name for datasets
#
function _generate_data_common
{
	typeset pool=$1
	typeset newdata=$2
	typeset hashfile=$3
	typeset datasetname=$4

	typeset -i datasets=3
	typeset -i files=5
	typeset -i blocks=10

	[[ -n $hashfile ]] && rm -f $hashfile
	for i in {1..$datasets}; do
		( $newdata ) && log_must zfs create "$pool/$datasetname$i"
		for j in {1..$files}; do
			typeset file="/$pool/$datasetname$i/file$j"
			dd if=/dev/urandom of=$file bs=128k count=$blocks > /dev/null
			if [[ -n $hashfile ]]; then
				typeset cksum=$(xxh128digest $file)
				echo $cksum $file >> $hashfile
			fi
		done
		( $newdata ) && sync_pool "$pool"
	done

	return 0
}

function generate_data
{
	typeset pool=$1
	typeset hashfile="$2"
	typeset datasetname=${3:-ds}

	_generate_data_common $pool true "$hashfile" $datasetname
}

function overwrite_data
{
	typeset pool=$1
	typeset hashfile="$2"
	typeset datasetname=${3:-ds}

	_generate_data_common $1 false "$hashfile" $datasetname
}

#
# Verify hashsums of every file in hashsum file $1.
#
function verify_data_hashsums
{
	typeset hashfile=$1

	if [[ ! -f $hashfile ]]; then
		log_note "md5 sums file '$hashfile' doesn't exist"
		return 1
	fi

	while read -r digest file; do
		typeset digest1=$(xxh128digest $file)
		if [[ "$digest1" != "$digest" ]]; then
			return 1
		fi
	done < $hashfile

	return 0
}

#
# Set devices size in DEVICE_DIR to $1.
#
function increase_device_sizes
{
	typeset newfilesize=$1

	typeset -i i=0
	while (( i < $MAX_NUM )); do
		log_must truncate -s $newfilesize ${DEVICE_DIR}/${DEVICE_FILE}$i
		((i += 1))
	done
}

#
# Translate vdev names returned by zpool status into more generic names.
#
function _translate_vdev
{
	typeset vdev=$1

	#
	# eg: mirror-2 --> mirror
	# eg: draid2:4d:12c:1s-0 --> draid2
	#
	typeset keywords="mirror replacing raidz1 raidz2 raidz3 indirect draid1 draid2 draid3"
	for word in $keywords; do
		if echo $vdev |
		    grep -qE  "^${word}-[0-9]+\$|^${word}:[0-9]+d:[0-9]c:[0-9]+s-[0-9]+\$"
		then
			vdev=$word
			break
		fi
	done

	case "$vdev" in
		logs) echo "log" ;;
		raidz1) echo "raidz" ;;
		draid1) echo "draid" ;;
		*) echo $vdev ;;
	esac
}

#
# Check that pool configuration returned by zpool status matches expected
# configuration. Format for the check string is same as the vdev arguments for
# creating a pool
# Add -q for quiet mode.
#
# eg: check_pool_config pool1 "mirror c0t0d0s0 c0t1d0s0 log c1t1d0s0"
#
function check_pool_config
{
	typeset logfailure=true
	if [[ $1 == '-q' ]]; then
		logfailure=false
		shift
	fi

	typeset poolname=$1
	typeset expected=$2

	typeset status
	if ! status=$(zpool status $poolname 2>&1); then
		if $logfailure; then
			log_note "zpool status $poolname failed: $status"
		fi
		return 1
	fi

	typeset actual=""
	typeset began=false
	while read -r vdev _; do
		if ( ! $began ) && [[ $vdev == NAME ]]; then
			began=true
			continue
		fi
		( $began ) && [[ -z $vdev ]] && break;

		if ( $began ); then
			[[ -z $actual ]] && actual="$vdev" && continue
			vdev=$(_translate_vdev $vdev)
			actual="$actual $vdev"
		fi
	done <<<"$status"

	expected="$poolname $expected"

	if [[ "$actual" != "$expected" ]]; then
		if $logfailure; then
			log_note "expected pool vdevs:"
			log_note "> '$expected'"
			log_note "actual pool vdevs:"
			log_note "> '$actual'"
		fi
		return 1
	fi

	return 0
}

#
# Check that pool configuration returned by zpool status matches expected
# configuration within a given timeout in seconds. See check_pool_config().
#
# eg: wait_for_pool_config pool1 "mirror c0t0d0s0 c0t1d0s0" 60
#
function wait_for_pool_config
{
	typeset poolname=$1
	typeset expectedconfig="$2"
	typeset -i timeout=${3:-60}

	timeout=$(( $timeout + $(date +%s) ))

	while  (( $(date +%s) < $timeout )); do
		check_pool_config -q $poolname "$expectedconfig" && return 0
		sleep 3
	done

	check_pool_config $poolname "$expectedconfig"
}

#
# Check that pool status is ONLINE
#
function check_pool_healthy
{
	typeset pool=$1
	typeset status

	if ! status=$(zpool status $pool 2>&1); then
		log_note "zpool status $pool failed: $status"
		return 1
	fi

	status=$(echo "$status" | awk -v p="$pool" '!/pool:/ && $0 ~ p {print $2}')

	if [[ $status != "ONLINE" ]]; then
		log_note "Invalid zpool status for '$pool': '$status'" \
		    "!= 'ONLINE'"
		return 1
	fi

	return 0
}

#
# Return 0 if a device is currently being replaced in the pool.
#
function pool_is_replacing
{
	typeset pool=$1

	zpool status $pool | grep "replacing" | grep -q "ONLINE"
}

function set_vdev_validate_skip
{
	set_tunable32 VDEV_VALIDATE_SKIP "$1"
}

function get_zfs_txg_timeout
{
	get_tunable TXG_TIMEOUT
}

function set_zfs_txg_timeout
{
	set_tunable32 TXG_TIMEOUT "$1"
}

function set_spa_load_verify_metadata
{
	set_tunable32 SPA_LOAD_VERIFY_METADATA "$1"
}

function set_spa_load_verify_data
{
	set_tunable32 SPA_LOAD_VERIFY_DATA "$1"
}

function set_zfs_max_missing_tvds
{
	set_tunable32 MAX_MISSING_TVDS "$1"
}

#
# Use zdb to find the last txg that was synced in an active pool.
#
function get_last_txg_synced
{
	typeset pool=$1

	zdb -u $pool | awk '$1 == "txg" { print $3 }' | sort -n | tail -n 1
}
