#! /bin/bash
#
# Copyright 2008-2010 Cisco Systems, Inc.  All rights reserved.
#
# This program is free software; you may redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Author: Joe Eykholt (jeykholt at cisco dot com)
#
# Please send comments and changes to jeykholt at cisco dot com

VERSION="fcc v1.0.13 11/10/2010"

fcoe_dir=/sys/module/fcoe
fcoe_lib_dir=/sys/module/libfcoe
fdir=/sys/class/fc_host
sdir=/sys/class/scsi_host
cmdname=`basename $0`

usage() {
cat <<USAGE
usage: $cmdname '[<cmd> [<hba> ...]]'

cmd:
	create 		Start FCoE on an Ethernet interface.
	create-vn	Start FCoE (VN_port-VN_port) on an Ethernet interface.
	delete / del	Delete an FCoE instance
	destroy 	Same as delete
	enable / en	Same as create
	help		Show this usage message
	info		Show HBA detailed info
	list		List the HBAs with remote port and LUN status
	luns		Show LUN list and status
	scan		Scan the HBA
	stats		Show HBA statistics
	reload		Reload fcoe and fnic modules
	reset		Reset the HBA
	version		Show version
USAGE
}

verify_hba() {
	local x=$1

	if [ ! -d $fdir/$x ]
	then
		echo "$cmdname: invalid HBA name $x" >&2
		exit
	fi
}

hba_stats() {
	local x=$1
	local y

	verify_hba $x

	printf "\n$x Statistics:\n"
	(
		cd $fdir/$x/statistics
		for y in *
		do
			#
			# avoid the write-only files.
			# test -r doesn't help if we're root
			#
			if [ "$y" == "reset_statistics" ]
			then
				continue
			fi
			val=`cat $y`
			if [ "$val" != 0xffffffffffffffff ]
			then
				printf "%-30s%8lld\n" $y: $val
			fi
		done
	)
}


#
# format a /sys file containing a hex WWN or FC_ID
# from 0x123456\n to 12:34:56 or from 0xffffffff to - (unknown WWN or FC_ID)
#
fmt_hex() {
	sed -e 's/^0xff*$/-/' -e 's/0x//' -e 's/../&:/g' -e 's/:$//' < $1
}

rport_list() {
	local x
	local hba=$1

	rdir=/sys/class/fc_remote_ports
	host=`echo $hba | sed -e 's/host//'`
	rports=`ls -d $rdir/rport-$host:* 2>/dev/null`
	if [ -z "$rports" ]
	then
		return
	fi

	printf "\n$hba Remote Ports:\n"


	fmt="%-8s  %-23s  %-8s  %-8s  %-15s\n"

	printf "$fmt" Path "Port Name" "Port ID" State Roles
	for x in $rports
	do
		btl="`echo $x | sed -e 's/.*-\(.*-.*\)/\1/'`"
		printf "$fmt" "$btl" \
			"`fmt_hex $x/port_name`" \
			"`fmt_hex $x/port_id`" \
			"`cat $x/port_state`" \
			"`cat $x/roles`"
	done
}

#
# Translate capacity to readable units.
# compute sizes in base-10 marketing units
#
lun_cap() {
	local sizek=0 sizem=0 sizeg=0 sizet=0
	local size
	local onek=1000
	local cap

	size=$1
	let sizek="$size * 512 / $onek"
	let sizem="$sizek / $onek"
	let sizeg="$sizem / $onek"
	let sizet="$sizeg / $onek"
	if [ "$sizet" -gt 0 ]
	then
		cap="$sizet TB"
	elif [ "$sizeg" -gt 0 ]
	then
		cap="$sizeg GB"
	elif [ "$sizem" -gt 0 ]
	then
		cap="$sizem MB"
	else
		cap="$sizek KB"
	fi
	echo $cap
}

lun_list() {
	local x
	local lun
	local hba=$1
	local ddir=/sys/class/scsi_device

	host=`echo $hba | sed -e 's/host//'`

	local luns=`(cd $ddir &&
		 ls -d $host:* | sort -n -t: -k1 -k2 -k3 -k4) 2>/dev/null`

	if [ -z "$luns" ]
	then
		return
	fi

	printf "\n$hba LUNs:\n"

	fmt="%-10s %-8s %6s   %-15s %-20s %-8s\n"

	printf "$fmt" Path Device Size Vendor Model State

	for lun in $luns
	do
		(
			local size=0
			cap=-

			cd $ddir/$lun/device
			if [ -d block ]
			then
				dev=`ls block | tail -1`
				size=`cat block/$dev/size`
				cap=`lun_cap $size`
			elif [ -d char ]
			then
				dev=`ls char | tail -1`
				cap=-
			elif [ -d scsi_tape ]
			then
				dev=`ls scsi_tape | egrep '^st[0-9]*$'`
				cap=-
			elif [ -d scsi_generic ]
			then
				dev=`ls scsi_generic | tail -1`
				cap=-
			else
				dev="`ls -d block:* char:* 2>/dev/null |
					sed -e 's/.*\://'`"
				if [ -L block:$dev -o -d block:$dev ]
				then
					size=`cat block:$dev/size`
					cap=`lun_cap $size`
				fi
			fi

			printf "$fmt" "$lun" "$dev" "$cap"\
				"`cat vendor`" \
				"`cat model`" \
				"`cat state`"
		)
	 done
}

sym_name() {
	local hba=$1
	local file

	file=$fdir/$hba/symbolic_name
	if [ -f "$file" ]
	then
		sed -e 's/.*over //' < $file
	else
		exit 1
	fi
}

#
# Lookup the host name for a given symbolic name
#
hba_name() {
	local sym=$1
	local hba

	if [ -d "$fdir/$sym" ]
	then
		echo $sym
		exit
	fi

	for hba in $all_hbas
	do
		if [ "`sym_name $hba`" = "$sym" ]
		then
			echo $hba
			exit
		fi
	done
	exit 1
}

hba_state() {
	local x

	echo "FC HBAs:"
	fmt="%-8s  %-23s  %-8s  %-8s  %-15s\n"
	printf "$fmt" HBA "Port Name" "Port ID" State Device
	for x in $hbas
	do
		(
			cd $fdir/$x
			printf "$fmt" "$x" \
				"`fmt_hex $fdir/$x/port_name`" \
				"`fmt_hex $fdir/$x/port_id`" \
				"`cat $fdir/$x/port_state`" \
				"`sym_name $x`"
		) 2>/dev/null
	done
}

hba_info() {
	local x=`hba_name $1`
	local fmt="\t%-20s %s\n"

	verify_hba $x
	printf "\n$x Info:\n"
	(
		cd $fdir/$x

		printf "$fmt" "Symbolic Name" "`cat symbolic_name`"
		printf "$fmt" "Port Name" "`fmt_hex port_name`"
		printf "$fmt" "Node Name" "`fmt_hex node_name`"
		printf "$fmt" "Port Type" "`cat port_type`"
		echo
		printf "$fmt" "Port State" "`cat port_state`"
		printf "$fmt" "Port ID" "`fmt_hex port_id`"
		printf "$fmt" "Fabric Name" "`fmt_hex fabric_name`"
		echo
		printf "$fmt" "Max Frame Size" "`cat maxframe_size`"
		printf "$fmt" "Speed" "`cat speed`"
		echo
	)
}

scsi_state() {
	local x
	local dev

	printf "\nSCSI States:\n"
	fmt="%-8s  %-10s  %-15s  %-10s  %8s\n"
	printf "$fmt" HBA Device Mode State Busy
	for x in $scsi_hbas
	do
		(
			cd $fdir/$x
			dev="`cat $sdir/$x/proc_name``cat $sdir/$x/unique_id`"
			printf "$fmt" "$x" "$dev" \
				"`cat $sdir/$x/active_mode`" \
				"`cat $sdir/$x/state`" \
				"`cat $sdir/$x/host_busy`"
		) 2>/dev/null
	done
}

hba_list() {
	local x

	hba_state
	#  scsi_state

	for x in $hbas
	do
		rport_list $x
		lun_list $x
	done
}

#
# Do a command for a list of arguments
#
repeat() {
	local cmd=$1
	local x
	shift

	for x
	do
		$cmd $x
	done
}

fcoe_ctl() {
	local cmd=$1
	local hba=$2
	local file=$fcoe_lib_dir/parameters/$cmd

	if [ -w "$file" ]
	then
		echo $hba > $file
	elif [ -f "$file" ]
	then
		echo "$cmdname: no permission to $cmd $hba" >&2
	else
		echo "$cmdname: file $file doesn't exist.  " \
			"Check for fcoe module." >&2
	fi
}

fc_host_ctl() {
	local hba=$2
	local host=$2
	local cmd=$1
	local value
	local file
	local dir

	dir=$fdir/$host
	if [ ! -d "$dir" ]
	then
		host=`hba_name $hba`
		dir=$fdir/$host
		if [ $? != 0 ]
		then
			echo "$cmdname: hba $hba not found" >&2
			exit 1
		fi
	fi

	case "$cmd" in
		reset)
			file=$dir/issue_lip
			value=1
		;;
		scan)
			file=$dir/device/scsi_host/$host/scan
			value="- - -"
	esac

	if [ -w "$file" ]
	then
		echo $value > $file
	elif [ -f "$file" ]
	then
		echo "$cmdname: no permission to $cmd $hba" 1>&2
		exit 2
	else
		echo "$cmdname: $cmd not supported for $hba" 1>&2
		exit 2
	fi
}

load_mod()
{
	if [ ! -d $fcoe_dir ]
	then
		modprobe fcoe
		echo "$cmdname: loading fcoe module" >&2
		sleep 1
		if [ ! -d $fcoe_dir ]
		then
			echo "$cmdname: $fcoe_dir not found" >&2
			exit 2
		fi
	fi
}

#
# Start of main script code.
#
scsi_hbas=
if [ -d "$sdir" ]
then
	scsi_hbas=`ls $sdir 2>/dev/null`
fi
if [ -d "$fdir" ]
then
	# sort the list of HBAs by the number after the 't' in "host9"
	all_hbas=`ls $fdir 2>/dev/null | sort -n -tt -k2`
fi
hbas="$all_hbas"

if [ $# -lt 1 ]
then
	hba_list
	exit 0
fi

if [ $# -lt 2 ]
then
	cmd=$1
	hba_spec=no
else
	cmd=$1
	hba=$2
	shift
	if [ $# -eq 0 ]
	then
		hba_spec=n
	elif [ $# -eq 1 -a "$1" = all ]
	then
		hba_spec=y
	else
		hba_spec=y
		hbas="$@"
		scsi_hbas="$@"
	fi
fi

hba_required()
{
	if [ "$hba_spec" != y ]
	then
		echo "$cmdname: $cmd requires HBA name" >&2
		exit 2
	fi
}

case "$cmd" in
	create | enable | en)
		hba_required
		load_mod
		repeat "fcoe_ctl create" $hbas
		;;
	create-vn)
		hba_required
		load_mod
		repeat "fcoe_ctl create_vn2vn" $hbas
		;;
	delete | del | destroy)
		if [ ! -d $fcoe_lib_dir ]
		then
			echo "$cmdname: $fcoe_lib_dir not found" >&2
			exit 2
		fi
		hba_required
		repeat "fcoe_ctl destroy" $hbas
		;;
	info)
		repeat hba_info $hbas
		;;
	list)
		hba_list
		;;
	lun*)
		repeat lun_list $hbas
		;;
	names)
		repeat echo $hbas
		;;
	stat*)
		repeat hba_stats $hbas
		;;
	realname)
		hba_name $hba
		;;
	reload)
		mods=
		for mod in fcoe fnic libfcoe libfc
		do
			if [ -d /sys/module/$mod ]
			then
				mods="$mods $mod"
			fi
		done
		rmmod $mods
		for mod in $mods
		do
			modprobe $mod
		done
		;;
	reset | scan)
		hba_required
		repeat "fc_host_ctl $cmd" $hbas
		;;
	version)
		echo $VERSION
		;;
	*)
		usage
		;;
esac
