#!/bin/sh
#
#/*******************************************************************
# * This file is part of the Emulex Linux Device Driver for         *
# * Fibre Channel Host Bus Adapters.                                *
# * Copyright (C) 2003-2010 Emulex.  All rights reserved.           *
# * EMULEX and SLI are trademarks of Emulex.                        *
# * www.emulex.com                                                  *
# *                                                                 *
# * This program is free software; you can redistribute it and/or   *
# * modify it under the terms of version 2 of the GNU General       *
# * Public License as published by the Free Software Foundation.    *
# * This program is distributed in the hope that it will be useful. *
# * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND          *
# * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,  *
# * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE      *
# * DISCLAIMED, EXCEPT TO THE EXTENT THAT SUCH DISCLAIMERS ARE HELD *
# * TO BE LEGALLY INVALID.  See the GNU General Public License for  *
# * more details, a copy of which can be found in the file COPYING  *
# * included with this package.                                     *
# *******************************************************************/
#
# scripts/lun_scan (VERSION variable below)
# This script is provided by Emulex to use with its 7.x and 8.x linux device
# drivers for Light Pulse Fibre Channel adapters.
#
# This script allows a user to peform one of more of the following actions,
# for specified SCSI Hosts/LPFC HBAs:
#    - Scan for new devices
#    - Resize existing LUNs
#    - Delete LUNs that are offline/deleted.
#
# Usage:
#
# lun_scan.sh  [-h|--help] [SCAN_OPTIONS] [ <#> [<#>] | all ]"
#
#  where:
#    -h, --help     : Prints this usage message
#    <#>            : Scsi host number of a specific LPFC HBA for which an
#                     operation is to be applied.
#
#    All            : Indicates that operation should be applied to all hosts.
#
#    [SCAN_OPTIONS] could be one or more of:
#      -l, --lip    : Reinitialize Fibre Channel link before scanning
#
#      -s, --scan   : Scan for new devices
#
#      -r, --resize : Resize LUNs
#
#      -d, --delete : Delete LUNs that are offline/deleted
#
# Note: If no [SCAN_OPTIONS] are specified, the default options are
#       --delete, --resize followed by --scan.
#       If one or more [SCAN_OPTIONS] are specified only those options
#       will be performed.
#
# Warning: Scanning an adapter while performing a tape backup should
#         be avoided.
#
# Examples:
#
#  lun_scan.sh all     : Scans all LPFC HBAs."
#  lun_scan.sh 2 4     : Scans the LPFC HBAs with scsi host number 2 and 4.
#  lun_scan.sh -s 2    : Scans only for new LUNs the LPFC HBA with scsi
#                        host number 2."
#  lun_scan.sh -s -d 2 : Scans for new LUNs and remove LUNS that have
#                        been deleted in the LPFC HBA with scsi host number 2.
#  lun_scan.sh -l 1    : Reinitialize FC link before scanning the LPFC
#                        HBA with scsi host number 1.
#  lun_scan.sh -s -d 2 : Scans for new LUNs and remove LUNS that have been
#                        deleted in the LPFC HBA with scsi host number 2.
#
#############################################################################

VERSION="1.3"
OS_REV=`uname -r | cut -c1-3`
ELX_SCAN=$(basename $0)

err_msg () {
    msg_line="$1"
    cat<<EOF
$0: ${msg_line}
EOF
    exit 1
}

usage()
{
 echo ""
 echo "Emulex Linux LUN Scanning Application - Version $VERSION"
 echo ""
 echo "Usage: ${ELX_SCAN} [-h|--help] [SCAN_OPTIONS] [ <#> [<#>] | all ]"
 echo ""
 echo "  where:"
 echo ""
 echo "    -h, --help  : Prints this usage message"
 echo "    <#>         : Scsi host number of a specific LPFC HBA that is to be"
 echo "                  scanned. More than one host number can be specified."
 echo "    all         : Indicates that all LPFC HBAs are to be scanned."
 echo ""
 echo "  [SCAN_OPTIONS] is one or more of:"
 echo "    -l, --lip    : Reinitialize Fibre Channel link before scanning"
 echo "    -s, --scan   : Scan for new devices"
 echo "    -r, --resize : Resize LUNs"
 echo "    -d, --delete : Delete LUNs that are offline/deleted"
 echo ""
 echo "  Note: If no [SCAN_OPTIONS] are specified, the default options are"
 echo "        --delete, --resize followed by --scan."
 echo "        If one or more [SCAN_OPTIONS] are specified only those options"
 echo "        will be performed."
 echo ""
 echo "  Warning: Scanning an adapter while performing a tape backup should"
 echo "           be avoided."
 echo ""
}

input_is_valid () {
    INPUT=$*
    GOOD=0
    BAD=1
    DETERMINATION=$GOOD
    ALL_TEST=$(echo $INPUT| tr 'a-z' 'A-Z')
    if [ "${ALL_TEST}" == "ALL" ] ; then
	ALL_HOSTS=1
    else
	check=$(echo ${INPUT} | grep ^[0-9' '] )
	if [ "$?" -ne 0 ] ; then
	    DETERMINATION=$BAD
	else
	    check=$(echo $INPUT|grep  [a-zA-Z])
	    if [ "$?" -ne 1 ] ; then
		DETERMINATION=$BAD
	    fi
	fi
    fi
    return $DETERMINATION
}

usage_exit () {
    echo "Invalid argument $1"
    usage
    exit 1
}

ALL_HOSTS=0
OPTIONS_USED=0
DELETE_SPECIFIED=0
RESIZE_SPECIFIED=0
SCAN_SPECIFIED=0
LIP_SPECIFIED=0
HELP_ONLY=0

hostslist=

while [ $# -gt 0 ]; do
    case $1 in
	-d|--delete)
	    OPTIONS_USED=1
	    DELETE_SPECIFIED=1
	    shift
	    ;;
	-r|--resize)
	    OPTIONS_USED=1
	    RESIZE_SPECIFIED=1
	    shift
	    ;;
	-s|--scan)
	    OPTIONS_USED=1
	    SCAN_SPECIFIED=1
	    shift
	    ;;
	-l|--lip)
	    OPTIONS_USED=1
	    LIP_SPECIFIED=1
	    shift
	    ;;
	-h|--help)
	    OPTIONS_USED=1
	    HELP_ONLY=1
	    shift
	    ;;
	all)
	    ALL_HOSTS=1
	    shift
	    ;;
	[0-9]*)
	    hostslist="$hostslist $1"
	    shift
	    ;;

 	*)
 	    hostslist="$hostslist $1"
             input_is_valid $hostslist
 	    if [ $? -ne 0 ] ; then
 		echo "Please specify a valid host number"
 		usage_exit $1
 	    else
 		shift
 	    fi
 	    ;;

    esac
done

if [ ${HELP_ONLY} -eq 1 ] ; then
    usage
    exit 0
fi

# if no options were specified then enable all operations except LIP.
if [ ${OPTIONS_USED} -eq 0 ] ; then
    DELETE_SPECIFIED=1
    RESIZE_SPECIFIED=1
    SCAN_SPECIFIED=1
    LIP_SPECIFIED=0
fi

if [ -n "${hostslist}" ] ; then
    input_is_valid $hostslist ||\
	err_msg "Please specify a valid host number"
    hosts=($hostslist)
else
    ALL_HOSTS=1
fi


abort_exit() {
	echo ""
	echo "Error: Cannot find an lpfc HBA instance with scsi host number : $host"
	echo "... Aborting lun_scan."
	echo ""
	exit 2
}

get_hosts_list () {
    if [ ${OS_REV} = 2.4 ]; then
	lowhost=0
	ha=`ls /proc/scsi/lpfc/?   2> /dev/null | cut -f5 -d'/'`
	hb=`ls /proc/scsi/lpfc/??  2> /dev/null | cut -f5 -d'/'`
	hc=`ls /proc/scsi/lpfc/??? 2> /dev/null | cut -f5 -d'/'`
	all_hosts="$ha $hb $hc"
	lowhost=`echo $all_hosts | sed "s/ .*//"`
    else
	ha=`ls -1 -d /sys/bus/pci/drivers/lpfc/*/host* 2>/dev/null | sed -e "s/.*host//"`
 	hb=`ls -1 -d /sys/bus/pci/drivers/lpfc/*/host*/host* 2>/dev/null | sed -e "s/.*host//"`
 	hc=`ls -1 -d /sys/bus/pci/drivers/lpfc/*/host*/vport*/host* 2>/dev/null | sed -e "s/.*host//"`
	all_hosts="$ha $hb $hc"
    fi
}

send_lip_command() {
cat<<EOF
         Re-initializing Fiber Channel link for host$host
EOF
    if [ -e /sys/class/fc_host/host$host/issue_lip ] ; then
		# new issue_lip location used on RHEL 5 & SLES 10
	echo 1 > /sys/class/fc_host/host$host/issue_lip
    elif [ -e /sys/class/scsi_host/host$host/issue_lip ] ; then
	echo 1 > /sys/class/scsi_host/host$host/issue_lip
    fi
	#Do nothing if issue_lip doesn't exist, i.e. host is a vport

    return $?
}

scan_hosts() {

if [ ${OS_REV} = 2.4 ]; then

    for host in $hosts ; do
	# Convert host number to lpfc instance number.
	instance=`expr $host - $lowhost`

	if [ ! -e /proc/scsi/lpfc/$host ] ; then
	    abort_exit
	fi

	echo Scanning lpfc$instance, scsi host number : $host;

	max_lun=256

	targets=`cat /proc/scsi/lpfc/$host | grep 'lpfc*t*' | cut -f2 -d't' | cut -f1 -d' '`

	for target in $targets ; do
	    # Convert target ID to decimal from Hex format
	    let "dec_target = 0x$target"
	    echo "   Scanning Target Id $dec_target..."
	    for ((lun=0; lun<$max_lun; lun++)) ; do
		echo "scsi add-single-device $host 0 $dec_target $lun" >/proc/scsi/scsi
	    done
	done
    done
else

    for host in ${hosts[@]} ; do
	let item=0
	if [ ! -e /sys/bus/pci/drivers/lpfc/*/host$host ] &&
	   [ ! -e /sys/bus/pci/drivers/lpfc/*/host*/host$host ] &&
	   [ ! -e /sys/bus/pci/drivers/lpfc/*/host*/vport*/host$host ] ; then
	    abort_exit
	fi
	if [ "${LIP_SPECIFIED}" -eq 1 ] ; then
	    send_lip_command
	fi
	if [ "${DELETE_SPECIFIED}" -eq 1 ] || [ "${RESIZE_SPECIFIED}" -eq 1 ] ; then

	    which sg_turs > /dev/null 2>&1
	    if [ $? -ne 0 ] ; then
		echo "Missing sg3_utils - unable to detect removed target LUNs"
	    else
		# Determine if sg_turs accepts -v switch
		sg_turs -v 2>&1 | grep -q "Unrecognized switch"
		if [ $? -eq 0 ] ; then
		    # grep found the "Unrecognized switch" error message
		    SG_TURS="sg_turs"
		else
		    SG_TURS="sg_turs -v"
		fi
                echo "Searching for :"
		if [ "${DELETE_SPECIFIED}" -eq 1 ] ; then
		    let item+=1
		    echo -e "\t $item. Target LUNs to remove on scsi host number : $host"
		fi
		if [ "${RESIZE_SPECIFIED}" -eq 1 ] ; then
		    let item+=1
		    echo -e " \t $item. Target LUNs to resize on scsi host number : $host"
		fi
		# send SCSI TUR to each device on this host
		cd /sys/class/scsi_host/host$host
		# device paths for RHEL 5 and SLES 10
		device_dirs="device/rport-*/target*/$host:0:*"
		# device paths for RHEL 4
		device_dirs="${device_dirs} device/target*/$host:0:*"
		# device paths for SLES 9
		device_dirs="${device_dirs} device/$host:0:*"


		      for id in $device_dirs ; do
			    if [ -e $id/generic ] ; then
				# number of retries for Unit Attention
				ua_retries_left=3
				sg_link=$(ls -d $id/generic)
				sg_device=$(readlink $sg_link)
				sg_device=${sg_device##*/}
				block_link=$(ls -d $id/block*)
				block_link=$(readlink $block_link)
				sd_device=${block_link##*/}
				while [ $ua_retries_left -gt 0 ] ; do
				    tur_output=$($SG_TURS /dev/$sg_device 2>&1)
				    case $tur_output in
					*Unit\ Attention*)
					    let ua_retries_left=(ua_retries_left - 1)
					    if [ $ua_retries_left -gt 0 ] ; then
						echo -n "Device $sd_device returned Unit Attention"
						echo ", retrying $ua_retries_left more times"
						# delay for target
						sleep 1
					    else
						# that was the last retry
						echo -n "Device $sd_device unresolved Unit Attention"
						echo ", skipping"
					    fi
					    ;;
					*Illegal\ Request*)
					    # only retry if we get Unit Attention back
					    ua_retries_left=0
					    if [ "${DELETE_SPECIFIED}" -eq 1 ] ; then
						echo "removing $sd_device (no longer available)"
						echo 1 > $id/generic/device/delete
					    fi
					    ;;
					*DID_NO_CONNECT*)
					    # only retry if we get Unit Attention back
					    ua_retries_left=0
					    # Temporary path down - leave it alone
					    ;;
					*)
					    # only retry if we get Unit Attention back
					    ua_retries_left=0
					    # Check if this LUN has been resized
					    if [ "${RESIZE_SPECIFIED}" -eq 1 ] ; then
						if [ -n "$sd_device" ]; then
						    echo 1 > /sys/block/$sd_device/device/rescan
						fi
					    fi
					    ;;
				    esac
				done
			    fi
		      done
	     fi
	  fi
	      # Perform SCAN Operation only if it is allowable
	  if [ "${SCAN_SPECIFIED}" -eq 1 ] ; then
	      echo Scanning lpfc HBA instance with scsi host number : $host
	      echo '- - -' > /sys/class/scsi_host/host$host/scan   || \
		  err_msg "Unable to scan for host $host"
	  fi
      done
    echo " "
fi
}


if  [ ${ALL_HOSTS} -eq 1 ] ; then
    get_hosts_list
    hosts=($all_hosts)
else
    hosts=($hostslist)
fi

scan_hosts
