#!/bin/bash
# SPDX-License-Identifier: GPL-3.0+
# Copyright (C) 2018 Johannes Thumshirn
#
# Test specific to NVMe devices

. common/rc
. common/multipath-over-rdma

def_traddr="127.0.0.1"
def_adrfam="ipv4"
def_trsvcid="4420"
def_remote_wwnn="0x10001100aa000001"
def_remote_wwpn="0x20001100aa000001"
def_local_wwnn="0x10001100aa000002"
def_local_wwpn="0x20001100aa000002"

if [ -f "/etc/nvme/hostid" ]; then
	def_hostid="$(cat /etc/nvme/hostid 2> /dev/null)"
else
	def_hostid="$(uuidgen)"
fi
if [ -z "$def_hostid" ] ; then
	def_hostid="0f01fb42-9f7f-4856-b0b3-51e60b8de349"
fi

if [ -f "/etc/nvme/hostnqn" ]; then
	def_hostnqn="$(cat /etc/nvme/hostnqn 2> /dev/null)"
fi
if [ -z "$def_hostnqn" ] ; then
	def_hostnqn="nqn.2014-08.org.nvmexpress:uuid:${def_hostid}"
fi

nvme_trtype=${nvme_trtype:-"loop"}
nvme_img_size=${nvme_img_size:-"1G"}
nvme_num_iter=${nvme_num_iter:-"1000"}

_nvme_requires() {
	_have_program nvme
	_require_nvme_test_img_size 4m
	case ${nvme_trtype} in
	loop)
		_have_driver nvme-loop
		_have_configfs
		;;
	pci)
		_have_driver nvme
		;;
	tcp)
		_have_driver nvme-tcp
		_have_driver nvmet-tcp
		_have_configfs
		;;
	rdma)
		_have_driver nvme-rdma
		_have_driver nvmet-rdma
		_have_configfs
		_have_program rdma
		_have_driver rdma_rxe || _have_driver siw
		;;
	fc)
		_have_driver nvme-fc
		_have_driver nvme-fcloop
		_have_configfs
		def_adrfam="fc"
		;;
	*)
		SKIP_REASONS+=("unsupported nvme_trtype=${nvme_trtype}")
		return 1
	esac

	if [[ -n ${nvme_adrfam} ]]; then
		case ${nvme_adrfam} in
		ipv6)
			def_traddr="::1"
			def_adrfam="ipv6"
			;;
		ipv4)
			;; # was already set
		fc)
			def_adrfam="fc"
			;;
		*)
			# ignore for non ip transports
			if [[ "${nvme_trtype}" == "tcp" ||
			      "${nvme_trtype}" == "rdma" ]]; then
				SKIP_REASONS+=("unsupported nvme_adrfam=${nvme_adrfam}")
				return 1
			fi
		esac
	fi

	return 0
}

group_requires() {
	_have_root
}

group_device_requires() {
	_require_test_dev_is_nvme
}

NVMET_CFS="/sys/kernel/config/nvmet/"

_require_test_dev_is_nvme() {
	if ! readlink -f "$TEST_DEV_SYSFS/device" | grep -q nvme; then
		SKIP_REASONS+=("$TEST_DEV is not a NVMe device")
		return 1
	fi
	return 0
}

_require_nvme_test_img_size() {
	local require_sz_mb
	local nvme_img_size_mb

	require_sz_mb="$(convert_to_mb "$1")"
	nvme_img_size_mb="$(convert_to_mb "${nvme_img_size}")"

	if ((nvme_img_size_mb < require_sz_mb)); then
		SKIP_REASONS+=("nvme_img_size must be at least ${require_sz_mb}m")
		return 1
	fi
	return 0
}

_require_nvme_trtype() {
	local trtype
	for trtype in "$@"; do
		if [[ "${nvme_trtype}" == "$trtype" ]]; then
			return 0
		fi
	done
	SKIP_REASONS+=("nvme_trtype=${nvme_trtype} is not supported in this test")
	return 1
}

_require_nvme_trtype_is_loop() {
	if ! _require_nvme_trtype loop; then
		return 1
	fi
	return 0
}

_require_nvme_trtype_is_fabrics() {
	if ! _require_nvme_trtype loop fc rdma tcp; then
		return 1
	fi
	return 0
}

_require_nvme_cli_auth() {
	if ! nvme gen-dhchap-key -n nvmf-test-subsys > /dev/null 2>&1 ; then
		SKIP_REASONS+=("nvme gen-dhchap-key command missing")
		return 1
	fi
	return 0
}

_test_dev_nvme_ctrl() {
	echo "/dev/char/$(cat "${TEST_DEV_SYSFS}/device/dev")"
}

_test_dev_nvme_nsid() {
	cat "${TEST_DEV_SYSFS}/nsid"
}

_nvme_calc_rand_io_size() {
	local img_size_mb
	local io_size_kb

	img_size_mb="$(convert_to_mb "$1")"
	io_size_kb="$(((img_size_mb * 1024) / $(nproc)))"

	echo "${io_size_kb}k"
}

_nvme_fcloop_add_rport() {
	local local_wwnn="$1"
	local local_wwpn="$2"
	local remote_wwnn="$3"
	local remote_wwpn="$4"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${remote_wwnn},wwpn=${remote_wwpn},lpwwnn=${local_wwnn},lpwwpn=${local_wwpn},roles=0x60" > ${loopctl}/add_remote_port
}

_nvme_fcloop_add_lport() {
	local wwnn="$1"
	local wwpn="$2"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${wwnn},wwpn=${wwpn}" > ${loopctl}/add_local_port
}

_nvme_fcloop_add_tport() {
	local wwnn="$1"
	local wwpn="$2"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${wwnn},wwpn=${wwpn}" > ${loopctl}/add_target_port
}

_setup_fcloop() {
	local local_wwnn="${1:-$def_local_wwnn}"
	local local_wwpn="${2:-$def_local_wwpn}"
	local remote_wwnn="${3:-$def_remote_wwnn}"
	local remote_wwpn="${4:-$def_remote_wwpn}"

	_nvme_fcloop_add_tport "${remote_wwnn}" "${remote_wwpn}"
	_nvme_fcloop_add_lport "${local_wwnn}" "${local_wwpn}"
	_nvme_fcloop_add_rport "${local_wwnn}" "${local_wwpn}" \
		               "${remote_wwnn}" "${remote_wwpn}"
}

_nvme_fcloop_del_rport() {
	local local_wwnn="$1"
	local local_wwpn="$2"
	local remote_wwnn="$3"
	local remote_wwpn="$4"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${remote_wwnn},wwpn=${remote_wwpn}" > ${loopctl}/del_remote_port 2> /dev/null
}

_nvme_fcloop_del_lport() {
	local wwnn="$1"
	local wwpn="$2"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${wwnn},wwpn=${wwpn}" > ${loopctl}/del_local_port 2> /dev/null
}

_nvme_fcloop_del_tport() {
	local wwnn="$1"
	local wwpn="$2"
	local loopctl=/sys/class/fcloop/ctl

	echo "wwnn=${wwnn},wwpn=${wwpn}" > ${loopctl}/del_target_port 2> /dev/null
}

_cleanup_fcloop() {
	local local_wwnn="${1:-$def_local_wwnn}"
	local local_wwpn="${2:-$def_local_wwpn}"
	local remote_wwnn="${3:-$def_remote_wwnn}"
	local remote_wwpn="${4:-$def_remote_wwpn}"

	_nvme_fcloop_del_tport "${remote_wwnn}" "${remote_wwpn}"
	_nvme_fcloop_del_lport "${local_wwnn}" "${local_wwpn}"
	_nvme_fcloop_del_rport "${local_wwnn}" "${local_wwpn}" \
			       "${remote_wwnn}" "${remote_wwpn}"
}

_cleanup_nvmet() {
	local dev
	local port
	local subsys
	local transport
	local name

	if [[ ! -d "${NVMET_CFS}" ]]; then
		return 0
	fi

	# Don't let successive Ctrl-Cs interrupt the cleanup processes
	trap '' SIGINT

	shopt -s nullglob

	for dev in /sys/class/nvme/nvme*; do
		dev="$(basename "$dev")"
		transport="$(cat "/sys/class/nvme/${dev}/transport")"
		if [[ "$transport" == "${nvme_trtype}" ]]; then
			echo "WARNING: Test did not clean up ${nvme_trtype} device: ${dev}"
			_nvme_disconnect_ctrl "${dev}"
		fi
	done

	for port in "${NVMET_CFS}"/ports/*; do
		name=$(basename "${port}")
		echo "WARNING: Test did not clean up port: ${name}"
		rm -f "${port}"/subsystems/*
		rmdir "${port}"
	done

	for subsys in "${NVMET_CFS}"/subsystems/*; do
		name=$(basename "${subsys}")
		echo "WARNING: Test did not clean up subsystem: ${name}"
		for ns in "${subsys}"/namespaces/*; do
			rmdir "${ns}"
		done
		rmdir "${subsys}"
	done

	for host in "${NVMET_CFS}"/hosts/*; do
		name=$(basename "${host}")
		echo "WARNING: Test did not clean up host: ${name}"
		rmdir "${host}"
	done

	shopt -u nullglob
	trap SIGINT

	if [[ "${nvme_trtype}" == "fc" ]]; then
		_cleanup_fcloop "${def_local_wwnn}" "${def_local_wwpn}" \
				"${def_remote_wwnn}" "${def_remote_wwpn}"
		modprobe -rq nvme-fcloop
	fi
	modprobe -rq nvme-"${nvme_trtype}" 2>/dev/null
	if [[ "${nvme_trtype}" != "loop" ]]; then
		modprobe -rq nvmet-"${nvme_trtype}" 2>/dev/null
	fi
	modprobe -rq nvmet 2>/dev/null
	if [[ "${nvme_trtype}" == "rdma" ]]; then
		stop_soft_rdma
	fi
}

_setup_nvmet() {
	_register_test_cleanup _cleanup_nvmet
	modprobe -q nvmet
	if [[ "${nvme_trtype}" != "loop" ]]; then
		modprobe -q nvmet-"${nvme_trtype}"
	fi
	modprobe -q nvme-"${nvme_trtype}"
	if [[ "${nvme_trtype}" == "rdma" ]]; then
		start_soft_rdma
		for i in $(rdma_network_interfaces)
		do
			if [[ "${nvme_adrfam}" == "ipv6" ]]; then
				ipv6_addr=$(get_ipv6_ll_addr "$i")
				if [[ -n "${ipv6_addr}" ]]; then
					def_traddr=${ipv6_addr}
				fi
			else
				ipv4_addr=$(get_ipv4_addr "$i")
				if [[ -n "${ipv4_addr}" ]]; then
					def_traddr=${ipv4_addr}
				fi
			fi
		done
	fi
	if [[ "${nvme_trtype}" = "fc" ]]; then
		modprobe -q nvme-fcloop
		_setup_fcloop "${def_local_wwnn}" "${def_local_wwpn}" \
			      "${def_remote_wwnn}" "${def_remote_wwpn}"

		def_traddr=$(printf "nn-%s:pn-%s" \
				    "${def_remote_wwnn}" \
				    "${def_remote_wwpn}")
		def_host_traddr=$(printf "nn-%s:pn-%s" \
					 "${def_local_wwnn}" \
					 "${def_local_wwpn}")
	fi
}

_nvme_disconnect_ctrl() {
	local ctrl="$1"

	nvme disconnect -d "${ctrl}"
}

_nvme_disconnect_subsys() {
	local subsysnqn="$1"

	nvme disconnect -n "${subsysnqn}"
}

_nvme_connect_subsys() {
	local positional_args=()
	local trtype=""
	local subsysnqn=""
	local traddr="$def_traddr"
	local host_traddr="$def_host_traddr"
	local trsvcid="$def_trsvcid"
	local hostnqn="$def_hostnqn"
	local hostid="$def_hostid"
	local hostkey=""
	local ctrlkey=""
	local nr_io_queues=""
	local nr_write_queues=""
	local nr_poll_queues=""
	local keep_alive_tmo=""
	local reconnect_delay=""
	local ctrl_loss_tmo=""

	while [[ $# -gt 0 ]]; do
		case $1 in
			-a|--traddr)
				traddr="$2"
				shift 2
				;;
			-w|--host-traddr)
				host_traddr="$2"
				shift 2
				;;
			-s|--trsvcid)
				trsvcid="$2"
				shift 2
				;;
			-n|--hostnqn)
				hostnqn="$2"
				shift 2
				;;
			-I|--hostid)
				hostid="$2"
				shift 2
				;;
			-S|--dhchap-secret)
				hostkey="$2"
				shift 2
				;;
			-C|--dhchap-ctrl-secret)
				ctrlkey="$2"
				shift 2
				;;
			-i|--nr-io-queues)
				nr_io_queues="$2"
				shift 2
				;;
			-W|--nr-write-queues)
				nr_write_queues="$2"
				shift 2
				;;
			-P|--nr-poll-queues)
				nr_poll_queues="$2"
				shift 2
				;;
			-k|--keep-alive-tmo)
				keep_alive_tmo="$2"
				shift 2
				;;
			-c|--reconnect-delay)
				reconnect_delay="$2"
				shift 2
				;;
			-l|--ctrl-loss-tmo)
				ctrl_loss_tmo="$2"
				shift 2
				;;
			*)
				positional_args+=("$1")
				shift
				;;
		esac
	done

	set -- "${positional_args[@]}"

	trtype="$1"
	subsysnqn="$2"

	ARGS=(-t "${trtype}" -n "${subsysnqn}")
	if [[ "${trtype}" == "fc" ]] ; then
		ARGS+=(-a "${traddr}" -w "${host_traddr}")
	elif [[ "${trtype}" != "loop" ]]; then
		ARGS+=(-a "${traddr}" -s "${trsvcid}")
	fi
	ARGS+=(--hostnqn="${hostnqn}")
	ARGS+=(--hostid="${hostid}")
	if [[ -n "${hostkey}" ]]; then
		ARGS+=(--dhchap-secret="${hostkey}")
	fi
	if [[ -n "${ctrlkey}" ]]; then
		ARGS+=(--dhchap-ctrl-secret="${ctrlkey}")
	fi
	if [[ -n "${nr_io_queues}" ]]; then
		ARGS+=(--nr-io-queues="${nr_io_queues}")
	fi
	if [[ -n "${nr_write_queues}" ]]; then
		ARGS+=(--nr-write-queues="${nr_write_queues}")
	fi
	if [[ -n "${nr_poll_queues}" ]]; then
		ARGS+=(--nr-poll-queues="${nr_poll_queues}")
	fi
	if [[ -n "${keep_alive_tmo}" ]]; then
		ARGS+=(--keep-alive-tmo="${keep_alive_tmo}")
	fi
	if [[ -n "${reconnect_delay}" ]]; then
		ARGS+=(--reconnect-delay="${reconnect_delay}")
	fi
	if [[ -n "${ctrl_loss_tmo}" ]]; then
		ARGS+=(--ctrl-loss-tmo="${ctrl_loss_tmo}")
	fi

	nvme connect "${ARGS[@]}" 2> /dev/null
}

_nvme_discover() {
	local trtype="$1"
	local traddr="${2:-$def_traddr}"
	local host_traddr="${3:-$def_host_traddr}"
	local trsvcid="${3:-$def_trsvcid}"

	ARGS=(-t "${trtype}")
	ARGS+=(--hostnqn="${def_hostnqn}")
	ARGS+=(--hostid="${def_hostid}")
	if [[ "${trtype}" = "fc" ]]; then
		ARGS+=(-a "${traddr}" -w "${host_traddr}")
	elif [[ "${trtype}" != "loop" ]]; then
		ARGS+=(-a "${traddr}" -s "${trsvcid}")
	fi
	nvme discover "${ARGS[@]}"
}

_create_nvmet_port() {
	local trtype="$1"
	local traddr="${2:-$def_traddr}"
	local adrfam="${3:-$def_adrfam}"
	local trsvcid="${4:-$def_trsvcid}"

	local port
	for ((port = 0; ; port++)); do
		if [[ ! -e "${NVMET_CFS}/ports/${port}" ]]; then
			break
		fi
	done

	mkdir "${NVMET_CFS}/ports/${port}"
	echo "${trtype}" > "${NVMET_CFS}/ports/${port}/addr_trtype"
	echo "${traddr}" > "${NVMET_CFS}/ports/${port}/addr_traddr"
	echo "${adrfam}" > "${NVMET_CFS}/ports/${port}/addr_adrfam"
	if [[ "${adrfam}" != "fc" ]]; then
		echo "${trsvcid}" > "${NVMET_CFS}/ports/${port}/addr_trsvcid"
	fi

	echo "${port}"
}

_remove_nvmet_port() {
	local port="$1"
	rmdir "${NVMET_CFS}/ports/${port}"
}

_create_nvmet_ns() {
	local nvmet_subsystem="$1"
	local nsid="$2"
	local blkdev="$3"
	local uuid="00000000-0000-0000-0000-000000000000"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local ns_path="${subsys_path}/namespaces/${nsid}"

	if [[ $# -eq 4 ]]; then
		uuid="$4"
	fi

	mkdir "${ns_path}"
	printf "%s" "${blkdev}" > "${ns_path}/device_path"
	printf "%s" "${uuid}" > "${ns_path}/device_uuid"
	printf 1 > "${ns_path}/enable"
}

_create_nvmet_subsystem() {
	local nvmet_subsystem="$1"
	local blkdev="$2"
	local uuid=$3
	local cfs_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"

	mkdir -p "${cfs_path}"
	echo 1 > "${cfs_path}/attr_allow_any_host"
	_create_nvmet_ns "${nvmet_subsystem}" "1" "${blkdev}" "${uuid}"
}

_create_nvmet_host() {
	local nvmet_subsystem="$1"
	local nvmet_hostnqn="$2"
	local nvmet_hostkey="$3"
	local nvmet_ctrlkey="$4"
	local cfs_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local host_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	mkdir "${host_path}"
	echo 0 > "${cfs_path}/attr_allow_any_host"
	ln -s "${host_path}" "${cfs_path}/allowed_hosts/${nvmet_hostnqn}"
	if [[ "${nvmet_hostkey}" ]] ; then
		echo "${nvmet_hostkey}" > "${host_path}/dhchap_key"
	fi
	if [[ "${nvmet_ctrlkey}" ]] ; then
		echo "${nvmet_ctrlkey}" > "${host_path}/dhchap_ctrl_key"
	fi
}

_remove_nvmet_ns() {
	local nvmet_subsystem="$1"
	local nsid=$2
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local nvmet_ns_path="${subsys_path}/namespaces/${nsid}"

	echo 0 > "${nvmet_ns_path}/enable"
	rmdir "${nvmet_ns_path}"
}

_remove_nvmet_subsystem() {
	local nvmet_subsystem="$1"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"

	_remove_nvmet_ns "${nvmet_subsystem}" "1"
	rm -f "${subsys_path}"/allowed_hosts/*
	rmdir "${subsys_path}"
}

_remove_nvmet_host() {
	local nvmet_host="$1"
	local host_path="${NVMET_CFS}/hosts/${nvmet_host}"

	rmdir "${host_path}"
}

_create_nvmet_passthru() {
	local nvmet_subsystem="$1"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local passthru_path="${subsys_path}/passthru"

	mkdir -p "${subsys_path}"
	echo 1 > "${subsys_path}/attr_allow_any_host"

	_test_dev_nvme_ctrl > "${passthru_path}/device_path"
	echo 1 > "${passthru_path}/enable"
	if [[ -f "${passthru_path}/clear_ids" ]]; then
		echo 1 > "${passthru_path}/clear_ids"
	fi
}

_remove_nvmet_passhtru() {
	local nvmet_subsystem="$1"
	local subsys_path="${NVMET_CFS}/subsystems/${nvmet_subsystem}"
	local passthru_path="${subsys_path}/passthru"

	echo 0 > "${passthru_path}/enable"
	rmdir "${subsys_path}"
}

_add_nvmet_subsys_to_port() {
	local port="$1"
	local nvmet_subsystem="$2"

	ln -s "${NVMET_CFS}/subsystems/${nvmet_subsystem}" \
		"${NVMET_CFS}/ports/${port}/subsystems/${nvmet_subsystem}"
}

_remove_nvmet_subsystem_from_port() {
	local port="$1"
	local nvmet_subsystem="$2"

	rm "${NVMET_CFS}/ports/${port}/subsystems/${nvmet_subsystem}"
}

_set_nvmet_hostkey() {
	local nvmet_hostnqn="$1"
	local nvmet_hostkey="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_hostkey}" > \
	     "${cfs_path}/dhchap_key"
}

_set_nvmet_ctrlkey() {
	local nvmet_hostnqn="$1"
	local nvmet_ctrlkey="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_ctrlkey}" > \
	     "${cfs_path}/dhchap_ctrl_key"
}

_set_nvmet_hash() {
	local nvmet_hostnqn="$1"
	local nvmet_hash="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_hash}" > \
	     "${cfs_path}/dhchap_hash"
}

_set_nvmet_dhgroup() {
	local nvmet_hostnqn="$1"
	local nvmet_dhgroup="$2"
	local cfs_path="${NVMET_CFS}/hosts/${nvmet_hostnqn}"

	echo "${nvmet_dhgroup}" > \
	     "${cfs_path}/dhchap_dhgroup"
}

_find_nvme_dev() {
	local subsys=$1
	local subsysnqn
	local dev
	for dev in /sys/class/nvme/nvme*; do
		[ -e "$dev" ] || continue
		dev="$(basename "$dev")"
		subsysnqn="$(cat "/sys/class/nvme/${dev}/subsysnqn")"
		if [[ "$subsysnqn" == "$subsys" ]]; then
			echo "$dev"
			for ((i = 0; i < 10; i++)); do
				if [[ -e /sys/block/$dev/uuid &&
					-e /sys/block/$dev/wwid ]]; then
					return
				fi
				sleep .1
			done
		fi
	done
}

_find_nvme_passthru_loop_dev() {
	local subsys=$1
	local nsid
	local dev

	dev=$(_find_nvme_dev "${subsys}")
	nsid=$(_test_dev_nvme_nsid)
	echo "/dev/${dev}n${nsid}"
}

_nvmet_passthru_target_setup() {
	local subsys_name=$1

	_create_nvmet_passthru "${subsys_name}"
	port="$(_create_nvmet_port "${nvme_trtype}")"
	_add_nvmet_subsys_to_port "${port}" "${subsys_name}"

	echo "$port"
}

_nvmet_passthru_target_connect() {
	local trtype=$1
	local subsys_name=$2

	_nvme_connect_subsys "${trtype}" "${subsys_name}" || return
	nsdev=$(_find_nvme_passthru_loop_dev "${subsys_name}")

	# The following tests can race with the creation
	# of the device so ensure the block device exists
	# before continuing
	while [ ! -b "${nsdev}" ]; do sleep 1; done

	echo "${nsdev}"
}

_nvmet_passthru_target_cleanup() {
	local port=$1
	local subsys_name=$2

	_remove_nvmet_subsystem_from_port "${port}" "${subsys_name}"
	_remove_nvmet_port "${port}"
	_remove_nvmet_passhtru "${subsys_name}"
}

_discovery_genctr() {
	_nvme_discover "${nvme_trtype}" |
		sed -n -e 's/^.*Generation counter \([0-9]\+\).*$/\1/p'
}

_check_genctr() {
	local last=$1
	local msg=$2
	local genctr

	genctr=$(_discovery_genctr)
	if (( "${genctr}" <= "${last}" )); then
		echo "Generation counter not incremented when ${msg} (${genctr} <= ${last})"
	fi

	echo "${genctr}"
}

declare -A NS_DEV_FAULT_INJECT_SAVE
declare -A CTRL_DEV_FAULT_INJECT_SAVE

_nvme_err_inject_setup()
{
        local a

        for a in /sys/kernel/debug/"$1"/fault_inject/*; do
                NS_DEV_FAULT_INJECT_SAVE[${a}]=$(<"${a}")
        done

        for a in /sys/kernel/debug/"$2"/fault_inject/*; do
                CTRL_DEV_FAULT_INJECT_SAVE[${a}]=$(<"${a}")
        done
}

_nvme_err_inject_cleanup()
{
        local a

        for a in /sys/kernel/debug/"$1"/fault_inject/*; do
                echo "${NS_DEV_FAULT_INJECT_SAVE[${a}]}" > "${a}"
        done

        for a in /sys/kernel/debug/"$2"/fault_inject/*; do
                echo "${CTRL_DEV_FAULT_INJECT_SAVE[${a}]}" > "${a}"
        done
}

_nvme_enable_err_inject()
{
        echo "$2" > /sys/kernel/debug/"$1"/fault_inject/verbose
        echo "$3" > /sys/kernel/debug/"$1"/fault_inject/probability
        echo "$4" > /sys/kernel/debug/"$1"/fault_inject/dont_retry
        echo "$5" > /sys/kernel/debug/"$1"/fault_inject/status
        echo "$6" > /sys/kernel/debug/"$1"/fault_inject/times
}

_nvme_disable_err_inject()
{
        echo 0 > /sys/kernel/debug/"$1"/fault_inject/probability
        echo 0 > /sys/kernel/debug/"$1"/fault_inject/times
}

_nvme_reset_ctrl() {
	echo 1 > /sys/class/nvme/"$1"/reset_controller
}

_nvme_delete_ctrl() {
	echo 1 > /sys/class/nvme/"$1"/delete_controller
}
