#!/bin/bash
# SPDX-License-Identifier: MIT
#
# chreipl-fcp-mpath: use multipath information to change FCP IPL target
# (C) Copyright IBM Corp. 2021
#
# Uses the following system-utilities (and shell-builtins):
#   Those necessary for sourced library:
#     - chreipl-fcp-mpath-common.sh
#   GNU coreutils:
#     - truncate
#   util-linux:
#     - hexdump

# Try to change the current re-IPL target to a dfferent, operational path to
# the same volume.
#
# Makes use of udev event environment variables:
#	DM_UUID
#	SUBSYSTEM
#	DEVPATH
#	CHREIPL_FCP_MPATH_IS_TGT

# shellcheck disable=SC2034
declare -gr debug_trace_tag=15tcip
# shellcheck disable=SC1091
source '@chreiplzfcpmp-lib@' || exit 127

function apply_ipl_information() {
	local sdev_wwid="${1}"
	local sdev_busid="${2}" sdev_wwpn="${3}" sdev_lun="${4}"
	local ipl_type="${5}" ipl_busid="${6}" ipl_wwpn="${7}" ipl_lun="${8}"
	local -a records
	local try_update_id_file=true

	[ "${ipl_type}" = "fcp" ]				|| return 1

	[[ "${sdev_busid}" =~ ^[[:xdigit:]]{1,3}\.[[:xdigit:]]\.[[:xdigit:]]{1,4}$ ]] \
								|| return 2
	[[ "${sdev_wwpn}" =~ ^0x[[:xdigit:]]{16}$ ]]		|| return 3
	[[ "${sdev_lun}" =~ ^0x[[:xdigit:]]{16}$ ]]		|| return 4

	# After updating the firmware re-IPL information below we also try to
	# update the information stored in the ID file (necessary, so it
	# contains the correct Device-Bus-ID/WWPN/LUN after the update). For
	# the update of the ID file we try to grab an exclusive lock, so there
	# are no overlapping reads/writes.
	#
	# In case we can't get the lock because the ID file is missing, but we
	# have a direct TGT match, we may still try to change the re-IPL
	# information, but skip the ID file update.
	#
	# "direct match" means, the event subject is either the SDEV that is
	# currently set as re-IPL target, or it is the dm-multipath device that
	# currently contains the re-IPL target.
	if ! id_file_lock_exclusive_no_create; then
		# rc == 1       --> could not read ${ID_FILE}
		[ "${PIPESTATUS[0]}" -eq 1 ]			|| return 5
		# if true, we are dealing with a direct TGT match
		[ "${CHREIPL_FCP_MPATH_IS_TGT}" = "true" ]	|| return 6
		try_update_id_file=false
	fi

	# If we have a direct match (see in the comment above), we know
	# that we have a path to the current re-IPL volume - no matter of the
	# WWID. Otherwise, we got here by comparing the WWID of the event
	# subject with the one recorded in the ID file; in this case we try to
	# make sure the information is still up-to-date.
	if [ "${CHREIPL_FCP_MPATH_IS_TGT}" != "true" ]; then
		# last bail to make sure we don't overwrite user choices..
		#
		# XXX: this will *NOT* prevent the race completely, but at least
		#      make it less likely
		{ readarray -d "" -t -u "${ID_FILE_LOCK}" records; } 2>/dev/null \
								|| return 7
		if '@DEBUG@'; then declare -p records 1>&2; fi

		[ "${#records[@]}" = "4" ]			|| return 8
		[ "${records[0]}" = "${sdev_wwid}" ]		|| return 9
		[ "${records[1]}" = "${ipl_busid}" ]		|| return 10
		[ "${records[2]}" = "${ipl_wwpn}" ]		|| return 11
		[ "${records[3]}" = "${ipl_lun}" ]		|| return 12
	fi

	# Take lock so we don't see any intermediate state from other helpers
	# running in parallel
	firmware_lock_exclusive					|| return 13

	if ! { echo "${sdev_busid}" >| /sys/firmware/reipl/fcp/device	\
	       && echo "${sdev_wwpn}" >| /sys/firmware/reipl/fcp/wwpn	\
	       && echo "${sdev_lun}" >| /sys/firmware/reipl/fcp/lun; };
	then
		log_alert "Changing the re-IPL device failed. The current re-IPL settings might be inconsistent. Check and correct the settings (see the README.md of chreipl-fcp-mpath) to make sure that the current re-IPL device is valid."
		return 14
	fi

	firmware_unlock_exclusive

	if [ "${sdev_busid}" != "${ipl_busid}" ]			\
	   || [ "${sdev_wwpn}" != "${ipl_wwpn}" ]			\
	   || [ "${sdev_lun}" != "${ipl_lun}" ]; then
		log_note "Changed re-IPL path to: ${sdev_busid}:${sdev_wwpn}:${sdev_lun}."
	fi

	# Try to update the information in the ID file if we have gotten the
	# lock for it.
	if ${try_update_id_file}; then
		# reset ID without removing the file
		truncate --no-create --size=0 "${ID_FILE}"	|| return 15
		echo -ne "${sdev_wwid}\x00${sdev_busid}\x00${sdev_wwpn}\x00${sdev_lun}\x00" \
			>>"${ID_FILE}"				|| return 16

		id_file_unlock_exclusive_no_create

		if '@DEBUG@'; then hexdump -vC "${ID_FILE}" 1>&2; fi
	fi

	return 0
}

declare -g SDEV=""
if [[ "${DM_UUID}" == mpath-* ]]; then
	# Assume Multipath Device Mapper Device;
	# e.g.: DEVPATH = /devices/virtual/block/dm-0

	for sdev in /sys/"${DEVPATH}"/slaves/sd*/device; do
		if sdev_test_path_state "${sdev}"; then
			SDEV="${sdev}"
			break
		fi
	done

	# No path of the multipath-device that represents the IPL volume is
	# online.
	if [ "${SDEV}" = "" ]; then
		log_crit "The re-IPL device cannot be changed because no operational path to the re-IPL volume remains. The next re-IPL might fail unless you re-attach or enable at least one valid path to the re-IPL volume."
	fi

elif [ "${SUBSYSTEM}" = block ]; then
	# Assume SCSI Disk;
	# e.g.: DEVPATH = /devices/css0/0.0.0014/0.0.1700/host0/rport-0:0-0/target0:0:0/0:0:0:1074806808/block/sds
	if sdev_test_path_state /sys/"${DEVPATH}"/device; then
		SDEV=/sys/"${DEVPATH}"/device
	fi

fi
[ "${SDEV}" != "" ]						|| exit 0

sdev_get_wwid "${SDEV}"						|| exit 0
sdev_get_fcp_addressing "${SDEV}"				|| exit 0
firmware_get_ipl_information					|| exit 0

# shellcheck disable=SC2153
apply_ipl_information							\
	"${SDEV_WWID}" "${SDEV_BUSID}" "${SDEV_WWPN}" "${SDEV_LUN}"	\
	"${IPL_TYPE}" "${IPL_BUSID}" "${IPL_WWPN}" "${IPL_LUN}"	|| exit 0

exit 0
