#!/bin/bash

shopt -s nullglob

err_base="extensions/ibft"

function do_readlink
{
	local opt link tool
	case $1 in
	-f) opt=$1 ; shift ;;
	esac
	link=$1
	tool=`type -P readlink 2>/dev/null`
	if test -x "$tool" ; then
		readlink $opt "$link" 2>/dev/null
	else
		cd -P "$link" 2>/dev/null && echo $PWD
	fi
}

function glob_first
{
	local pattern="$1"

	# This works because we set nullglob above - if there's
	# no match, the pattern word is removed
	set -- $pattern
	echo "$1"
}

function ibft_get_ifindex
{
	local devpath=$1
	local ibftmac=$2
	local net_dir=""

	if [ -d "$devpath/net/" ]; then
		# normal reference, e.g. device/net/eth0
		net_dir="$devpath/net"
	else
		# virtio reference, e.g. device/virtio0/net/eth0,
		# because iBFT references PCI IDs without func.
		net_dir=`glob_first $devpath/*/net/`
	fi

	if [ -n "$net_dir" ]; then
		for idxpath in ${net_dir}/*/ifindex ;  do
			ifindex=`cat "$idxpath" 2>/dev/null`
			[ -n "$ifindex" ] || continue

			ifbase=${idxpath%/ifindex}
			ifname=${ifbase##*/}
			if [ -n "$ibftmac" ] ; then
				mac=`cat "$ifbase/address" 2>/dev/null`
				if [ "X$mac" = "X$ibftmac" ] ; then
					echo $ifindex $ifname
					return 0
				fi
			else
				echo $ifindex $ifname
				return 0
			fi
		done
	fi

	return 1
}

function ibft_getattr
{
	local path="$1"
	if [ -f "$path" ]; then
		cat "$path"
	fi
}

function netmask2pfxlen
{
	local netmask=$1
	local OFS=$IFS

	IFS=. ; set -- $netmask ; IFS=$OFS

	declare -i nbits=0
	for octet; do
		case $octet in
		255)	nbits+=8;;
		254)	nbits+=7;;
		252)	nbits+=6;;
		248)	nbits+=5;;
		240)	nbits+=4;;
		224)	nbits+=3;;
		192)	nbits+=2;;
		128)	nbits+=1;;
		0)	: ;;
		*)	echo "$err_name: Invalid netmask \"$netmask\"" >&2
			return 1;;
		esac
	done

	echo $nbits
}

function get_vlan_ifname
{
	local vid=$1 dev=$2 line=""
	local conf="/proc/net/vlan/config"

	test -f "$conf" && \
	while read line ; do
		case $line in
		"VLAN Dev name"*|"Name-Type:"*) continue 2 ;;
		esac
		set -- ${line//|/ }
		test "$dev" = "$3" || continue
		test "$vid" = "$2" || continue
		echo "$1"
	done < "$conf" 2>/dev/null
}

function gen_control
{
	echo "  <control>"
	echo "    <persistent>true</persistent>"
	echo "    <usercontrol>false</usercontrol>"
	echo "  </control>"
}

# sysfs mountpoint
opt_rootdir=""
# relative ibft node
opt_fw_path=""
# list only mode
opt_listonly="no"
# [always] load iscsi_ibft
opt_modprobe="iscsi_ibft"
#set -- `getopt r:p: "$@"`
while [ $# -gt 0 -a "$1" != "--" ]; do
	opt=$1; shift;
	case $opt in
	-r)	opt_rootdir=$1;			shift;;
	-p)	opt_fw_path=$1;			shift;;
	-m)	opt_modprobe=$1;		shift;;
	-l)	opt_listonly=yes		;;
	*)	echo "$err_base: Bad command line option \"$opt\"" >&2; exit 1;;
	esac
done

IBFT_NICS="ethernet"
IBFT_PATH="/sys/firmware/ibft"
IBFT_ROOT="${opt_rootdir}${IBFT_PATH}"
if test ! -d "$IBFT_ROOT" -a "X$opt_modprobe" != "X" ; then
	modprobe -qs -- "${opt_modprobe}" &>/dev/null || :
fi
for nicpath in "${IBFT_ROOT}/${IBFT_NICS}"*; do
	[ "X$nicpath" != "X" -a -d "$nicpath" ]     || continue

	ibft_nic="${nicpath##*/}"
	ibft_num="${ibft_nic##${IBFT_NICS}}"
	[ -n "$ibft_num" ]                          || continue

	[ "X$opt_fw_path" != "X" -a \
	  "X$opt_fw_path" != "X${ibft_nic}" ]       && continue

	err_name="$err_base/$ibft_nic"

	nic_flags=`ibft_getattr "$nicpath/flags"`
	# Check for valid NIC block
	[ -n "$nic_flags" ] && ((nic_flags & 0x01)) || continue

	devpath=`do_readlink -f $nicpath/device`
	[ -n "$devpath" ]                           || continue

	nic_mac=`ibft_getattr "$nicpath/mac"`
	set -- `ibft_get_ifindex "$devpath" "$nic_mac"`
	[ $# -gt 0 ]                                || continue

	nic_ifindex=$1
	nic_name=$2

	nic_vlan=`ibft_getattr "$nicpath/vlan"`
	if [ "X$opt_listonly" = "Xyes" ] ; then
		if [ -n "$nic_vlan" -a $((nic_vlan)) -gt 0 ]; then
			vlan_ifname=`get_vlan_ifname "$nic_vlan" "$nic_name" 2>/dev/null`
			if test "x$vlan_ifname" = x ; then
				vlan_ifname="${nic_name}.${nic_vlan}"
			fi
			echo "$vlan_ifname $nic_name"
		else
			echo "$nic_name"
		fi
		continue
	fi

	# Enum: Other,Manual,WellKnown,Dhcp,RouterAdv
	# Note: kvm/gPXE is using 0, even it is from dhcp
	nic_origin=`ibft_getattr "$nicpath/origin"`
	nic_ipaddr=`ibft_getattr "$nicpath/ip-addr"`
	nic_prefixlen=$(ibft_getattr "$nicpath/prefix-len")
	nic_dhcp_server=`ibft_getattr "$nicpath/dhcp"`
	nic_gateway=`ibft_getattr "$nicpath/gateway"`
	nic_dns_server1=`ibft_getattr "$nicpath/primary-dns"`
	nic_dns_server2=`ibft_getattr "$nicpath/secondary-dns"`
	nic_hostname=`ibft_getattr "$nicpath/hostname"`

	case "$nic_ipaddr" in
	*.*.*.*)
		nic_family="ipv4"
		if [ -z "$nic_prefixlen" ] ; then
			# Older kernel iBFT support isn't IPv6 aware
			# and always converts the iBFT prefix length
			# byte into dotted-decimal ipv4 subnet-mask.
			netmask=`ibft_getattr "$nicpath/subnet-mask"`
			nic_prefixlen=`netmask2pfxlen "$netmask" 2>/dev/null`
		fi
		;;
	*:*)
		nic_family="ipv6"
		;;
	esac
	[ -n "$nic_family" ] || continue
	if [ $nic_origin -ne 3 -a -n "$nic_ipaddr" ] ; then
		# we need a prefix length for static setup
		[ -n "$nic_prefixlen"    ] || continue
		[ "$nic_prefixlen" = "0" ] && continue
	fi

	cat <<-EOF
	<interface origin="firmware:ibft:$ibft_nic">
	  <name namespace="ifindex">$nic_ifindex</name>
	  <alias>ibft${ibft_num}.0</alias>
	EOF

	gen_control

	if [ -n "$nic_vlan" -a $((nic_vlan)) -gt 0 ]; then
		vlan_ifname=`get_vlan_ifname "$nic_vlan" "$nic_name" 2>/dev/null`
		if test "x$vlan_ifname" = x ; then
			vlan_ifname="${nic_name}.${nic_vlan}"
		fi

		cat <<-EOF
		</interface>

		<interface origin="firmware:ibft:$ibft_nic">
		  <name>$vlan_ifname</name>
		  <alias>ibft${ibft_num}.${nic_vlan}</alias>
		EOF

		gen_control

		cat <<-EOF
		  <vlan>
		    <device namespace="ifindex">$nic_ifindex</device>
		    <tag>$nic_vlan</tag>
		  </vlan>
		EOF
	fi

	if [ $nic_origin -ne 3 -a -n "$nic_ipaddr" ]; then
		case $nic_gateway in
		"")
			default_route="<!-- no default route -->"
			;;
		::|0.0.0.0|255.255.255.255)
			default_route="<!-- wrong default route: $nic_gateway ignored -->"
			;;
		*)
			default_route="<route><nexthop><gateway>$nic_gateway"
			default_route="$default_route</gateway></nexthop></route>"
			;;
		esac
		if [ -n "$nic_hostname" ]; then
			host_name="<hostname>$nic_hostname</hostname>"
		else
			host_name="<!-- no host name -->"
		fi

		dns_servers=""
		for s in "$nic_dns_server1" "$nic_dns_server2" ; do
			[ -n "$s" ] || continue
			dns_servers+="<server>$s</server>"
		done
		if [ -n "$dns_servers" ] ; then
			dns_servers="<resolver><servers>$dns_servers</servers></resolver>"
		else
			dns_servers="<!-- no dns servers -->"
		fi

		cat <<-EOF
		  <$nic_family:static>
		    <address><local>$nic_ipaddr/$nic_prefixlen</local></address>
		    $default_route
		    $dns_servers
		    $host_name
		  </$nic_family:static>
		EOF
	fi
	if [ -n "$nic_dhcp_server" -o "$nic_origin" -eq 3 ] ; then
		# Note: There is currently no root-path option
		#       defined for DHCPv6 in any RFC...
		cat <<-EOF
		  <$nic_family:dhcp>
		    <enabled>true</enabled>
		  </$nic_family:dhcp>
		EOF
	fi

	echo "</interface>"
done

exit 0
