#!/bin/bash
#
# Name: probe-luns
# Copyright: (c)2002-2005 Hewlett-Packard Company
#
# Description: forces a rescan at certain connection addresses
#              by the scsi mid-layer
#
# CD		04/21/04 Search for targets by keying off of the
#                        "target" string only
# CD		05/17/04 put a delay in between each add/remove scsi
#                        command so as not to panic the kenrel
# CD 		05/19/04 changed sleep to usleep so that the delay
#                        works on both Red Hat and SUSE
# CD		06/11/04 Changes for SLES 9 (2.6 kernel)
# CD		02/01/05 Added command line options to just do
#                        remove-single-device and add-single-device
# CD		02/04/05 Fixed bug where garbage string was being sent
#                        to /proc/scsi/scsi during scan; Don't scan
#                        root device
# CD		03/23/05 Do not execute "scsi remove-single-device" if
#                        we are running on RHEL 4 (messes up LVM)
# CD		05/04/05 Do not execute "scsi remove-single-device" for
#                        any 2.6 distribution.  Executing it deallocates
#                        the memory for the device thereby possibly causing
#                        an application to try to read/write to a non-
#                        existent device
# CD		05/11/05 Fix grep typos
# CD		11/14/05 Rewrote probe-luns to scan Emulex adapters as well
#                        as QLogic adapters; Added -n argument to prevent the
#                        "scsi remove-single-device" from being executed;
#			 Rewrote command line argument handling code
# CD		01/23/06 When scanning for lpfc targets, insert a 0x in front
#                        of the numbers the signify that they are hex
# CD		06/14/06 On 2.6 kernels, use the command: 
#                        echo "- - -" > /sys/class/scsi_host/hostX/scan for
#                        Emulex adapters
# CD		08/30/06 Changes for SLES 10; Fix problem printing out scsi
#                        devices from /proc/scsi/scsi for adapters only listed
#                        in /sys
# CD		09/15/06 Add code to add legacy tape devices to
#			 /proc/scsi/device_info; Fix single instance scanning
#                        on Emulex adapters on 2.6 kernels
# CD		08/26/06 Added new tape definition to add_tape_devices

# defines

LUNSTART=0
LUNEND=32
LSSD=/opt/hp/hp_fibreutils/lssd
KERNELVER=`uname -r | cut -c 1-3`
DOREMOVE=y

# This is the delay in microseconds between each echo "scsi..." command

PROBEDELAY=100000

#
# functions
#

# prints out help blurb
print_help () {
 echo "Usage: $0 -a|-i|-r|-d|-n instance"
 echo ""
 echo "-a: force SCSI midlayer to rescan all supported FC devices"
 echo "-i: force SCSI midlayer to rescan a specific supported FC device instance"
 echo "-r: force SCSI midlayer to remove all supported FC devices.  This option is"
 echo "    not supported on 2.6 kernels"
 echo "-d: force SCSI midlayer to add all supported FC devices"
 echo "-n: do not execute \"scsi remove-single-device\""
 echo "-h: print out this help menu"
 exit 1
}



# read arguments passed from the command line

read_args () {

 AFLAG=0
 IFLAG=0
 RFLAG=0
 DFLAG=0
 NFLAG=0

 if [ $# -gt 0 ]
 then
        # parse command line into arguments

        getopt ai:rdnh $* 1>/dev/null 2>/dev/null

        # check result of parsing

        if [ $? != 0 ]
        then
                echo "Bad argument or missing argument"
                exit 1
        fi

        set -- `getopt ai:rdnh $*`

        while [ $1 != -- ]
        do
                case $1 in
                        -a) AFLAG=1;;
			-i) IFLAG=1
			    SPECHBAINST=$2
			    shift;;
			-r) RFLAG=1;;
			-d) DFLAG=1;;
			-n) NFLAG=1;;
                        -h) print_help;;
                        *) echo "$1 is an illegal argument"
                           exit 1;;
                esac
                shift   # next flag
        done
 fi
}

# Name: add_tape_devices
# Description: Add definitions for legacy tape devices to
#              /proc/scsi/device_info
# In: None
# Out: None
# Returns: None

add_tape_devices () {
        echo "Adding legacy tape devices to /proc/scsi/device_info"
        echo "HP:MSL5000 Series:0x20240" > /proc/scsi/device_info
	echo "COMPAQ:MSL5000 Series:0x20240" > /proc/scsi/device_info
        echo "HP:MSL6000 Series:0x20240" > /proc/scsi/device_info
        echo "HP:ESL9000 Series:0x20240" > /proc/scsi/device_info
        echo "HP:ESL E-Series:0x20240" > /proc/scsi/device_info
}

# Scan an adapter using the scan file in sysfs

scan_sysfs () {
 INST=$1

 # If we are not a 2.6 kernel, just return

 if [ "$KERNELVER" != "2.6" ]
 then
	return
 fi
 
 # Do a sanity check to make sure that the directory 
 # exists

 if test ! -d /sys/class/scsi_host/${INST}
 then
	echo "Could not find /sys/class/scsi_host/${INST}"
	return
 fi

 # Do the scan

 echo "Scanning /sys/class/scsi_host/${INST}"
 echo "- - -" > /sys/class/scsi_host/${INST}/scan
 sleep 1
}

# Name: sysfs_elx_list
# Description: Returns a list of directories that corrospond to Emulex adapters in
#              sysfs
# In: none
# Out: List of sysfs Emulex adapters
# Returns: none

sysfs_elx_list () {
        # Directory of SCSI hosts
        SCSIHOSTDIR=/sys/class/scsi_host

        # List of hosts in scsi host directory
        HOSTLIST=`ls $SCSIHOSTDIR 2>/dev/null`

        # List of emulex adapters to return
        SYSFS_ELX_LIST=""

        # Look for lpfc host adapters.  These will be the ones with the lpfc_log_verbose
        # file

        for i in $HOSTLIST
        do
                if test -f ${SCSIHOSTDIR}/${i}/lpfc_log_verbose
                then
                        SYSFS_ELX_LIST=$SYSFS_ELX_LIST" $SCSIHOSTDIR/${i}"
                fi
        done
}
 
# produces a list of adapter instances
# outputs variable ADAPTERS

adapter_inst () {
 # find QLogic adapters
 ADAPTERS=`find /proc/scsi | grep /[0-9/].* | grep qla | sort -u`
 
 # find Emulex adapters; the methods for scanning for Emulex adapters are
 # different depending on what kernel is being used

 if [ "$KERNELVER" = "2.6" ]
 then
	sysfs_elx_list
	ADAPTERS=$ADAPTERS" "$SYSFS_ELX_LIST
 else
 	ADAPTERS=$ADAPTERS" `find /proc/scsi | grep /[0-9/].* | grep lpfc | sort -u`"
 fi
}


# determine root devices
# output variabel ROOTINST

determine_root_inst () {

 # determine root device from mounted filesystems
 ROOTDEV=`mount | grep "on / " | awk '{print $1}' | sed 's/\/dev\///g' | sed 's/[0-9/]//g'`

 # determine root device scsi instance (if there is one) using lssd
 if [ "`echo $ROOTDEV | grep sd`" != "" ]
 then
 	ROOTINST=`$LSSD | grep $ROOTDEV | awk '{print $2}' | sed 's/,/ /g'`
 else
 	ROOTINST=""
 fi
}

# This function determines the list of targets for a particular SCSI host
# instance that is passed in as a paramter.  The fuction outputs the
# TARGETS variable with the numerical list of targets

get_targets () {

 HBAINST=$1

 # If we were fed an empty string, then set TARGETS to NULL and
 # simply return

 if [ "$HBAINST" = "" ]
 then
	TARGETS=""
	return
 fi

 if [ "`echo $HBAINST | grep qla`" != "" ]
 then
	# We are a QLogic adapter
	TARGETS=`cat $HBAINST | grep target | grep -v adapter | awk 'BEGIN {FS="="} {print $1}' | awk 'BEGIN {FS="-"} {print $4}'`
	return
 elif [ "`echo $HBAINST | grep lpfc`" != "" ]
 then
	# We are an Emulex adapter
	TMPVAR=`cat $HBAINST | grep lpfc |  awk '{print $1}' | awk 'BEGIN {FS="t"} {print $2}'`
	TARGETS=""
	for x in $TMPVAR
	do
		TARGETS=$TARGETS"`printf "%d" 0x$x` "
	done
	return
 else
	# We are not a supported fibre channel adapter to return NULL in TARGETS
	TARGETS=""
 fi
}


# forces a rescan of the SCSI midlayer for all qla* adapters

scan_all () {

 # get root device scsi address (if one exists)

 determine_root_inst

 # get a list of adapters in the system

 adapter_inst

 # perform midlayer removal

 # do not perform "scsi remove-single-device" if we are RHEL 4
 if [ "$DOREMOVE" != "n" ]
 then
	for i in $ADAPTERS
 	do
		# get list of targets

		get_targets $i
		INST=`basename $i`

		for j in $TARGETS
		do
			CNT=$LUNSTART

			# send scsi remove command to midlayer
			while [ $CNT -le $LUNEND ]
			do
				# do not perform scsi command on root device
				if [ "$ROOTINST" != "$INST 0 $j $CNT" ]
				then
					printf "\rRemoving $i, target $j, LUN $CNT      "
					echo "scsi remove-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
					usleep $PROBEDELAY
				fi
				CNT=`expr $CNT + 1`
			done
		done
 		echo ""
	done
 fi

 # perform midlayer scanning

 for i in $ADAPTERS
 do
	if [ "${KERNELVER}" = "2.6" ] && [ "`echo $i | grep /sys/class/scsi_host`" != "" ]
	then
		#
		# We currently only to this if we are Emulex and 2.6

		INST=`basename $i`
		scan_sysfs $INST
	else
		# get list of targets

        	get_targets $i
		INST=`basename $i`

		for j in $TARGETS
		do
			CNT=$LUNSTART

			# send scsi add command to midlayer

			while [ $CNT -le $LUNEND ]
			do
				# do not perform scsi command on root device!
                        	if [ "$ROOTINST" != "$INST 0 $j $CNT" ]
				then
					printf "\rScanning $i, target $j, LUN $CNT      "
					echo "scsi add-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
					usleep $PROBEDELAY
				fi
				CNT=`expr $CNT + 1`
			done
		done
		echo ""
	fi
 done

 print_scsi
}

# scan a particular adapter instance

scan_instance () {
 INST=$1

 # determine root device scsi address (if one exists)

 determine_root_inst

 # verify that instance exists. First by scanning /proc and then by scanning
 # /sys

 if [ "$KERNELVER" = "2.4" ]
 then
 	DIRS="/proc/scsi/qla2200 /proc/scsi/qla2300 /proc/scsi/lpfc"
 elif [ "$KERNELVER" = "2.6" ]
 then
	DIRS="/proc/scsi/qla2xxx"
 else
	DIRS=""
 fi

 FOUND=n

 for i in $DIRS
 do
	find ${i}/${INST} 1>/dev/null 2>/dev/null

  	if [ $? -eq 0 ]
  	then
   		FOUND=y
   		ADAPTER="${i}/${INST}"
		ADAPTERDIR=$i
  	fi
 done

 if [ "$FOUND" = "n" ] && test -d /sys/class/scsi_host/host${INST}
 then
	ADAPTERDIR=/sys/class/scsi_host/
	ADAPTER=/sys/class/scsi_host/host${INST}
	FOUND=y
 fi
 
 if [ "$FOUND" = "n" ]
 then
	echo "There is no SCSI adapter instance $INST"
	exit 1
 fi

 if [ "${KERNELVER}" = "2.6" ] && [ "`echo $ADAPTER | grep /sys/class/scsi_host`" != "" ]
 then
 	#
        # We currently only to this if we are Emulex and 2.6

        scan_sysfs host${INST}
 else
 	# get list of targets

 	get_targets $ADAPTER

 	#
	# remove LUNs in midlayer for this adapter instance

 	# do not perform "scsi remove-single-device" if we are RHEL 4
 	if [ "$DOREMOVE" != "n" ]
 	then
 		for j in $TARGETS
 		do
			CNT=$LUNSTART

			# send scsi remove command to midlayer
			while [ $CNT -le $LUNEND ]
			do
				# do not perform scsi command on root device! and do not exeucte
				# "scsi remove-single-device if we are running RHEL 4
 	               		if [ "$ROOTINST" != "$INST 0 $j $CNT" ] || [ "$RHEL4" = "y" ]
				then
					printf "\rRemoving ${ADAPTERDIR}/${INST}, target $j, LUN $CNT      "
					echo "scsi remove-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
					usleep $PROBEDELAY
				fi
				CNT=`expr $CNT + 1`
			done
 		done
 	fi

 	# force midlayer to rescan this adapter instance

 	for j in $TARGETS
 	do
		CNT=$LUNSTART

		# send scsi add command to midlayer

		while [ $CNT -le $LUNEND ]
		do
			# do not perform scsi command on root device!
                	if [ "$ROOTINST" != "$INST 0 $j $CNT" ]
			then
				printf "\rScanning ${ADAPTERDIR}/${INST}, target $j, LUN $CNT      "
				echo "scsi add-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
				usleep $PROBEDELAY
			fi
			CNT=`expr $CNT + 1`
		done
 	done
 fi

 echo ""
 print_scsi ${INST}
}

# print out device information from /proc/scsi/scsi

print_scsi () {

 # get list of adapters

 adapter_inst
 hbas=""

 # extract just the host instance numbers
 
 for x in $ADAPTERS
 do
 	hbas=$hbas" `basename $x | sed 's/host//g'`"
 done
 
 # print data from /proc/scsi/scsi

 printf "\r                                                      \n\n"
 for inst in ${hbas}
 do
    cat /proc/scsi/scsi | tr -d '\n'|
    awk '{ for(i=1;i<=NF;i++) if(index($i,"scsi")!=0)printf("\n%s ",$i); else printf("%s ",$i)}' | \
    awk 'BEGIN{n=split("scsi|Channel|Id|Lun|Vendor|Model|Rev|Type",ar,"|");} \
         {for(i=1;i<NF;i++)for(j=1;j<=n;j++) \
          if(index($i,ar[j])){printf(" %s",index($i,"scsi")?$i:$(i+1));break}print "";}' | \
    awk '{if(index($0,"scsi'${inst}'")) \
          printf("%-6.6s %2.2s %2.2s %2.2s %-10.10s %-10.10s %-10.10s %s\n", $1,$2,$3,$4,$5,$6,$7,index($8,"known")?"":$8);}'
  done
  echo
}


# do just a scsi remove-single-device on all QLogic adapter devices

remove_all () {

 # do not perform "scsi remove-single-device" if we are RHEL 4
 if [ "$DOREMOVE" != "n" ]
 then
 	# get a list of qla* adapters in the system

 	adapter_inst

 	# get scsi address of root device (if one exists)

 	determine_root_inst

 	# perform midlayer removal

 	for i in $ADAPTERS
 	do
		# get list of targets

		get_targets $i
		INST=`echo $i | awk 'BEGIN {FS="/"} {print $5}'`

		for j in $TARGETS
		do
			CNT=$LUNSTART

			# send scsi remove command to midlayer
			while [ $CNT -le $LUNEND ]
			do
				# do not perform scsi command on root device
	                        if [ "$ROOTINST" != "$INST 0 $j $CNT" ] || [ "$RHEL4" = "y" ]
				then
					printf "\rRemoving $i, target $j, LUN $CNT      "
					echo "scsi remove-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
					usleep $PROBEDELAY
				fi
				CNT=`expr $CNT + 1`
			done
		done
 	done
 	echo ""
 fi
}


# do just a scsi add-single-device on all QLogic adapter devices

add_all () {
 # get a list of qla* adapters in the system

 adapter_inst

 # get scsi address of root device (if one exists)

 determine_root_inst

 # do scsi add-single-device on all QLogic devices
 
 for i in $ADAPTERS
 do
	if [ "${KERNELVER}" = "2.6" ] && [ "`echo $i | grep /sys/class/scsi_host`" != "" ]
        then
                #
                # We currently only to this if we are Emulex and 2.6

                INST=`basename $i`
                scan_sysfs $INST
	else
		# get list of targets

	        get_targets $i
		INST=`echo $i | awk 'BEGIN {FS="/"} {print $5}'`

		for j in $TARGETS
		do
			CNT=$LUNSTART

			# send scsi add command to midlayer

			while [ $CNT -le $LUNEND ]
			do
				# do not perform scsi command on root device!
                	        if [ "$ROOTINST" != "$INST 0 $j $CNT" ]
				then
					printf "\rScanning $i, target $j, LUN $CNT      "
					echo "scsi add-single-device $INST 0 $j $CNT" > /proc/scsi/scsi
					usleep $PROBEDELAY
				fi
			CNT=`expr $CNT + 1`
			done
		done
		echo ""
	fi
 done
 echo ""
}

#
# script main
#

if [ $# -lt 1 ]
then
 echo "Arguments needed!!!"
 echo ""
 print_help
fi

# parse command line strings (if any)
read_args $*

# Add support for legacy tape devices if we are a 2.6 kernel

if [ "$KERNELVER" = "2.6" ]
then
	add_tape_devices
fi

# based on either a command line parameter or the kernel version set whether
# we execute a "scsi remove-single-device" using the DOREMOVE flag

if [ $NFLAG -eq 1 ] || [ "$KERNELVER" = "2.6" ]
then
 DOREMOVE=n
fi

# Pick action based upon which command line flag is set

if [ $AFLAG -eq 1 ]
then
 # scan all HBA instances (may or may not involve both adding and removing)
 scan_all
elif [ $RFLAG -eq 1 ]
then
 # execute scsi remove-single-device on all devices as long as DOREMOVE = 1
 remove_all
elif [ $DFLAG -eq 1 ]
then
 # execute scsi add-single-device on all devices
 add_all
elif [ $IFLAG -eq 1 ]
then
 # execute a SCSI midlayer scan on a particular HBA instance
 scan_instance $SPECHBAINST
else
 print_help
fi

exit 0
