#! /bin/bash

# Copyright (c) 2004 International Business Machines
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.#
# Author Santiago Leon <sleon@ec.ibm.com>
#
# lsdevinfo - This utility provides the HMC or IVM with name information for
# 	      virtual devices so they can be matched against the VIOS names.
#
# TODO: Show more device info
#	  (currently only the essential information is displayed)
#       Show MPIO devices
#

LSDEVINFO="lsdevinfo"
VERSION="0.1"
OFPATHNAME="/usr/sbin/ofpathname"
CAT="/bin/cat"
LS="/bin/ls"
GREP="/bin/grep"
SED="/bin/sed"
TR="/usr/bin/tr"
OD="/usr/bin/od"
CUT="/usr/bin/cut"
PSERIES_PLATFORM=$(dirname $0)/pseries_platform

# Usage statemnet
usage()
{
    echo "Usage: $LSDEVINFO [-q criteria] [-F format] [-R] [-c] [-h]"
    echo "Provide information on Virtual devices"
    echo ""
    echo "Optional arguments."
    echo "  -q criteria	     Specifies a criteria to select which devices are"
    echo "                   to be displayed."
    echo "  -F format	     Specifies the set of attributes to be displayed."
    echo "  -R		     Recursively display children of selected devices"
    echo "  -c		     Display output as a comma separated list for"
    echo "                   each device."
    echo "  -V				 Display version information and exit"
    echo "  -h				 Display this help information and exit"
    echo ""
}

show_version()
{
    echo "$LSDEVINFO: Version $VERSION"
    echo "Written by: Santiago Leon <sleon@ec.ibm.com>"
}

#
# Criteria matching boilerplate.
#
_class_eq() { [[ $class = $crit_rhs ]]; }
_class_neq() { [[ $class != $crit_rhs ]]; }
_class_like() { [[ $class =~ $crit_rhs ]]; }

_driver_eq() { [[ $driver = $crit_rhs ]]; }
_driver_neq() { [[ $driver != $crit_rhs ]]; }
_driver_like() { [[ $driver =~ $crit_rhs ]]; }

_name_eq() { [[ $name = $crit_rhs ]]; }
_name_neq() { [[ $name != $crit_rhs ]]; }
_name_like() { [[ $name =~ $crit_rhs ]]; }

_parent_eq() { [[ $parent = $crit_rhs ]]; }
_parent_neq() { [[ $parent != $crit_rhs ]]; }
_parent_like() { [[ $parent =~ $crit_rhs ]]; }

_physloc_eq() { [[ $physloc = $crit_rhs ]]; }
_physloc_neq() { [[ $physloc != $crit_rhs ]]; }
_physloc_like() { [[ $physloc =~ $crit_rhs ]]; }

_prefix_eq() { [[ $prefix = $crit_rhs ]]; }
_prefix_neq() { [[ $prefix != $crit_rhs ]]; }
_prefix_like() { [[ $prefix =~ $crit_rhs ]]; }

_status_eq() { [[ $status = $crit_rhs ]]; }
_status_neq() { [[ $status != $crit_rhs ]]; }
_status_like() { [[ $status =~ $crit_rhs ]]; }

_subclass_eq() { [[ $subclass = $crit_rhs ]]; }
_subclass_neq() { [[ $subclass != $crit_rhs ]]; }
_subclass_like() { [[ $subclass =~ $crit_rhs ]]; }

_type_eq() { [[ $type = $crit_rhs ]]; }
_type_neq() { [[ $type != $crit_rhs ]]; }
_type_like() { [[ $type =~ $crit_rhs ]]; }

_uniquetype_eq() { [[ $uniquetype = $crit_rhs ]]; }
_uniquetype_neq() { [[ $uniquetype != $crit_rhs ]]; }
_uniquetype_like() { [[ $uniquetype =~ $crit_rhs ]]; }

# Check if the attribute we're filtering on appears in the string
# given as argument.
criteria_is_relevant()
{
    [[ "$1" =~ "$crit_lhs" ]]
}

# Run the criteria-matching function.
criteria_matches()
{
    $criteria_checker
}

# Select a criteria-matching function based on the $criteria string.
parse_criteria()
{
    if [[ $criteria =~ "!=" ]] ; then
        crit_lhs=$(echo $criteria | $SED -e "s/[ ]*!=.*//")
        crit_rhs=$(echo $criteria | $SED -e "s/.*!=[ ]*//")
	case "$crit_lhs" in
	    class) criteria_checker=_class_neq;;
	    driver) criteria_checker=_driver_neq;;
	    name) criteria_checker=_name_neq;;
	    parent) criteria_checker=_parent_neq;;
	    physloc) criteria_checker=_physloc_neq;;
	    prefix) criteria_checker=_prefix_neq;;
	    status) criteria_checker=_status_neq;;
	    subclass) criteria_checker=_subclass_neq;;
	    type) criteria_checker=_type_neq;;
	    uniquetype) criteria_checker=_uniquetype_neq;;
	    *) criteria_checker=false;;
	esac
    elif [[ $criteria =~ "=" ]]; then
        crit_lhs=$(echo $criteria | $SED -e "s/[ ]*=.*//")
        crit_rhs=$(echo $criteria | $SED -e "s/.*=[ ]*//")
	case "$crit_lhs" in
	    class) criteria_checker=_class_eq;;
	    driver) criteria_checker=_driver_eq;;
	    name) criteria_checker=_name_eq;;
	    parent) criteria_checker=_parent_eq;;
	    physloc) criteria_checker=_physloc_eq;;
	    prefix) criteria_checker=_prefix_eq;;
	    status) criteria_checker=_status_eq;;
	    subclass) criteria_checker=_subclass_eq;;
	    type) criteria_checker=_type_eq;;
	    uniquetype) criteria_checker=_uniquetype_eq;;
	    *) criteria_checker=false;;
	esac
    elif [[ $criteria =~ " LIKE " ]]; then
        crit_lhs=$(echo $criteria | $SED -e "s/[ ]*LIKE.*//")
        crit_rhs=$(echo $criteria | $SED -e "s/.*LIKE[ ]*//")
	case "$crit_lhs" in
	    class) criteria_checker=_class_like;;
	    driver) criteria_checker=_driver_like;;
	    name) criteria_checker=_name_like;;
	    parent) criteria_checker=_parent_like;;
	    physloc) criteria_checker=_physloc_like;;
	    prefix) criteria_checker=_prefix_like;;
	    status) criteria_checker=_status_like;;
	    subclass) criteria_checker=_subclass_like;;
	    type) criteria_checker=_type_like;;
	    uniquetype) criteria_checker=_uniquetype_like;;
	    *) criteria_checker=false;;
	esac
    else
        echo "Criteria must have =, !=, or LIKE operand. Exiting."
        exit 1
    fi
}

# print_attr
# Prints the attribute in the first parameter if the name of the attribute is
# in the argument of the -F command line parameter. 
#
print_attr ()
{
    attr=$1
    attr_val=${!attr}

    if [[ $format == "" || $format =~ $attr ]]; then
	echo -ne $separator$begin$attr=\"$attr_val\"
    fi
}

#
# Main
#

# default: CR separated list
comma_sep=0

# default: non recursive
recursive=0

# default: display all devices
criteria=""
criteria_checker=:
crit_lhs=""

# default: display all attributes
format=""

. $PSERIES_PLATFORM
if [[ $platform != $PLATFORM_PSERIES_LPAR ]]; then
     echo "$LSDEVINFO: is not supported on the $platform_name platform"
     exit 1
fi

while getopts "cRq:F:Vh" flag ; do
    case "$flag" in
        c) comma_sep=1;;

        R) recursive=1;;

        q) criteria=$OPTARG;;

        F) format=$OPTARG;;
 
        V) show_version
                        exit 0 ;;

        h)              usage
                        exit 0 ;;
        \?)             usage
                        exit 1 ;;
        :)              echo "Option -$OPTARG requires an argument."
                        exit 1 ;;
    esac
done

# Criteria can't have conjunctions (by the spec)
if [[ $criteria =~ " AND " ]] ; then
    echo "AND conjunction not supported. Exiting"
    exit 1
fi

# If we have a criteria string, parse it and choose a criteria
# matching function.
if [[ -n "$criteria" ]]; then
    parse_criteria
fi

# Fill variables for the two display formats (regular and comma-separated) so
# we can print the output in a single place.
if [[ $comma_sep -eq 0 ]]; then
    dev_begin="device:\n"
    separator="\n"
    begin="\t"
    dev_end="\n\n"
    path_begin="\n\npath:\n\tparent="
    path_end="" 
else
    dev_begin=""
    separator=","
    dev_end="\n"
    path_begin=",path=(parent="
    path_end=")"
fi

show_eth ()
{
    # if there is a criteria in the command line, check if this device matches
    if [[ $criteria != "" ]] ; then
        show=0
        attrs="name physloc uniquetype class subclass type prefix driver status"
        if criteria_is_relevant "$attrs" && criteria_matches; then
            show=1
        fi
    fi

    # print the info only if the device matches the criteria
    if [[ $show -ne 0 ]]; then
	# the name attribute is always printed
	echo -ne $dev_begin$begin"name="\"$name\"

	print_attr "uniquetype"
	print_attr "class"
	print_attr "subclass"
	print_attr "type"
	print_attr "prefix"
	print_attr "driver"
	print_attr "status"

	# if there is no format in the command line or it contains "path", then
	# print the path. Doesn't use print_attr because all of the fields in 
	# the path attribute should be printed.
	if [[ $format == "" || $format =~ "path" ]]; then
	    echo -ne $path_begin\"$parent\"
	    echo -ne $separator$begin"physloc="\"$physloc\"
	    echo -ne $separator$begin"connection="\"$connection\"
	    echo -ne $path_end
	fi
	# done with this device
	echo -ne $dev_end
    fi
}

# Look at every vNIC device
for dev in $($LS -d /proc/device-tree/vdevice/vnic* 2> /dev/null); do
    # use ofpathname to get the device name (i.e. eth0)
    name=$($OFPATHNAME -l $(echo $dev | $SED -e "s/\/proc\/device-tree//") 2> /dev/null)
    connection=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/l-lan@//")
    parent="vio"

    # get the physical location
    physloc=$(tr -d '\0' < $dev/ibm,loc-code)
    uniquetype="adapter/vdevice/IBM,vnic"
    class="adapter"
    subclass="vdevice"
    type="IBM,vnic"
    prefix="eth"
    driver="ibmvnic"
    status=1

    show=1
    show_eth
done

# Look at every ibmveth (Virtual Ethernet) device 
for dev in $($LS -d /proc/device-tree/vdevice/l-lan* 2> /dev/null); do 
    # use ofpathname to get the device name (i.e. eth0)
    name=$($OFPATHNAME -l $(echo $dev | $SED -e "s/\/proc\/device-tree//") 2> /dev/null)
    connection=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/l-lan@//")
    parent="vio"

    # get the physical location 
    physloc=$(tr -d '\0' < $dev/ibm,loc-code)
    uniquetype="adapter/vdevice/IBM,l-lan"
    class="adapter"
    subclass="vdevice"
    type="IBM,l-lan"
    prefix="eth"
    driver="ibmveth"
    status=1

    show=1
    show_eth
done

# Look for PCI ethernet devices
for pci_dev in $($LS -d /proc/device-tree/pci* 2> /dev/null); do 
    for dev in $($LS -d $pci_dev/ethernet* 2> /dev/null); do 
	# use ofpathname to get the device name (i.e. eth0)
	name=$($OFPATHNAME -l $(echo $dev | $SED -e "s/\/proc\/device-tree//") 2> /dev/null)
	connection=$(echo $pci_dev | $SED -e "s/\/proc\/device-tree\/pci@//")
	parent="pci"

	# get the physical location 
	physloc=$(tr -d '\0' < $dev/ibm,loc-code)
	type="$($OD -t x2 $dev/vendor-id $dev/device-id |
		$CUT -f3,5 -d ' ' -s --output-delimiter='' | tr -d '\0')"
	uniquetype="adapter/pci/$type"
	class="adapter"
	subclass="pci"
	prefix="eth"
	driver=$($LS -l /sys/class/net/$name/device/driver 2> /dev/null |
		 $SED -e "s/^.*\///")
	status=1

	show=1
	show_eth
    done
done


# Look at every ibmvscsi (Virtual SCSI) device
for dev in $($LS -d /proc/device-tree/vdevice/v-scsi* 2> /dev/null) ; do
    # pull the physical location
    physloc=$(tr -d '\0' < $dev/ibm,loc-code)
    hostphysloc=$physloc
    connection=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/v-scsi@//")

    # find the slot so it can be used in sysfs
    slot=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/v-scsi@//")

    # there is only one host per device, assign it to the path's name
    for host in $($LS -d /sys/devices/vio/$slot/host*) ; do
	parent=$(echo $host | $SED -e "s/.*\///")
	name=$parent

	uniquetype="adapter/vdevice/IBM,v-scsi"
	class="adapter"
	subclass="vdevice"
	type="IBM,v-scsi"
	prefix="host"
	driver="ibmvscsic"

	host=$($LS -d /sys/devices/vio/$slot/host*/ 2> /dev/null)
	if [[ -d $host/scsi_host ]]; then
	     scsihost=$($LS -d $host/scsi_host/host*/ 2> /dev/null)
	else
	     scsihost=$($LS -d $host/scsi_host*/ 2> /dev/null)
	fi

	if [[ $(cat $scsihost/state) == "running" ]] ; then
            status=1
	else
	    status=0
	fi

	show=1
	# if there is a criteria in the command line, check if this
	# device matches
	if [[ $criteria != "" ]] ; then
	    show=0
	    attrs="name physloc status uniquetype class subclass type prefix driver"
	    if criteria_is_relevant "$attrs" && criteria_matches; then
		show=1
	    fi
	fi

	if [[ $show -ne 0 ]]; then
	    # the name attribute is always printed
	    echo -ne $dev_begin$begin"name="\"$name\"

	    print_attr "uniquetype"
	    print_attr "class"
	    print_attr "subclass"
	    print_attr "type"
	    print_attr "prefix"
	    print_attr "driver"
	    print_attr "status"

	    # print the path, see note for ibmveth above
	    if [[ $format == "" || $format =~ "path" ]]; then
	        echo -ne $path_begin"\"vio\""
	        echo -ne $separator$begin"connection="\"$connection\"
	        echo -ne $separator$begin"physloc="\"$physloc\"
	        echo -ne $path_end
	    fi
	    # done with this target
	    echo -ne $dev_end
        fi

	# loop through the targets for this host. 
	for t in $($LS -d $host/target* 2> /dev/null); do
	    target=$(echo $($LS -d $t/$($LS $t | $GREP -v uevent | $GREP -v power | $GREP -v subsystem)))
	    if [[ ! -d $target/block ]]; then
	         name=$(echo $($LS -d $target/block* 2> /dev/null) | $SED -e "s/.*://")
	    else
	         name=$($LS $target/block 2> /dev/null)
	    fi

	    conn=$($OFPATHNAME /dev/$name 2> /dev/null | $SED -e "s/.*disk@//")
	    connection=${conn:0:12}
	    uniquetype="disk/vscsi/vdisk"
	    class="disk"
	    subclass="vscsi"
	    type="vdisk"
	    physloc=$hostphysloc"-L"$conn

	    if [[ $(cat $target/state) == "running" ]] ; then
	         status=1
	    else
		 status=0
	    fi

	    # if there is a criteria in the command line, we are recursive and
	    # the parent passed criteria, show the device
	    if [[ $criteria != "" && $show -eq 1 && $recursive -eq 1 ]]; then
                show=1
	    elif [[ $criteria != "" ]] ; then
	        # if there is a criteria in the command line, check if this
	        # device matches
	        show=0
		attrs="name status physloc parent uniquetype class subclass type"
		if criteria_is_relevant "$attrs" && criteria_matches; then
		    show=1
		fi
	    else
	        show=1
	    fi

	    # print the info only if the device matches the criteria
	    if [[ $show -ne 0 ]]; then
		# the name attribute is always printed
		echo -ne $dev_begin$begin"name="\"$name\"

		print_attr "uniquetype"
		print_attr "class"
		print_attr "subclass"
		print_attr "type"
		print_attr "status"

		# print the path, see note for ibmveth above
		if [[ $format == "" || $format =~ "path" ]]; then
		    echo -ne $path_begin\"$parent\"
		    echo -ne $separator$begin"connection="\"$connection\"
		    echo -ne $separator$begin"physloc="\"$physloc\"
		    echo -ne $separator$begin"path_id="\""0"\"
		    echo -ne $separator$begin"path_status="\"$status\"
		    echo -ne $path_end
		fi
		# done with this target
		echo -ne $dev_end
	    fi
	done
    done
done


# Look at every ibmvfc (Virtual FibreChannel) device
for dev in $($LS -d /proc/device-tree/vdevice/vfc-client* 2> /dev/null) ; do
    # pull the physical location
    physloc=$(tr -d '\0' < $dev/ibm,loc-code)
    connection=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/vfc-client@//")
    hostphysloc=$physloc

    # find the slot so it can be used in sysfs
    slot=$(echo $dev | $SED -e "s/\/proc\/device-tree\/vdevice\/vfc-client@//")

    # there is only one host per device, assign it to the path's name
    for host in $($LS -d /sys/devices/vio/$slot/host* 2> /dev/null) ; do
	parent=$(echo $host | $SED -e "s/.*\///")
	name=$parent

	uniquetype="adapter/vdevice/IBM,vfc-client"
	class="adapter"
	subclass="vdevice"
	type="IBM,vfc-client"
	prefix="host"
	driver="ibmvfc"

	host=$($LS -d /sys/devices/vio/$slot/host*/ 2> /dev/null)
	if [[ -d $host/scsi_host ]]; then
	     scsihost=$($LS -d $host/scsi_host/host*/ 2> /dev/null)
	else
	     scsihost=$($LS -d $host/scsi_host*/ 2> /dev/null)
	fi

	if [[ $(cat $scsihost/state) == "running" ]] ; then
            status=1
	else
	    status=0
	fi

	show=1
	# if there is a criteria in the command line, check if this
	# device matches
	if [[ $criteria != "" ]] ; then
	    show=0
	    attrs="name physloc status uniquetype class subclass type prefix driver"
	    if criteria_is_relevant "$attrs" && criteria_matches; then
		show=1
	    fi
	fi

	if [[ $show -ne 0 ]]; then
	    # the name attribute is always printed
	    echo -ne $dev_begin$begin"name="\"$name\"

	    print_attr "uniquetype"
	    print_attr "class"
	    print_attr "subclass"
	    print_attr "type"
	    print_attr "prefix"
	    print_attr "driver"
	    print_attr "status"

	    # print the path, see note for ibmveth above
	    if [[ $format == "" || $format =~ "path" ]]; then
	        echo -ne $path_begin"\"vio\""
	        echo -ne $separator$begin"connection="\"$connection\"
	        echo -ne $separator$begin"physloc="\"$physloc\"
	        echo -ne $path_end
	    fi
	    # done with this target
	    echo -ne $dev_end
        fi

	# As opposed to ibmvscsi, there are multiple rports in each host
	for rport in $($LS -d $host/rport* 2> /dev/null); do

	    # in ibmvfc there are two layers of directories before getting to
	    # the targets
	    for t in $($LS -d $rport/target* 2> /dev/null); do
	        for target in $($LS $t | $GREP "[0-9]*:[0-9]*:[0-9]*:[0-9]*"); do
		    if [[ ! -d $t/$target/block ]]; then
			 name=$(echo $($LS -d $t/$target/block*) | $SED -e "s/.*://")
		    else
			 name=$($LS $t/$target/block 2> /dev/null)
		    fi

		    connection=$($OFPATHNAME /dev/$name 2> /dev/null | $SED -e "s/.*disk@//")
		    physloc=$hostphysloc"-W"$(echo $connection | $TR "[:lower:]" "[:upper:]" | $SED -e "s/,/-L/")
		    uniquetype="disk/fcp/disk"
		    class="disk"
		    subclass="fcp"
		    type="disk"

		    if [[ $(cat $t/$target/state) == "running" ]] ; then
			 status=1
		    else
			 status=0
		    fi

		    # if there is a criteria in the command line, we are recursive and
		    # the parent passed criteria, show the device
		    if [[ $criteria != "" && $show -eq 1 && $recursive -eq 1 ]]; then
                        show=1
		    elif [[ $criteria != "" ]] ; then
		    # if there is a criteria in the command line, check if this
		    # device matches
                        show=0
			attrs="name physloc status parent uniquetype class subclass type"
			if criteria_is_relevant "$attrs" && criteria_matches; then
			    show=1
			fi
		    else
                        show=1
		    fi

		    # print the info only if the device matches the criteria
		    if [[ $show -ne 0 ]]; then
			# the name attribute is always printed
			echo -ne $dev_begin$begin"name="\"$name\"

			print_attr "uniquetype"
			print_attr "class"
			print_attr "subclass"
			print_attr "type"
			print_attr "status"

			# print the path, see note for ibmveth above
			if [[ $format == "" || $format =~ "path" ]]; then
			    echo -ne $path_begin\"$parent\"
		    	    echo -ne $separator$begin"connection="\"$connection\"
		    	    echo -ne $separator$begin"physloc="\"$physloc\"
		    	    echo -ne $separator$begin"path_id="\""0"\"
		    	    echo -ne $separator$begin"path_status="\"$status\"
			    echo -ne $path_end
			fi
			# done with this device
			echo -ne $dev_end
		    fi
		done
	    done
	done
    done
done

exit 0

# end
