#!/bin/bash
# SPDX-License-Identifier: GPL-3.0+
# Copyright (C) 2017 Omar Sandoval
#
# Default helper functions.

shopt -s extglob

. common/shellcheck
# Include fio helpers by default.
. common/fio
. common/cgroup

# If a test runs multiple "subtests", then each subtest should typically run
# for TIMEOUT / number of subtests.
_divide_timeout() {
	local num_tests="$1"
	if [[ -v TIMEOUT ]]; then
		((TIMEOUT = (TIMEOUT + num_tests - 1) / num_tests))
	fi
}

_have_root() {
	if [[ $EUID -ne 0 ]]; then
		SKIP_REASON="not running as root"
		return 1
	fi
	return 0
}

_have_modules() {
	local missing=()
	local module

	for module in "$@"; do
		if ! modprobe -n -q "$1"; then
			missing+=("$module")
		fi
	done
	if [[ ${#missing} -gt 1 ]]; then
		SKIP_REASON="the following modules are not available: ${missing[*]}"
		return 1
	elif [[ ${#missing} -eq 1 ]]; then
		SKIP_REASON="${missing[0]} module is not available"
		return 1
	fi
	return 0
}

_have_module_param() {
	if ! modinfo -F parm -0 "$1" | grep -q -z "^$2:"; then
		SKIP_REASON="$1 module does not have parameter $2"
		return 1
	fi
	return 0
}

_have_program() {
	if command -v "$1" >/dev/null 2>&1; then
		return 0
	fi
	SKIP_REASON="$1 is not available"
	return 1
}

_have_src_program() {
	if [[ ! -x "$SRCDIR/$1" ]]; then
		SKIP_REASON="$1 was not built; run \`make\`"
		return 1
	fi
	return 0
}

_have_loop() {
	_have_modules loop && _have_program losetup
}

_have_blktrace() {
	# CONFIG_BLK_DEV_IO_TRACE might still be disabled, but this is easier
	# to check. We can fix it if someone complains.
	if [[ ! -d /sys/kernel/debug/block ]]; then
		SKIP_REASON="CONFIG_DEBUG_FS is not enabled"
		return 1
	fi
	_have_program blktrace
}

_have_configfs() {
	if ! findmnt -t configfs /sys/kernel/config >/dev/null; then
		SKIP_REASON="configfs is not mounted at /sys/kernel/config"
		return 1
	fi
	return 0
}

_have_kernel_option() {
	local f opt=$1

	SKIP_REASON="kernel $(uname -r) config not found"
	for f in /proc/config.gz /boot/config-$(uname -r); do
		[ -e "$f" ] || continue
		if zgrep -q "^CONFIG_${opt}=[my]$" "$f"; then
			SKIP_REASON=""
			return 0
		else
			SKIP_REASON="kernel option $opt has not been enabled"
		fi
	done

	return 1
}

_have_tracefs() {
	stat /sys/kernel/debug/tracing/trace > /dev/null 2>&1
	if ! findmnt -t tracefs /sys/kernel/debug/tracing >/dev/null; then
		SKIP_REASON="tracefs is not mounted at /sys/kernel/debug/tracing"
		return 1
	fi
	return 0
}

_have_tracepoint() {
	local event=$1

	if [[ ! -d /sys/kernel/debug/tracing/events/${event} ]]; then
		SKIP_REASON="tracepoint ${event} does not exist"
		return 1
	fi
	return 0
}

_test_dev_can_discard() {
	if [[ $(cat "${TEST_DEV_SYSFS}/queue/discard_max_bytes") -eq 0 ]]; then
		SKIP_REASON="$TEST_DEV does not support discard"
		return 1
	fi
	return 0
}

_test_dev_is_rotational() {
	if [[ $(cat "${TEST_DEV_SYSFS}/queue/rotational") -eq 0 ]]; then
		SKIP_REASON="$TEST_DEV is not rotational"
		return 1
	fi
	return 0
}

_test_dev_queue_get() {
	if [[ $1 = scheduler ]]; then
		sed -e 's/.*\[//' -e 's/\].*//' "${TEST_DEV_SYSFS}/queue/scheduler"
	else
		cat "${TEST_DEV_SYSFS}/queue/$1"
	fi
}

_test_dev_queue_set() {
	# For bash >=4.3 we'd write if [[ ! -v TEST_DEV_QUEUE_SAVED["$1"] ]].
	if [[ -z ${TEST_DEV_QUEUE_SAVED["$1"]} &&
	      ${TEST_DEV_QUEUE_SAVED["$1"]-unset} == unset ]]; then
		TEST_DEV_QUEUE_SAVED["$1"]="$(_test_dev_queue_get "$1")"
	fi
	echo "$2" >"${TEST_DEV_SYSFS}/queue/$1"
}

_test_dev_is_pci() {
	if ! readlink -f "$TEST_DEV_SYSFS/device" | grep -q pci; then
		# nvme needs some special casing
		if readlink -f "$TEST_DEV_SYSFS/device" | grep -q nvme; then
			local blkdev
			local ctrldev
			# First get the controller device from the namespace blockdev
			blkdev="$(echo "$TEST_DEV_SYSFS" | cut -d '/' -f 7)"
			ctrldev="$(echo "$blkdev" | grep -Eo 'nvme[0-9]+')"
			# Then get the pci device from the controller device
			if readlink -f "$ctrldev/device" | grep -1 pci; then
				return 0
			fi
		fi

		SKIP_REASON="$TEST_DEV is not a PCI device"
		return 1
	fi
	return 0
}

_get_pci_dev_from_blkdev() {
	readlink -f "$TEST_DEV_SYSFS/device" | \
		grep -Eo '[0-9a-f]{4,5}:[0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]' | \
		tail -1
}

_get_pci_parent_from_blkdev() {
	readlink -f "$TEST_DEV_SYSFS/device" | \
		grep -Eo '[0-9a-f]{4,5}:[0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]' | \
		tail -2 | head -1
}

_test_dev_in_hotplug_slot() {
	local parent
	parent="$(_get_pci_parent_from_blkdev)"

	local slt_cap
	slt_cap="$(setpci -s "${parent}" CAP_EXP+14.w)"
	if [[ $((0x${slt_cap} & 0x20)) -eq 0 ]]; then
		SKIP_REASON="$TEST_DEV is not in a hot pluggable slot"
		return 1
	fi
	return 0
}

# Older versions of xfs_io use pwrite64 and such, so the error messages won't
# match current versions of xfs_io. See c52086226bc6 ("filter: xfs_io output
# has dropped "64" from error messages") in xfstests.
_filter_xfs_io_error() {
	sed -e 's/^\(.*\)64\(: .*$\)/\1\2/'
}
