#!/bin/bash

TCMADM=/usr/bin/targetcli
TCM_LOG_FILE="/var/log/vstorage/vstorage-iscsid.log"

# An FC endpoint can be detected using a prefix in a symbolic name of HBA in SYSFS
# 'fcoe ...' - FCoE device
# 'QLE2...'  - QLogic devices (qla2xxx)
#
# \arg WWPN
function _tcm_fc_endpoint() {
	local $*

	source /usr/libexec/vstorage-iscsi/fc_functions

	fc_hba_name=`fc_list_hba | grep $wwpn | cut -d '|' -f1`
	[ $? -ne 0 ] && return 1

	case "$fc_hba_name" in
		"fcoe"*)
			echo "tcm_fc"
			;;
		"QLE2"*)
			echo "qla2xxx"
			;;
		*)
			echo "Unsupported FC HBA '$fc_hba_name'" 1>&2
			return 2
			;;
	esac

	return 0
}

function _tcm_target_path() {
	local $*

	case $SCSI_TRANSPORT in
		"iscsi")
			echo "/iscsi/${target}/tpg1"
			;;
		"fc")
			wwpn=`_tcm_get_target_wwpn target=$target`
			[ $? -ne 0 ] && return 1

			ep=`_tcm_fc_endpoint wwpn=$wwpn`
			[ $? -ne 0 ] && return 2

			echo "/$ep/naa.$wwpn"
			;;
		*)
			echo "Unknown SCSI_TRANSPORT '$SCSI_TRANSPORT'" 1>&2
			exit 1
	esac

	return 0
}

# ---------------------------------------------------------------------------------------
# ISCSI transport

function _tcm_add_portal_iscsi() {
	local $*
	$TCMADM /iscsi/${target}/tpg1/portals create $ip_address $port >>${TCM_LOG_FILE} 2>&1
	return $?
}

function _tcm_create_target_iscsi() {
	local "$@"

	$TCMADM /iscsi create $target >>${TCM_LOG_FILE} 2>&1
	[ $? -ne 0 ] && return 1

	tcm_disable_target target=$target
	if [ $? -ne 0 ]; then
		echo "Failed to disable target: $target" 1>&2
		return 2
	fi

	for addr in $addresses; do
		local ip_address="$(echo "$addr" | cut -d '/' -f1)"
		tcm_add_portal target=$target ip_address=$ip_address port=$TGTD_PORT
		if [ $? -ne 0 ] ; then
			echo "Failed to create portal $ip_address:$TGTD_PORT"
			return 3
		fi
	done

	return 0
}

function _tcm_show_initiators_iscsi() {
	local "$@"
	[ -z "$portals" ] && return 0

	user_status="unauthorized"
	rc=$($TCMADM /iscsi/$target/tpg1/ get attribute authentication | cut -d '=' -f2)
	if [ $? -eq 0 -a $rc -ne 0 ]; then
		user_status="authorized"
	fi

	ss -napt -o state established | grep $TGTD_PORT | \
		grep "$portals" | \
		awk -v user_status=$user_status ' { printf user_status " %s\n", $4} '

	return $?
}

# this is a BUG in targetcli: https://bugzilla.redhat.com/show_bug.cgi?id=1161489
# so find out active sessions via config_fs
function _tcm_target_has_active_sessions_iscsi() {
	local "$@"
	nr_sessions=`cat /sys/kernel/config/target/iscsi/$target/fabric_statistics/iscsi_instance/sessions 2>/dev/null`
	[ $? -ne 0 ] && return 0
	return $nr_sessions
}

# LIO/ACL uses 2 types of authorization:
# - using initiator IQN + credentials
# - using credentials
#
# WebCP assign user without IQN, so this function doesn't create IQN ACL, so generate_node_acls must be enabled
function _tcm_add_account_iscsi() {
	local $*
	$TCMADM /iscsi/${target}/tpg1 set attribute authentication=1 >>${TCM_LOG_FILE} 2>&1 && \
		$TCMADM /iscsi/${target}/tpg1 set auth userid=$user password=$passw >>${TCM_LOG_FILE} 2>&1
	return $*
}

# ---------------------------------------------------------------------------------------
# FC/FCoE transport

function _tcm_get_target_wwpn() {
	local $*

	addresses=`cat "$ISCSI_ROOT/$target/control/address"`
	if [ -z "$addresses" ] ; then
		echo "Unknown address for target $target" >>${TCM_LOG_FILE} 2>&1
		return 1
	fi

	fc_validate_addr $addresses >/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "Invalid WWPN for target $target" >>${TCM_LOG_FILE} 2>&1
		return 2
	fi

	echo $addresses
	return 0
}

function _tcm_add_portal_fc() {
	echo "'${FUNCNAME[0]}()' is not supported yet for SCSI_TRANSPORT=fc" >>${TCM_LOG_FILE}
	exit 1
}

function _tcm_create_target_fc() {
	local $*

	for wwpn in $addresses; do
		ep=`_tcm_fc_endpoint wwpn=$wwpn 2>>${TCM_LOG_FILE}`
		[ $? -ne 0 ] && continue

		$TCMADM /$ep create $wwpn >>${TCM_LOG_FILE} 2>&1
		rc=$?
		[ $rc -ne 0 ] && return $rc
	done

	return 0
}

function _tcm_show_initiators_fc() {
	local "$@"
	local user="unauthorized"
	local wwpn=""

	[ -z $portals ] && return 0

	ep=`_tcm_fc_endpoint wwpn=$portals 2>>${TCM_LOG_FILE}`
	[ $? -ne 0 ] && return 1

	initiators=`fc_get_initiators "$portals"`
	[ -z $initiators ] && return 0

	account=`/bin/readlink "$ISCSI_ROOT/$target/control/account"`
	if [ -n "$account" ] ; then
		user=`basename $account`
		wwpn=`cat "$account/auth" `
	fi

	for i in $initiators; do
		$TCMADM /$ep/naa.$portals/acls/naa.$i >/dev/null 2>&1
		if [ $? -ne 0 -o -z "${wwpn}" -o "${i}" != "${wwpn}" ]; then
			echo "unauthorized $i"
		else
			echo "$user $i"
		fi
	done

	return 0
}

# NOTE: A FCoE target doesn't export any information in a sys_fs in vn2vn mode (in current kernel 3.10)
function _tcm_target_has_active_sessions_fc() {
	local "$@"

	initiators=`fc_get_initiators "$portals"` 2>/dev/null
	[ $? -ne 0 ] && return 0

	ep=`_tcm_fc_endpoint wwpn=$portals 2>>${TCM_LOG_FILE}`
	[ $? -ne 0 ] && return 1

	for i in $initiators; do
		$TCMADM /$ep/naa.$portals/acls/naa.$i >/dev/null 2>&1
		if [ $? -eq 0 ]; then
			return 1
		fi
	done

	return 0
}

# In current version (a may be always) TCM FCoE doesn't support authentication via user/password info.
# Also TCM FCoE doesn't allow dynamic ACL, so let's use account for zonning in following way:
# <username> - symbolic name which can be used in `show_initiator`
# <password> - WWPN of neighbour
#
# NOTE: by default TCM mappes all new LUNs to all acl's, so doesn't matter which LUNs exist now
function _tcm_add_account_fc() {
	local $*

	wwpn=`_tcm_get_target_wwpn target=$target 2>/dev/null`
	[ $? -ne 0 ] && return 1

	ep=`_tcm_fc_endpoint 2>>${TCM_LOG_FILE}`
	[ $? -ne 0 ] && return 2

	$TCMADM /$ep/naa.$wwpn/acls create $passw >>${TCM_LOG_FILE} 2>&1

	return $?
}

# ---------------------------------------------------------------------------------------

function tcm_add_portal() {
	local $*
	_tcm_add_portal_${SCSI_TRANSPORT} target=$target ip_address=$ip_address port=$TGTD_PORT
	return $?
}

function _tcm_verify_target_existence() {
	local $*
	tgt_path=`_tcm_target_path target=$target`
	[ $? -ne 0 ] && return 1

	msg=`$TCMADM $tgt_path 2>&1`
	[ $? -eq 0 ] && return 0

	if [[ "$msg" == "No such path"* ]] ; then
		return 2
	fi

	return 3
}

function tcm_create_target() {
	local "$@"

	# TODO: configure this params in the config file and remove the command bellow
	# target discovery is available without CHAP auth, like in tgtd
	$TCMADM set global auto_add_default_portal=false auto_enable_tpgt=false >>${TCM_LOG_FILE} 2>&1 && \
		$TCMADM /iscsi set discovery_auth enable=0 >>${TCM_LOG_FILE} 2>&1
	rc=$?
	if [ $rc -ne 0 ]; then
		echo "Failed configure TCM" 1>&2
		return $rc
	fi

	_tcm_create_target_${SCSI_TRANSPORT} target=$target addresses="$addresses"

	# Persistent reservation is enabled from the box (???):
	# http://linux-iscsi.org/wiki/Persistent_Reservations
	# path: /var/target/pr/*

	return $?
}

function _tcm_list_luns() {
	local "$@"

	case $SCSI_TRANSPORT in
		"iscsi")
			ls /sys/kernel/config/target/iscsi/${target}/tpgt_1/lun/ 2>/dev/null | cut -d '_' -f2
			;;
		"fc")
			# transform wwpn to format 00:11:22:33:44:55:66:77
			p=`echo "$portals" | sed 's/\([[:xdigit:]]\{2\}\)/\1:/g'`
			ls /sys/kernel/config/target/fc/${p::-1}/tpgt_1/lun/ 2>/dev/null | cut -d '_' -f2
			;;
	esac
}

function tcm_delete_target() {
	local $*

	# this checking prevents errors after incomplete target deletion
	_tcm_verify_target_existence target=$target
	[ $? -ne 0 ] && return 0

	portals=$(tcm_show_target_portals target=$target) 2>>${TCM_LOG_FILE}
	if [ -z "$portals" ]; then
		echo "Can't find portals for target '$target'" 1>&2
		return 1
	fi

	if [ -n "$force" ]; then
		# Offline everything first on forced stop.
		tcm_disable_target target=$target
		if [ $? -ne 0 ]; then
			echo "Failed to disable target '$target'" 1>&2
			return 2
		fi
	fi

	busy=1
	attempts=10
	while true; do
		_tcm_target_has_active_sessions_${SCSI_TRANSPORT} target=$target portals="$portals"
		busy=$?
		attempts=$(($attempts-1))
		if [ $attempts -eq 0 -o $busy -eq 0 ] ; then
			break
		fi
		sleep 1
	done

	if [ $busy -ne 0 ] ; then
		echo "Can't stop TCM target, there are active initiators" 1>&2
		return 3
	fi

	# detach all luns directrly to prevent ploop holding
	luns=`_tcm_list_luns target=$target portals="$portals"`
	for lun_id in $luns; do
		tcm_detach_lun lun_id=$lun_id
	done

	tgt_path=`_tcm_target_path target=$target`
	[ $? -ne 0 ] && return 4

	tcm_transport=`echo $tgt_path | cut -d '/' -f2`
	tcm_target=`echo $tgt_path | cut -d '/' -f3`

	$TCMADM /$tcm_transport delete $tcm_target >>${TCM_LOG_FILE} 2>&1
	[ $? -ne 0 ] && return 5

	return 0
}

# authentication params:
#
# authentication=0
# ----------------
# If set to 1, enforce authentication for this TPG.
#
# generate_node_acls=0
# --------------------
# If set to 1, allow all initiators to login (i.e. demo mode).
#
# demo_mode_write_protect=0
# -------------------------
# If set to 1 in demo mode, prevent writes to LUNs
function tcm_disable_authentication() {
	local $*

	case $SCSI_TRANSPORT in
		"iscsi")
			$TCMADM /iscsi/$target/tpg1 set attribute authentication=0 generate_node_acls=1 demo_mode_write_protect=0 >>${TCM_LOG_FILE} 2>&1
			;;
		"fc")
			# doesn't make sense for vn2vn + fc doesn't support dynamic acls, only WWPN authentication
			echo "">/dev/null
			;;
	esac

	return $?
}

function tcm_add_account() {
	local $*
	_tcm_add_account_${SCSI_TRANSPORT} target=$target user=$user passw=$passw
	return $?
}

# ======================
# AuthMethod=CHAP,None
# --------------------
# Authentication method used by the TPG.
#
# DataDigest=CRC32C,None
# ----------------------
# If set to CRC32C, the integrity of the PDU data part is verified.
#
# DataPDUInOrder=Yes
# ------------------
# If set to Yes, the data PDUs within sequences must be in order.
#
# DataSequenceInOrder=Yes
# -----------------------
# If set to Yes, the data sequences must be in order.
#
# DefaultTime2Retain=20
# ---------------------
# Maximum time, in seconds, after an initial wait, before which an active task reassignment is still possible after an unexpected connection termination or a connection reset.
#
# DefaultTime2Wait=2
# ------------------
# Minimum time, in seconds, to wait before attempting an explicit/implicit logout or an active task reassignment after an unexpected connection termination or a connection reset.
#
# ErrorRecoveryLevel=0
# --------------------
#
# HeaderDigest=CRC32C,None
# ------------------------
# If set to CRC32C, the integrity of the PDU header part is verified.
#
# IFMarkInt=2048~65535
# --------------------
# Deprecated according to RFC 7143.
#
# IFMarker=No
# -----------
# Deprecated according to RFC 7143.
#
# ImmediateData=Yes
# -----------------
# Immediate data support.
#
# InitialR2T=Yes
# --------------
# If set to No, the default use of R2T (Ready To Transfer) is disabled.
#
# MaxBurstLength=262144
# ---------------------
# Maximum SCSI data payload in bytes in a Data-In or a solicited Data-Out iSCSI sequence.
#
# MaxConnections=1
# ----------------
# Maximum number of connections acceptable.
#
# MaxOutstandingR2T=1
# -------------------
# Maximum number of outstanding R2Ts per task.
#
# MaxRecvDataSegmentLength=8192
# -----------------------------
# Maximum data segment length in bytes the target can receive in an iSCSI PDU.
#
# MaxXmitDataSegmentLength=262144
# -------------------------------
# Outgoing MaxRecvDataSegmentLength sent over the wire during iSCSI login response.
#
# OFMarkInt=2048~65535
# --------------------
# Deprecated according to RFC 7143.
#
# OFMarker=No
# -----------
# Deprecated according to RFC 7143.
#
# TargetAlias=LIO Target
# ----------------------
# Human-readable target name or description.
function tcm_configure_target() {
	local $*
	# TODO
	return 0
}

function tcm_enable_target() {
	local $*

	tgt_path=`_tcm_target_path target=$target`
	[ $? -ne 0 ] && return 1

	$TCMADM $tgt_path enable >>${TCM_LOG_FILE} 2>&1
	return $?
}

function tcm_disable_target() {
	local $*

	tgt_path=`_tcm_target_path target=$target`
	[ $? -ne 0 ] && return 1

	$TCMADM $tgt_path disable >>${TCM_LOG_FILE} 2>&1
	return $?
}

function tcm_get_target_status() {
	local $*

	tgt_path=`_tcm_target_path target=$target` 2>&1
	if [ $? -ne 0 ]; then
		echo "service failed"
		return 1
	fi

	status=`$TCMADM $tgt_path info 2>/dev/null`
	if [ $? -ne 0 ]; then
		echo "service failed"
		return 1
	fi

	echo $status | grep "enable:\s*True">/dev/null
	if [ $? -ne 0 ]; then
		echo "not running"
		return 1
	fi

	echo "running"
	return 0
}

function tcm_show_target_portals() {
	local $*

	case $SCSI_TRANSPORT in
		"iscsi")
			ls -1 /sys/kernel/config/target/iscsi/$target/tpgt_1/np/ 2>/dev/null
			;;
		"fc")
			_tcm_get_target_wwpn target=$target 2>/dev/null
			;;
	esac
}

# Research info:
# 1) Linux kernel since 4.1 provides target_show_dynamic_sessions() call.
# It's used to see dynamic ACLs in config_fs
#
# 2) There is no stable way to get dynamic ALC in CentOS kernel (3.10.*)
function tcm_show_initiators() {
	local $*
	portals=$(tcm_show_target_portals target=$target)
	_tcm_show_initiators_${SCSI_TRANSPORT} target=$target portals="$portals"
	return $?
}

# this is HACK, targetcli doesn't allow set WWN for device. Also device must be detached from targets
function tcm_set_backstore_wwn() {
	local $*
	dev_name=`echo $dev | cut -d '/' -f3`
	members=`cat /sys/kernel/config/target/core/alua/lu_gps/default_lu_gp/members`
	iblock_path=`echo ${members} | tr ' ' '\n' | grep $dev_name`
	if [ $? -ne 0 ] || [ -z $iblock_path ] ; then return 1 ; fi

	ls /sys/kernel/config/target/core/$iblock_path/wwn/vpd_unit_serial >/dev/null 2>&1
	[ $? -ne 0 ] && return 2

	echo $wwn > /sys/kernel/config/target/core/$iblock_path/wwn/vpd_unit_serial 2>&1

	return $?
}

# FIXME: SCSI standart requires "must have LUN 0".
#        WebCP doesn't allow to create LUN 0.
#        Targetcli doesn't allow to create LUN 0 without device/file, like in tgtd.
#        So, reserach is it correct to have iscsi without LUN 0 in concept of LIO/TCM
function tcm_attach_lun() {
	local $*

	backstore_name=`basename $dev`

	$TCMADM /backstores/block create ${backstore_name} $dev >>${TCM_LOG_FILE} 2>&1
	rc=$?
	if [ $rc -ne 0 ] ; then
		echo "Failed to create block backstore: dev=$dev" 1>&2
		return 1
	fi

	# set wwn before attaching a backstore to a target
	set_wwn_result=0
	if [ -n $serial_sn ]; then
		tcm_set_backstore_wwn dev=$dev wwn=$serial_sn
		set_wwn_result=$?
	fi

	tgt_path=`_tcm_target_path target=$target`
	rc=$?
	if [ $rc -eq 0 ]; then
		$TCMADM $tgt_path/luns create /backstores/block/${backstore_name} $lun_id >>${TCM_LOG_FILE} 2>&1
		rc=$?
	fi

	if [ $rc -ne 0 ] ; then
		echo "Failed to create LUN $lun_id for target $target)" 1>&2
		$TCMADM /backstores/block delete ${backstore_name} >>${TCM_LOG_FILE} 2>&1
		return 2
	fi

	if [ $set_wwn_result -ne 0 ]; then
		echo "Can't set serial_sn '$serial_sn' for new LUN $lun_id" 1>&2
	fi

	return $set_wwn_result
}

function tcm_detach_lun() {
	local $*

	tgt_path=`_tcm_target_path target=$target`
	[ $? -ne 0 ] && return 1

	lun_info=`$TCMADM ls $tgt_path/luns/lun${lun_id}`
	if [ -z "$lun_info" ]; then
		echo "Can't find LUN ${lun_id} for target ${target}" 1>&2
		return 2
	fi

	dev=`echo $lun_info | grep -o -E 'block/\S+' | cut -d '/' -f2`

	$TCMADM $tgt_path/luns/ delete lun${lun_id} >>${TCM_LOG_FILE} 2>&1
	rc=$?
	[ $rc -ne 0 ] && return 3

	$TCMADM /backstores/block delete $dev >>${TCM_LOG_FILE} 2>&1
	rc=$?
	if [ $rc -ne 0 ] ; then
		echo "Can't remove device /dev/$dev from backstores" 1>&2
		return 4
	fi

	return 0
}

function tcm_is_lun_online() {
	local $*

	tgt_path=`_tcm_target_path target=$target` 2>>${TCM_LOG_FILE}
	if [ $? -ne 0 ]; then
		echo "No"
		return
	fi

	lun_status=$($TCMADM $tgt_path/luns/lun$lun_id) 1>/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "No"
		return
	fi

	$TCMADM $tgt_path info | grep "enable: True" 1>/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "No"
		return
	fi

	echo "Yes"
}

function tcm_detach_dev() {
	local $*
	dev_name=`echo $dev | cut -d '/' -f3`

	msg=`$TCMADM /backstores/block delete $dev 2>&1`
	rc=$?

	[ $rc -eq 0 ] && return 0

	[ "$msg" == "No storage object named $dev_name." ] && return 0

	echo "Can't remove device /dev/$dev_name from backstores" 1>&2
	echo $msg

	return 1
}

function tcm_show_lun_stats() {
	local $*
	echo ""
}

function tcm_set_limit() {
	echo "LIO/TCM set_limit isn't supported"
	return 1
}

function tcm_rescan_lun() {
	# this is not required for TCM, all changes are detected by a SCSI system
	return 0
}
