RESULTS=/results
RUNSTATS="$RESULTS/run-stats"

function get_fs_config()
{
    local fs="$1"
    local cfg_dir="${2:-/root/fs}"

    if test "$fs" == "$FS_CONFIGURED" ; then
	return 0
    fi
    FS_DIR="$cfg_dir/$fs"
    if test ! -d $FS_DIR ; then
	echo "File system $fs not supported"
	echo "Could not find directory $cfg_dir/$fs"
	return 1
    fi
    MKFS_CONFIG_FILE=
    if test -n "$MKFS_CONFIG" ; then
	MKFS_CONFIG_FILE="$FS_DIR/mkfs_cfg/$MKFS_CONFIG.conf"
	if ! test -f "$MKFS_CONFIG_FILE" ; then
	    MKFS_CONFIG_FILE=
	fi
    fi
    . "$FS_DIR/config"
    FS_CONFIGURED="$fs"
    return 0
}

function clear_fs_config()
{
    unset SIZE REQUIRE_FEATURE FS_CONFIGURED
    unset FSX_AVOID FSSTRESS_AVOID XFS_IO_AVOID TEST_SET_EXCLUDE
    unset TEST_DEV TEST_DIR SCRATCH_DEV SCRATCH_MNT
    unset TEST_LOGDEV SCRATCH_LOGDEV TEST_RTDEV SCRATCH_RTDEV
    type reset_vars &> /dev/null && reset_vars
}

# Sources the config file of a single config from the variable FSTESTCFG,
# setting the necessary environment variables and defining some common
# function names like reset_vars.
# If the actual config files (test-appliance/files/root/fs) aren't at /root/fs
# for any case, the first argument can be used to change where the function
# looks.
# The config file for the next entry in FSTESTCFG will be sourced.
# variables modified include SIZE REQUIRE_FEATURE
# FSX_AVOID FSSTRESS_AVOID XFS_IO_AVOID TEST_SET_EXCLUDE
# TEST_DEV TEST_DIR SCRATCH_DEV SCRATCH_MNT
# TEST_LOGDEV SCRATCH_LOGDEV TEST_RTDEV SCRATCH_RTDEV
# The variables explicitly set by the func are FS, TC, and FSTESTCFG

# If return value is 1, no config was successfully sourced, and
# this function should be called again if FSTESTCFG is not empty
# If return value is 2, cfg_dir couldn't be found.
function get_one_fs_config() {
    if test -z "$FSTESTCFG"; then
	return 2
    fi
    local cfg_dir="${1:-/root/fs}"
    if test ! -d "$cfg_dir"; then
	return 2
    fi
    TC="${FSTESTCFG%% *}"
    case "$FSTESTCFG" in
	*\ *) FSTESTCFG="${FSTESTCFG#* }" ;;
	*)    FSTESTCFG=""
    esac
    export BASE_FSTYPE="$FSTESTTYP"
    export FS_PREFIX=
    case "$TC" in
        *:*/*)
	    # set primary fstype if provided (ex. ext4:overlay)
	    BASE_FSTYPE="${TC%%:*}"
	    FS_PREFIX="$BASE_FSTYPE:"
	    TC="${TC#*:}"
	    FS="${TC%%/*}"
	    TC="${TC#*/}"
	    ;;
	*/*)
	    FS="${TC%%/*}"
	    TC="${TC#*/}"
	    ;;
	*)
	    if test -d "$cfg_dir/$TC"; then
		FS="$TC"
		TC=default
	    else
		FS="$FSTESTTYP"
	    fi
	    ;;
    esac
    if test ! -d "$cfg_dir/$FS" ; then
	echo "Unknown file system type $FS"
	return 1
    fi
    if test "$fs" != "$FS_CONFIGURED" ; then
        clear_fs_config
    fi
    get_fs_config "$FS" "$cfg_dir"
    TC=$(test_name_alias $TC)
    if test -f "$cfg_dir/$FS/cfg/$TC.list"; then
	FSTESTCFG="$(cat $cfg_dir/$FS/cfg/$TC.list | sed -e '/#/d' \
		    -e '/^$/d' -e s:^:$FS/:) $FSTESTCFG"
	FSTESTCFG="$(echo $FSTESTCFG)"
	return 1
    fi
    if test -f "$cfg_dir/$FS/cfg/$TC"; then
	. "$cfg_dir/$FS/cfg/$TC"
    else
	echo "Unknown configuration $FS/$TC"
	return 1
    fi
    if test -n "$adjust_mkfs_options" ; then
       $adjust_mkfs_options
    fi
    if test -z "$TEST_DEV" ; then
	if test -z "$SIZE" ; then
	    echo "No TEST_DEV and no SIZE"
	    return 1
	fi
    fi
    return 0
}


# Arg 1 - location of test-appliance/files/root/fs/*.
#         This defaults to "/root/fs" if the arg is empty
#         ("/root/fs" is valid in the chroot of the test appliance)
# Arg 2 - size of pri_tst (vdb), defaults to 5
# Arg 3 - size of sm_scr (vdc), defaults to 5
# Arg 4 - size of sm_tst (vdd), defaults to 5
# Arg 5 - size of lg_tst (vde), defaults to 20
# Arg 6 - size of lg_scr (vdf), defaults to 20
# Arg 7 - size of tiny_tst (vdi), defaults to 1
# Arg 8 - size of tiny_scr (vdj), defaults to 1
# REQUIRED_PARTITION_SIZE - associative array with keys:
#     "PRI_TST", "SM_SCR", "SM_TST", "LG_TST", "LG_SCR"
#     "TOTAL_SIZE" - sum of above 5 values.
#     If any of the 5 keys is unset/null, that device is not required for this
#     set of configs.
function compute_partition_sizes()
{
    unset REQUIRED_PARTITION_SIZE CREATE_FILESTORE
    declare -g -A REQUIRED_PARTITION_SIZE
    local cfg_dir="${1:-/root/fs}"
    if test ! -d "$cfg_dir"; then
	return 1
    fi
    # store var before processing, restore it at end.
    local orig_fstest_cfg="$FSTESTCFG"

    # These values are in GB and determine the partition sizes.
    # The final scratch disk size is computed by adding the partitions to be
    # used to scratch_size.
    local scratch_size=0
    local pri_tst_in_use_size="${2:-5}"
    local sm_scr_in_use_size="${3:-5}"
    local sm_tst_in_use_size="${4:-5}"
    local lg_tst_in_use_size="${5:-20}"
    local lg_scr_in_use_size="${6:-20}"
    local tiny_tst_in_use_size="${7:-1}"
    local tiny_scr_in_use_size="${8:-1}"

    local pri_tst=true # PRI_TST_DEV PRI_TST_MNT
    local sm_scr=true # SM_SCR_DEV SM_SCR_MNT
    local sm_tst=true # SM_TST_DEV SM_TST_MNT
    local lg_tst=true # LG_TST_DEV LG_TST_MNT
    local lg_scr=true # LG_SCR_DEV LG_SCR_MNT
    local tiny_tst=true # TINY_TST_DEV TINY_TST_MNT
    local tiny_scr=true # TINY_SCR_DEV TINY_SCR_MNT

    if $pri_tst; then
	((scratch_size+=$pri_tst_in_use_size))
	REQUIRED_PARTITION_SIZE["PRI_TST"]="$pri_tst_in_use_size"
    fi
    if $sm_scr; then
	((scratch_size+=$sm_scr_in_use_size))
	REQUIRED_PARTITION_SIZE["SM_SCR"]="$sm_scr_in_use_size"
    fi
    if $sm_tst; then
	((scratch_size+=$sm_tst_in_use_size))
	REQUIRED_PARTITION_SIZE["SM_TST"]="$sm_tst_in_use_size"
    fi
    if $lg_tst; then
	((scratch_size+=$lg_tst_in_use_size))
	REQUIRED_PARTITION_SIZE["LG_TST"]="$lg_tst_in_use_size"
    fi
    if $lg_scr; then
	((scratch_size+=$lg_scr_in_use_size))
	REQUIRED_PARTITION_SIZE["LG_SCR"]="$lg_scr_in_use_size"
    fi
    if $tiny_tst; then
	((scratch_size+=$tiny_tst_in_use_size))
	REQUIRED_PARTITION_SIZE["TINY_TST"]="$tiny_tst_in_use_size"
    fi
    if $tiny_scr; then
	((scratch_size+=$tiny_scr_in_use_size))
	REQUIRED_PARTITION_SIZE["TINY_SCR"]="$tiny_scr_in_use_size"
    fi
    clear_fs_config

    # echo "Computed scratch size $scratch_size"
    # echo "Required: pri_tst ($pri_tst_in_use_Size): $pri_tst"
    # echo "sm_scr ($sm_scr_in_use_size): $sm_scr"
    # echo "sm_tst ($sm_tst_in_use_size): $sm_tst"
    # echo "lg_tst ($lg_tst_in_use_size): $lg_tst"
    # echo "lg_scr ($lg_scr_in_use_size): $lg_scr"
    REQUIRED_PARTITION_SIZE["TOTAL_SIZE"]=$scratch_size
    FSTESTCFG="$orig_fstest_cfg"
    return 0
}

function gce_run_hooks()
{
    if test -n "$RUN_ON_GCE"
    then
	run_hooks "$@"
    fi
}

function copy_xunit_results()
{
    local RESULT="$RESULT_BASE/result.xml"
    local RESULTS="$RESULT_BASE/results.xml"

    if test -f "$RESULT"
    then
	sed -i.orig -e 's/xmlns=\".*\"//' "$RESULT"
	if test -f "$RESULTS"
	then
	    merge_xunit "$RESULTS" "$RESULT"
	else
	    local fsconfig="$(cat /run/fstest-config)"
	    if ! update_properties_xunit --fsconfig "$fsconfig" "$RESULTS" \
		 "$RESULT" "$RUNSTATS"
	    then
		mv "$RESULT" "$RESULT.broken"
	    fi
	fi
	rm "$RESULT"
    fi

    /root/xfstests/bin/syncfs $RESULT_BASE
}

function record_test_error()
{
    local failure="$1"
    # if results.xml already exists, add to it
    if test -f "$RESULT_BASE/results.xml"
    then
	add_error_xunit "$RESULT_BASE/results.xml" "$failure" "xfstests.global"
    # otherwise, we add directly to result.xml
    else
	add_error_xunit "$RESULT_BASE/result.xml" "$failure" "xfstests.global"
	# in the case that the first test crashes, make sure results.xml gets
	# setup correctly via copy_xunit_results
	copy_xunit_results
    fi
}

# check to see if a device is assigned to be used
function is_dev_free() {
    local device="$1"

    for dev in "$TEST_DEV" \
	       "$SCRATCH_DEV" \
	       "$SCRATCH_LOGDEV" \
	       "$TEST_LOGDEV" \
	       "$LOGWRITES_DEV" \
	       "$SCRATCH_RTDEV" \
	       "$TEST_RTDEV"
    do
	if test "$dev" == "$1" ; then
	    return 1
	fi
    done
    return 0
}

function gen_version_files ()
{
    local version patchlevel sublevel
    local vi_cpp=/run/version_info.cpp
    local vi_sh=/run/version_info.sh

    read version patchlevel sublevel <<< \
	 $(uname -r | sed -e 's/-.*$//' | tr . ' ')

    echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + \
	((c) > 255 ? 255 : (c)))' > $vi_cpp
    echo \#define LINUX_VERSION_MAJOR $version >> $vi_cpp
    echo \#define LINUX_VERSION_PATCHLEVEL $patchlevel >> $vi_cpp
    echo \#define LINUX_VERSION_SUBLEVEL $sublevel >> $vi_cpp
    if [ $sublevel -gt 255 ]; then
	sublevel=255
    fi
    echo \#define LINUX_VERSION_CODE \
	$(expr $version \* 65536 + $patchlevel \* 256 + $sublevel) >> $vi_cpp
    test -n "$FS" && echo \#define FC $FS >> $vi_cpp
    test -n "$TC" && echo \#define TC $TC >> $vi_cpp
    test "$TC" = dax && echo \#define IS_DAX_CONFIG >> $vi_cpp

    echo LINUX_VERSION_MAJOR=$version > $vi_sh
    echo LINUX_VERSION_MINOR=$patchlevel >> $vi_sh
    echo LINUX_VERSION_SUBLEVEL=$sublevel >> $vi_sh
    test -n "$FS" && echo FS_CFG=$FS >> $vi_sh
    test -n "$TC" && echo TEST_CFG=$TC >> $vi_sh
}

function set_mkfs_config
{
    . /run/version_info.sh
    case "$LINUX_VERSION_MAJOR" in
	4)
	    case "$LINUX_VERSION_MINOR" in
		19)
		    MKFS_CONFIG=lts_4.19
		    ;;
	    esac
	    ;;
	5)
	    case "$LINUX_VERSION_MINOR" in
		4)
		    MKFS_CONFIG=lts_5.4
		    ;;
		10)
		    MKFS_CONFIG=lts_5.10
		    ;;
		14|15)
		    # 5.14 is an honorary 5.15 for RHEL 9 and SLES 15 SP4+
		    MKFS_CONFIG=lts_5.15
		    ;;
	    esac
	    ;;
	6)
	    case "$LINUX_VERSION_MINOR" in
		1)
		    MKFS_CONFIG=lts_6.1
		    ;;
		6)
		    MKFS_CONFIG=lts_6.6
		    ;;
		12)
		    MKFS_CONFIG=lts_6.12
		    ;;
	    esac
	    ;;
    esac
}

function clear_pool_devs ()
{
    if test -n "$POOL0_DEV" ; then
	losetup -d "$POOL0_DEV"
	POOL0_DEV=
    fi
    if test -n "$POOL1_DEV" ; then
	losetup -d "$POOL1_DEV"
	POOL1_DEV=
    fi
    if test -n "$POOL2_DEV" ; then
	losetup -d "$POOL2_DEV"
	POOL2_DEV=
    fi
    if test -n "$POOL3_DEV" ; then
	losetup -d "$POOL3_DEV"
	POOL3_DEV=
    fi
}

function clean_empty_dirs()
{
    local i

    for i in $(find "$RESULTS" -name results-\* -type d -print)
    do
	if test $(ls "$i" | wc -l) -le 1 -a -f "$i/check.time"
	then
	    rm "$i/check.time"
	    rmdir "$i"
	fi
    done
    for i in $(find "$RESULTS" -maxdepth 1 -mindepth 1 -type d -empty)
    do
	rmdir "$i"
    done
}

function runtests_setup()
{
    while [ "$1" != "" ]; do
	case $1 in
	    --run-once)
		RUN_ONCE=yes
		;;
	    *)
		echo "Illegal option: $1"
		exit 1
		;;
	esac
	shift
    done

    if test -z "$FSTESTAPI" ; then
	echo "Missing TEST API!"
	umount "$RESULTS"
	poweroff -f > /dev/null 2>&1
    fi

    set $FSTESTAPI

    if test "$1" -ne "$API_MAJOR" ; then
	echo " "
	echo "API version of kvm-xfstests is $1.$2"
	echo "Major version number must be $API_MAJOR"
	echo " "
	umount "$RESULTS"
	poweroff -f > /dev/null 2>&1
    fi

    if test "$2" -gt "$API_MINOR" ; then
	echo " "
	echo "API version of kvm-xfstests is $1.$2"
	echo "Minor version number is greater than $API_MINOR"
	echo "Some kvm-xfstests options may not work correctly."
	echo "please update or rebuild your root_fs.img"
	echo " "
	sleep 5
    fi

    CPUS=$(cat /proc/cpuinfo  | grep ^processor | tail -n 1 | awk '{print $3 + 1}')
    MEM=$(grep MemTotal /proc/meminfo | awk '{print $2 / 1024}')

    if test -n "$RUN_ONCE" -a -f "$RUNSTATS"
    then
	mv "$RUNSTATS" "$RUNSTATS.old"
	RESTARTED=yes
    fi

    cp /dev/null "$RUNSTATS"
    echo CMDLINE: \"$(echo $ORIG_CMDLINE | base64 -d)\" >> "$RUNSTATS"
    if test -n "$RUN_ON_GCE"
    then
	cp /usr/local/lib/gce-local.config /root/xfstests/local.config
	. /usr/local/lib/gce-funcs
	if test -n "$(gce_attribute no_vm_timeout)" ; then
	    systemctl stop gce-finalize.timer
	    systemctl disable gce-finalize.timer
	    logger -i "Disabled gce-finalize timer"
	fi
	image=$(gcloud compute disks describe --format='value(sourceImage)' \
		       --zone "$ZONE" ${instance} | \
		    sed -e 's;https://www.googleapis.com/compute/v1/projects/;;' \
			-e 's;global/images/;;')
	echo "FSTESTIMG: $image" >> "$RUNSTATS"
	echo "FSTESTPRJ: $(get_metadata_value_with_retries project-id)" >> "$RUNSTATS"
    fi
    echo -e "KERNEL: kernel\t$(uname -r -v -m)" >> "$RUNSTATS"
    sed -e 's/^/FSTESTVER: /g' /root/xfstests/git-versions >> "$RUNSTATS"
    echo FSTESTCFG: \"$FSTESTCFG\" >> "$RUNSTATS"
    echo FSTESTSET: \"$FSTESTSET\" >> "$RUNSTATS"
    echo FSTESTEXC: \"$FSTESTEXC\" >> "$RUNSTATS"
    echo FSTESTOPT: \"$FSTESTOPT\" >> "$RUNSTATS"
    echo MNTOPTS:   \"$MNTOPTS\" >> "$RUNSTATS"
    echo CPUS:      \"$CPUS\" >> "$RUNSTATS"
    echo MEM:       \"$MEM\" >> "$RUNSTATS"
    if test -n "$RUN_ON_GCE"
    then
	DMI_MEM=$(sudo dmidecode -t memory 2> /dev/null | \
		      grep "Maximum Capacity: " | \
		      sed -e 's/.*: //')
	if test $? -eq 0
	then
	    echo "DMI_MEM: $DMI_MEM (Max capacity)" >> "$RUNSTATS"
	fi
	PARAM_MEM=$(gce_attribute mem)
	if test -n "$PARAM_MEM"
	then
	    echo "PARAM_MEM: $PARAM_MEM (restricted by cmdline)" >> "$RUNSTATS"
	fi
	echo GCE ID:    \"$GCE_ID\" >> "$RUNSTATS"
	echo GCE ZONE: \"$ZONE\" >> "$RUNSTATS"
	MACHTYPE=$(basename $(get_metadata_value_with_retries machine-type))
	echo MACHINE TYPE: \"$MACHTYPE\" >> "$RUNSTATS"
	echo TESTRUNID: $TESTRUNID >> "$RUNSTATS"
    fi

    if test -z "$RUN_ON_GCE" -o -z "$RUN_ONCE"
    then
	for i in $(find "$RESULTS" -name results-\* -type d)
	do
	    if [ "$(ls -A $i)" ]; then
		find $i/* -type d -print | xargs rm -rf 2> /dev/null
		find $i -type f ! -name check.time -print | xargs rm -f 2> /dev/null
	    fi
	done
    fi

    if test -z "$RESTARTED"
    then
	cat "$RUNSTATS"
	free -m
    else
	test -f "$RESULTS/slabinfo.before" && \
	    mv "$RESULTS/slabinfo.before" "$RESULTS/slabinfo.before.old"
	test -f "$RESULTS/meminfo.before" && \
	    mv "$RESULTS/meminfo.before" "$RESULTS/meminfo.before.old"
    fi
}

function runtests_before_tests()
{
    if test ! -f /.dockerenv ; then
	echo 3 > /proc/sys/vm/drop_caches
    fi
    [ -e /proc/slabinfo ] && cp /proc/slabinfo "$RESULTS/slabinfo.before"
    cp /proc/meminfo "$RESULTS/meminfo.before"

    if test -n "$FSTESTSTR" ; then
	systemctl start stress
    fi
}

function runtests_after_tests()
{
    if test -n "$RUN_ON_GCE"
    then
	clean_empty_dirs
    fi

    if test -n "$FSTESTSTR" ; then
	[ -e /proc/slabinfo ] && cp /proc/slabinfo "$RESULTS/slabinfo.stress"
	cp /proc/meminfo "$RESULTS/meminfo.stress"
	systemctl status stress
	systemctl stop stress
    fi

    if test ! -f /.dockerenv ; then
	echo 3 > /proc/sys/vm/drop_caches
    fi
    [ -e /proc/slabinfo ] && cp /proc/slabinfo "$RESULTS/slabinfo.after"
    cp /proc/meminfo "$RESULTS/meminfo.after"
}

function runtests_save_results_tar()
{
    if test -n "$FSTEST_ARCHIVE" ; then
	tar -C $RESULTS -cf - . | \
	    xz -6e > /tmp/results.tar.xz
    fi
}
