#! /bin/bash
#
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# This software product is a proprietary product of Mellanox Technologies Ltd.
# (the "Company") and all right, title, and interest in and to the software
# product, including all associated intellectual property rights, are and
# shall remain exclusively with the Company.
#
# This software product is governed by the End User License Agreement
# provided with the software product.
#

# Helper functions for JSON manipulation
# Prefer jq if available, fallback to sed/awk for compatibility

# Read a JSON field value
json_get() {
	local file="$1"
	local field="$2"
	local default="$3"

	if [[ ! -f "$file" ]]; then
		echo "${default:-null}"
		return
	fi

	# Use jq if available (robust, handles all JSON edge cases)
	if command -v jq &> /dev/null; then
		local value=$(jq -r ".${field} // empty" "$file" 2>/dev/null)
		if [[ -z "$value" ]]; then
			echo "${default:-null}"
		else
			echo "$value"
		fi
		return
	fi

	# Fallback: Use grep/sed/awk to extract JSON field value
	local value=$(grep -o "\"${field}\"[[:space:]]*:[[:space:]]*[^,}]*" "$file" 2>/dev/null | \
		     sed 's/.*:[[:space:]]*//' | \
		     sed 's/^"\(.*\)"$/\1/' | \
		     sed 's/[[:space:]]*$//')

	if [[ -z "$value" ]]; then
		echo "${default:-null}"
	else
		echo "$value"
	fi
}

# Set a JSON field value
json_set() {
	local file="$1"
	local field="$2"
	local value="$3"

	# Use jq if available (robust, handles all JSON edge cases)
	if command -v jq &> /dev/null; then
		if [[ -f "$file" ]]; then
			# Update existing file
			local tmp=$(mktemp)
			jq ".${field} = \"${value}\"" "$file" > "$tmp" && mv "$tmp" "$file"
		else
			# Create new file
			echo "{}" | jq ".${field} = \"${value}\"" > "$file"
		fi
		return
	fi

	# Fallback: Use sed/awk for JSON manipulation
	if [[ -f "$file" ]]; then
		# Check if field already exists
		if grep -q "\"${field}\"[[:space:]]*:" "$file"; then
			# Update existing field
			sed -i "s/\"${field}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/\"${field}\": \"${value}\"/g" "$file"
		else
			# Remove any trailing whitespace and ensure proper line ending
			sed -i 's/[[:space:]]*$//' "$file"

			# Check if file is empty object {} - handle specially to avoid trailing comma
			# Normalize the file content for comparison (remove all whitespace)
			local file_content=$(cat "$file" | tr -d '[:space:]')
			if [[ "$file_content" == "{}" ]]; then
				# Empty object: write new content directly without comma
				echo "{" > "$file"
				echo "  \"${field}\": \"${value}\"" >> "$file"
				echo "}" >> "$file"
			else
				# Check if this is a single-line JSON or multi-line
				# Handle case where file has no trailing newline (wc -l returns 0)
				local line_count=$(wc -l < "$file")
				if [[ $line_count -le 1 ]]; then
					# Single line JSON (including files with no trailing newline): add comma and field
					sed -i 's/}[[:space:]]*$/,\n  "'"${field}"'": "'"${value}"'"\n}/' "$file"
				else
					# Multi-line JSON: add comma and field with proper indentation
					sed -i '$s/}[[:space:]]*$/,\n  "'"${field}"'": "'"${value}"'"\n}/' "$file"
				fi
			fi
		fi
	else
		# Create new file
		echo "{\"${field}\": \"${value}\"}" > "$file"
	fi
}

mftconfig=mstconfig
if [ -x /usr/bin/mlxconfig ]; then
	mftconfig=mlxconfig
fi

devlink=devlink
if [ -x /opt/mellanox/iproute2/sbin/devlink ]; then
	devlink=/opt/mellanox/iproute2/sbin/devlink
fi

get_eswitch_mode()
{
	pci_dev=$1
	shift

	$devlink dev eswitch show pci/${pci_dev} 2> /dev/null | cut -d ' ' -f 3
}

pci=`lspci -nD -d 15b3: | grep 'a2d[26c]\|101d\|1021' | head -n 1`
emu_manager=`echo $pci | cut -d ' ' -f 1`

if [[ -f /opt/mellanox/mlnx_virtnet/virtnet.conf ]]; then
	_emu_manager=$(json_get "/opt/mellanox/mlnx_virtnet/virtnet.conf" "ib_dev_p0" "")
	if [[ ! -z $_emu_manager ]] && [[ $_emu_manager != "null" ]]; then
		# Check if the infiniband device path exists
		if [[ -L "/sys/class/infiniband/$_emu_manager/device" ]]; then
			device_path=$(readlink "/sys/class/infiniband/$_emu_manager/device")
			if [[ ! -z "$device_path" ]]; then
				emu_manager=`basename "$device_path"`
			fi
		fi
	fi
fi

if [[ -z $emu_manager ]]; then
	echo "Error: no valid emulation manager is defined"
	exit 1
fi

is_emu_per_pf=$($mftconfig -d ${emu_manager} -e q VIRTIO_NET_EMU_MNG_ENABLE |
	grep -o "VIRTIO_NET_EMU_MNG_ENABLE.*" | awk '{print $3}' | grep True)
if [[ -z $is_emu_per_pf ]]; then
	is_emu_global=$($mftconfig -d ${emu_manager} -e q VIRTIO_NET_EMULATION_ENABLE |
		grep -o "VIRTIO_NET_EMULATION_ENABLE.*" | awk '{print $3}' | grep True)
	if [[ -z $is_emu_global ]]; then
		echo "${emu_manager} doesn't have VIRTIO_NET_EMULATION_ENABLE or VIRTIO_NET_EMU_MNG_ENABLE set"
		exit 250
	fi
fi

eswitch_mode="get_eswitch_mode ${emu_manager}"
COUNT=0
until [ $COUNT -eq 120 ] || [ "`${eswitch_mode}`" == "switchdev" ]; do
	sleep 1
	echo "Waiting for device ${emu_manager} to be switchdev mode for ${COUNT} seconds"
	let $(( COUNT++ ))
done

if [ "`${eswitch_mode}`" != "switchdev" ]; then
	echo "${emu_manager} is not in switch dev mode"
	exit 2
fi

config_file="/opt/mellanox/mlnx_virtnet/virtnet.conf"
p2=`$mftconfig -d ${emu_manager} -e q LINK_TYPE_P2`
if [[ -n `echo "$p2" 2> /dev/null | grep "Unknown Parameter"` ]]; then
	echo "Single port NIC"
	json_set "$config_file" "single_port" "1"
fi

if [[ -z `modinfo mlxdevm 2>&1 | grep -E "ERROR.*mlxdevm not found"` ]]; then
	if [[ -f $config_file ]]; then
		current_sf_provider=$(json_get "$config_file" "sf_provider" "")
		if [[ "$current_sf_provider" != "devlink" ]]; then
			echo "mlxdevm as SF provider"
			json_set "$config_file" "sf_provider" "mlxdevm"
		else
			echo "mlxdevm available, keeping existing devlink SF provider"
		fi
	fi
else
	echo "devlink as SF provider"
	json_set "$config_file" "sf_provider" "devlink"
fi

bf3=`echo $pci | grep 'a2dc\|1021'`

if [[ -n $bf3 ]]; then
	if [[ ! -e /opt/mellanox/mlnx_virtnet/providers/dpa.provider ]]; then
		echo "Generating default dpa.provider for BlueField-3/ConnectX-7"
		echo -e "Provider=libprovider-dpa\nScore=200" > \
		      /opt/mellanox/mlnx_virtnet/providers/dpa.provider
	fi
fi

recovery_dir="/opt/mellanox/mlnx_virtnet/recovery"

is_lag_cur=0
if [[ -f $config_file ]]; then
	is_lag_cur=$(json_get "$config_file" "is_lag" "0")
fi

is_lag_cur_valid=1
if [[ "$is_lag_cur" != "0" && "$is_lag_cur" != "1" ]]; then
	echo "Invalid is_lag value (${is_lag_cur}) in $config_file; expected 0 or 1. Skipping recovery purge/update; controller will fail config validation."
	is_lag_cur_valid=0
fi

mkdir -p ${recovery_dir}
if [[ -f "${recovery_dir}/.virtnet_conf_save" ]]; then
	is_lag_save=$(json_get "${recovery_dir}/.virtnet_conf_save" "is_lag" "0")
	is_lag_save_valid=1
	if [[ "$is_lag_save" != "0" && "$is_lag_save" != "1" ]]; then
		is_lag_save_valid=0
	fi

	if [[ $is_lag_cur_valid -eq 1 && $is_lag_save_valid -eq 1 && "$is_lag_save" != "$is_lag_cur" ]]; then
		cd $recovery_dir
		find . -type f -not \( -name ".virtnet_conf_save" -or -name ".mlxconfig_save" \) -delete
	fi
fi

if [[ -f $config_file && $is_lag_cur_valid -eq 1 ]]; then
	cp "$config_file" "${recovery_dir}/.virtnet_conf_save"
elif [[ ! -f $config_file ]]; then
	echo '{"is_lag": 0}' > "${recovery_dir}/.virtnet_conf_save"
fi

curr_map_count=`cat /proc/sys/vm/max_map_count`

#Define map threshold value to 131060 (65530 * 2) to support upto 2K devices
max_map_threshold=131060

if [[ "$curr_map_count" -lt "$max_map_threshold" ]]; then
	echo $max_map_threshold > /proc/sys/vm/max_map_count
fi

fields=(PF_BAR2_ENABLE \
	HIDE_PORT2_PF \
	PER_PF_NUM_SF \
	SRIOV_EN \
	PF_SF_BAR_SIZE \
	PF_TOTAL_SF)

fields_emu_per_pf=(VIRTIO_NET_EMU_MNG_ENABLE \
	PCI_SWITCH_EMU_MNG_ENABLE \
	PCI_SWITCH_EMU_MNG_NUM_PORT \
	VIRTIO_NET_EMU_MNG_NUM_VF \
	VIRTIO_NET_EMU_MNG_NUM_PF \
	VIRTIO_NET_EMU_MNG_NUM_MSIX)

fields_emu_global=(VIRTIO_NET_EMULATION_ENABLE \
	PCI_SWITCH_EMULATION_ENABLE \
	PCI_SWITCH_EMULATION_NUM_PORT \
	VIRTIO_NET_EMULATION_NUM_VF \
	VIRTIO_NET_EMULATION_NUM_PF \
	VIRTIO_NET_EMULATION_NUM_MSIX)

if [[ -z $is_emu_per_pf ]]; then
	fields=("${fields[@]}" "${fields_emu_global[@]}")
else
	fields=("${fields[@]}" "${fields_emu_per_pf[@]}")
fi
configs=$($mftconfig -d ${emu_manager} -e q "${fields[@]}")

fields_str=""
for f in ${fields[@]};
do
	if [[ $fields_str != "" ]]; then
		fields_str="${fields_str}|${f}"
	else
		fields_str="${f}"
	fi
done

mlxconfig_cur=`echo "$configs" | grep -E $fields_str | tr -s ' '`

if [[ ! -f "${recovery_dir}/.mlxconfig_save" ]]; then
	mkdir -p ${recovery_dir}
	echo "$mlxconfig_cur" > ${recovery_dir}/.mlxconfig_save
	exit 0
fi

mlxconfig_save=`cat ${recovery_dir}/.mlxconfig_save | grep -E $fields_str | tr -s ' '`

for f in ${fields[@]};
do
	val_save=`echo "$mlxconfig_save" | grep -o "$f.*" | awk '{print $3}'`
	val_cur=`echo "$mlxconfig_cur" | grep -o "$f.*" | awk '{print $3}'`
	if [[ "$val_save" != "$val_cur" ]]; then
		echo "$mlxconfig_cur" > ${recovery_dir}/.mlxconfig_save
		cd $recovery_dir
		find . -type f -not \( -name ".virtnet_conf_save" -or -name ".mlxconfig_save" \) -delete
		exit 0
	fi
done

exit 0
