#!/bin/bash

FCOE_DEVICES="/usr/libexec/vstorage-iscsi/fcoe_devices"
FCOE_VLAN_ID=101
FCOE_VN2VN_MODE="vn2vn"
FCOEADM=fcoeadm
DCBTOOL=dcbtool
LLDPTOOL=lldptool
SYSTEMD_TOOL="/usr/bin/systemctl"

source /usr/libexec/vstorage-iscsi/fc_functions

# list all net devices (including logical: lo, bridge)
function _list_netdev() {
	ls /sys/class/net/ -1 2>/dev/null
	rc=$?

	if [ $rc -ne 0 ]; then
		echo "sysfs isn't mount" 1>&2
		return 1
	fi

	return 0
}

function _is_hw_netdev() {
	local netdev=$1

	ls /sys/class/net/$netdev/device >/dev/null 2>&1
	rc=$?
	[ $rc -ne 0 ] && return 1

	return 0
}

# list hardware net devices (without logical: lo, bridge)
function _list_hw_netdev() {
	local list_netdev=`_list_netdev`
	rc=$?
	[ $rc -ne 0 ] && return $rc # an error is logged

	for netdev in $list_netdev; do
		_is_hw_netdev $netdev
		if [ $? -eq 0 ]; then
			echo "$netdev"
		fi
	done

	return 0
}

# check software dcb management
#
# Research info:
# This information can't be obtain in runtime and it's available in a driver description.
# So, this function uses a file with module names that support FCoE
function fcoe_need_software_dcb() {
	local netdev=$1

	fcoe_check_device_dcb_support $netdev
	if [ $? -ne 0 ]; then
		echo "no"
		return 0
	fi

	driver_name=`ethtool -i $netdev 2>/dev/null | grep -Po '(?<=driver\:\s).*' 2>/dev/null`
	need_software_dcb=`grep $driver_name $FCOE_DEVICES | awk '{ printf $2'\n' }' 2>/dev/null`

	need_software_dcb=`echo $need_software_dcb | tr [:upper:] [:lower:]` # to lower case
	if [ "$need_software_dcb" = "yes" -o "$need_software_dcb" = "no" ]; then
		echo $need_software_dcb
		return 0
	fi

	echo "no"
	return 0
}

# check whether device support dcb (in some version of dcbtool device must be up)
function fcoe_check_device_dcb_support() {
	local netdev=$1

	_is_hw_netdev $netdev
	if [ $? -ne 0 ]; then
		return 1
	fi

	$DCBTOOL gc $netdev dcb >/dev/null 2>&1
	[ $? -ne 0 ] && return 1

	return 0
}

# list of net devices that support FCoE
function fcoe_list_netdev() {
	local list_hw_dev=`_list_hw_netdev`
	[ $? -ne 0 ] && return 1 # an error is logged

	for netdev in $list_hw_dev; do
		fcoe_check_device_dcb_support $netdev
		[ $? -eq 0 ] && echo $netdev
	done

	return 0
}

# arg 1: fcoe net device
# arg 2: mode { fabric, vn2vn(default) }
#
# TODO:
# 1) research configuration CNA with 2 ports
# 2) maintain fabric mode
function fcoe_up_hba() {
	local netdev=$1
	local mode=${2:-$FCOE_VN2VN_MODE}

	vlan_netdev=`_fcoe_vlan_from_netdev $netdev`
	[ $? -ne 0 ] && return 1

	dcb_required=`fcoe_need_software_dcb $netdev`

	fcoe_check_device_dcb_support $netdev
	if [ $? -ne 0 ] && [ "$mode" != "$FCOE_VN2VN_MODE" ]; then
		echo "Device '$netdev' doesn't support FCoE" 1>&2
		return 2
	fi

	$FCOEADM -i $vlan_netdev 1>/dev/null 2>&1
	fcoe_hba_status $netdev >/dev/null 2>&1
	if [ $? -eq 0 ]; then
		echo "FCoE is up on '$netdev'" 1>&2
		return 3
	fi

	# create FIP discover responder on a CNA
	if [ "$mode" = "vn2vn" ]; then
		cat >/etc/fcoe/cfg-$netdev <<-EOF
		FCOE_ENABLE="no"
		DCB_REQUIRED="no"
		AUTO_VLAN="no"
		MODE="${mode}"
		FIP_RESP="yes"
		EOF
	else
		# see TODO
		echo "Fabric mode isn't supported yet" 1>&2
		exit 1
	fi

	[ $? -ne 0 ] && return 4

	# create FCoE on a CNA VLAN
	cat >/etc/fcoe/cfg-$vlan_netdev <<-EOF
	FCOE_ENABLE="yes"
	DCB_REQUIRED="${dcb_required}"
	AUTO_VLAN="no"
	MODE="${mode}"
	FIP_RESP="no"
	EOF

	[ $? -ne 0 ] && return 5

	ip link add link $netdev name $vlan_netdev type vlan id $FCOE_VLAN_ID && \
		ip link set $netdev up && \
		ip link set $vlan_netdev up

	if [ $? -ne 0 ]; then
		fcoe_down_hba $netdev >/dev/null 2>&1
		return 6
	fi

	if [ "$dcb_required" = "yes" ]; then
		# this branch isn't tested (need CNA without firmware dcb management)
		$LLDPTOOL set-lldp -i $netdev adminStatus=rxtx 1>/dev/null  && \
			$DCBTOOL sc $netdev dcb on 1>&2 && \
			$DCBTOOL sc $netdev pfc e:1 a:1 w:1 1>&2 && \
			$DCBTOOL sc $netdev app:fcoe e:1 1>&2
	else
		$LLDPTOOL set-lldp -i $netdev adminStatus=disabled >/dev/null
	fi

	if [ $? -ne 0 ]; then
		echo "Failed to configure lldp/dcb on device '$netdev'" 1>&2
		fcoe_down_hba $netdev >/dev/null 2>&1
		return 7
	fi

	$SYSTEMD_TOOL restart fcoe.service

	return $?
}

function fcoe_down_hba() {
	netdev=`_fcoe_netdev_from_vlan $1 2>/dev/null`
	vlan_netdev=`_fcoe_vlan_from_netdev $1 2>/dev/null`

	rm -rf /etc/fcoe/cfg-$netdev /etc/fcoe/cfg-$vlan_netdev

	fcoe_list_hba | grep $netdev>/dev/null 2>&1
	if [ $? -ne 0 ]; then
		echo "No FCoE found on '$netdev'" 1>&2
	else
		$FCOEADM -d $vlan_netdev
	fi

	systemctl restart fcoe.service

	# delete vlan, but don't DOWN a CNA, it can be used in ethernet role
	ip link set $vlan_netdev down >/dev/null 2>&1 && \
		ip link delete $vlan_netdev >/dev/null 2>&1

	return 0
}

function _fcoe_netdev_from_vlan() {
	local vlan=$1

	if [ -z "`_list_netdev | grep ^$vlan$`" ]; then
		echo "No such device '$vlan'" 1>&2
		return 1
	fi

	if [ -z "`echo $vlan | grep $FCOE_VLAN_ID-fcoe`" ]; then
		echo $1
	else
		echo $vlan | grep -oP '\S+(?=\.101-fcoe)'
	fi

	return 0
}

function _fcoe_vlan_from_netdev() {
	local netdev=$1

	if [ -z "`_list_netdev | grep ^$netdev$`" ]; then
		echo "No such device '$netdev'" 1>&2
		return 1
	fi

	if [ -z "`echo $netdev | grep $FCOE_VLAN_ID-fcoe`" ]; then
		echo $netdev.$FCOE_VLAN_ID-fcoe
	else
		echo $netdev
	fi

	return 0
}

##
# Show information about FCoE hba's in format:
# symbolic name | fc_host | wwpn | fc_id | state | [neighbour wwpn] | [neighbour fc_id]
function fcoe_list_hba() {
	fcoe_devs=`fc_list_hba | grep fcoe`
	[ $? -ne 0 ] && return 0

	echo "$fcoe_devs"
	return 0
}

function fcoe_hba_wwpn() {
	local netdev=$1

	fcoe_devs=`fcoe_list_hba`
	[ $? -ne 0 ] && return 1

	if [ -z "$netdev" ]; then
		echo "$fcoe_devs" | cut -d '|' -f3 | sed "s/^[ \t]0x*//"
		return 0
	fi

	vlan=`_fcoe_vlan_from_netdev $netdev`
	[ $? -ne 0 ] && return 2
	
	dev=`echo "$fcoe_devs" | grep $vlan`
	if [ $? -ne 0 ]; then
		echo "No FCoE HBA found on vlan netdev '$vlan'"
		return 3
	fi

	echo "$dev" | cut -d '|' -f3 | sed "s/^[ \t]0x*//"
	return 0
}

function fcoe_hba_status() {
	local netdev=$1

	fcoe_devs=`fcoe_list_hba`
	[ $? -ne 0 ] && return 1

	if [ -z "$netdev" ]; then
		echo "$fcoe_devs" | cut -d '|' -f5 | sed "s/^[ \t]*//"
		return 0
	fi

	vlan=`_fcoe_vlan_from_netdev $netdev`
	[ $? -ne 0 ] && return 2
	
	dev=`echo "$fcoe_devs" | grep $vlan`
	if [ $? -ne 0 ]; then
		echo "No FCoE HBA found on vlan netdev '$vlan'"
		return 3
	fi

	echo "$dev" | cut -d '|' -f5 | sed "s/^[ \t]*//"
	return 0
}

# forcible deletion
function fcoe_destroy_hba() {
	local netdev=$1

	vlan=`_fcoe_vlan_from_netdev $netdev`
	[ $? -ne 0 ] && return 1
	echo $vlan > /sys/bus/fcoe/ctlr_destroy /dev/null 2>&1
	return $?
}

# --------------------------------------------------------------------------------------
# vstorage scsi target interface

function fcoe_validate_addr() {
	local wwn=$1
	fc_validate_addr $wwn
	return $?
}

function fcoe_init_host() {
	return 0
}

function fcoe_remove_addr() {
	return 0
}

function fcoe_add_addr() {
	return 0
}

function fcoe_get_initiators() {
	local port=$1

	fcoe_devs=`fcoe_list_hba`
	[ $? -ne 0 ] && return 1

	if [ -z "$port" ]; then
		echo "$fcoe_devs" | cut -d '|' -f6 | cut -d 'x' -f2
		return 0
	fi

	dev=`echo "$fcoe_devs" | grep $port`
	if [ $? -ne 0 ]; then
		echo "No FCoE HBA found with wwpn '$port'"
		return 2
	fi

	echo "$dev" | cut -d '|' -f6 | cut -d 'x' -f2
	return 0
}
