#!/bin/sh
#
# This script attempts to keep the scsi mid layer's idea 
# and the cciss driver's idea of what tape drives are on
# what cciss controllers up to date.  Run this script
# whenever you hotplug or hotunplug a tape drive from a
# Smart Array controller.  This script replaces an older
# script which tended to make a lot of unnecessary noise 
# in dmesg output due to not being designed to avoid making
# such noise, and having to try to deduce necessary information 
# which was not provided by the cciss driver.
#

CCISS_PHYS_DEVS=/sbin/cciss_phys_devs

DEBUG_ON=no
verbose()
{
	if [ "$DEBUG_ON" = "yes" ]
	then
		echo "$*"
	fi
}

display_patch()
{

cat << 'EOF'

July 2005

Add SCSI host and device information not elsewhere available to /proc/scsi/cciss/*
Namely, connect cciss device instance with scsi host number, and
give scsi host number, bus, target, lun, devicetype, and 8-byte cciss LUNID
for each tapedrive/medium changer attached to a controller

For instance:

# cat /proc/scsi/cciss/2
cciss0: SCSI host: 2
c2b0t0l0 01 0x0000000000000001


 drivers/block/cciss_scsi.c |   17 ++++++++++++++++-
 1 files changed, 16 insertions(+), 1 deletion(-)

--- linux-2.6.11.10/drivers/block/cciss_scsi.c~cciss_scsi_info	2005-07-18 14:55:15.000000000 -0500
+++ linux-2.6.11.10-scameron/drivers/block/cciss_scsi.c	2005-07-19 14:47:34.000000000 -0500
@@ -1145,6 +1145,7 @@ cciss_scsi_proc_info(struct Scsi_Host *s
 
 	int buflen, datalen;
 	ctlr_info_t *ci;
+	int i;
 	int cntl_num;
 
 
@@ -1155,8 +1156,22 @@ cciss_scsi_proc_info(struct Scsi_Host *s
 	cntl_num = ci->ctlr;	/* Get our index into the hba[] array */
 
 	if (func == 0) {	/* User is reading from /proc/scsi/ciss*?/?*  */
-		buflen = sprintf(buffer, "hostnum=%d\n", sh->host_no); 	
+		buflen = sprintf(buffer, "cciss%d: SCSI host: %d\n", cntl_num, sh->host_no); 	
 
+			/* this information is needed by apps to know which cciss device corresponds to
+			   which scsi host number without having to open a scsi target device node.
+			   The device information is not a duplicate of /proc/scsi/scsi because the
+			   two may be out of sync due to scsi hotplug, rather this info is for an app
+			   to be able to use to know how to get them back in sync. */
+
+		for (i=0;i<ccissscsi[cntl_num].ndevices;i++) {
+			struct cciss_scsi_dev_t *sd = &ccissscsi[cntl_num].dev[i];
+			buflen += sprintf(&buffer[buflen], "c%db%dt%dl%d %02d "
+				"0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+				sh->host_no, sd->bus, sd->target, sd->lun, sd->devtype,
+				sd->scsi3addr[0], sd->scsi3addr[1], sd->scsi3addr[2], sd->scsi3addr[3],
+				sd->scsi3addr[4], sd->scsi3addr[5], sd->scsi3addr[6], sd->scsi3addr[7]);
+		}
 		datalen = buflen - offset;
 		if (datalen < 0) { 	/* they're reading past EOF. */
 			datalen = 0;

_
EOF
}

check_for_patch()
{
	head -1 "$1" | grep '^cciss[0-9]' > /dev/null 2>&1
	if [ "$?" != "0" ]
	then
cat << 'EOF' 1>&2
The cciss_hotplug script requires a kernel with a cciss driver which provides
certain information about attached tape devices via /proc/scsi/cciss/*
to work correctly.  The driver you are running does not appear to be such a
driver.  Despite this, (some of) your tape drive(s) may have been brought
online.  Check /proc/scsi/scsi.  For correct operation, obtain a kernel 
with the required support already contained.  Alternately, at your own risk
you could try patching your kernel.  (Running "cciss_hotplug --patch" will 
output a patch that might work for you, but no guarantees.)
EOF
exit 1
	fi
}

find_scsi_host_of_cciss()
{
	local ctlrnum="$1"

	for x in /proc/scsi/cciss/*
	do
		if [ -f $x ]
		then
			head -1 $x | grep '^cciss'"$ctlrnum"'[:]' > /dev/null 2>&1
			if [ "$?" = "0" ]
			then
				echo $x
				return
			fi
		fi
	done
	return
}

engage_scsi()
{
	# see what controllers have tape drives but are not engaged with the 
	# scsi subsystem

	verbose "engage_scsi"

	ctlrnum=`$CCISS_PHYS_DEVS -t /dev/cciss/c*d0 | awk -F: '{ print $1 }' |\
		 awk -F/ '{ print $4 }' | sed -e 's/^c//' -e 's/d[0-9]*$//'`

	if [ "$ctlrnum" = "" ]
	then
		# $CCISS_PHYS_DEVS found no tape drives at all
		verbose "no tape drives found on any cciss controllers"
		return
	fi

	for c in $ctlrnum 
	do
		if [ ! -d /proc/scsi/cciss ]
		then
			verbose "Engaging scsi for /proc/driver/cciss/cciss""$c"
			echo "engage scsi" > /proc/driver/cciss/cciss"$c"
			continue
		fi

		check_for_patch /proc/scsi/cciss/[0-9]*

		found=0;
		for x in /proc/scsi/cciss/[0-9]*
		do
			if [ -f "$x" ]
			then
				grep "^cciss""$c"":" "$x" > /dev/null 2>&1
				if [ "$?" = "0" ]
				then
					found=1
					break;
				fi
			fi
		done
		if [ "$found" != "1" ]
		then
			verbose "Engaging scsi for /proc/driver/cciss/cciss""$c"
			echo "engage scsi" > /proc/driver/cciss/cciss"$c"
		fi
	done
}

check_if_cciss_knows()
{
	# We found a device via report physical luns,
	# We want to know if cciss driver is aware of it.
	# return 1 if cciss driver knows it already, 0 otherwise

	local device="$1"
	local scsihost_rescanlist="$2"

	verbose "checking if cciss knows $device"
	local ctlrnum=`echo $device | awk -F: '{ print $1 }' |\
		 awk -F/ '{ print $4 }' | sed -e 's/^c//' -e 's/d[0-9]*$//'`
	local lunid=`echo $device | awk -F: '{ print $2 }'`
	local scsihost="`find_scsi_host_of_cciss $ctlrnum`"

	if [ "$scsihost" = "" ]
	then
		# something's wrong, didn't even find scsi host
		verbose "Did not find scsi host for $dev"
		return 0
	fi

	# echo "Checking if cciss driver knows device ""$device"", if not, will rescan."

	grep ' '"$lunid"'$' $scsihost > /dev/null 2>&1
	if [ "$?" = "0" ]
	then
		# we found it, cciss driver knows this device
		# return 1 means cciss does know this device
		verbose "cciss driver knows $device"
		return 1
	fi
	# we didn't find it, cciss driver does not know this device
	echo "$scsihost" >> $scsihost_rescanlist
	return 0
}

cciss_device_really_there()
{
	# we found a device in /proc/scsi/cciss/x, and we want to know if
	# it was found by report physical luns.  Return 0 if it was found
	# 1 if not found.

	local scsihost="$1"
	local ccissnum="$2"
	local device="$3"
	local lunid=`echo $device | awk '{ print $3 }'`

	pattern='/dev/cciss/c'"$ccissnum"'d0[:]'"$lunid"'[:]'	

	$CCISS_PHYS_DEVS -t /dev/cciss/c"$ccissnum"d0 | grep "$pattern" > /dev/null 2>&1
	if [ "$?" = "0" ]
	then
		# found it
		return 0
	fi
	return 1;
}

rescan_devices()
{
	RESCAN_CTLRS=`mktemp /tmp/cciss_rescan.XXXXXXX`
	export RESCAN_CTLRS

	verbose "rescan_devices"

	# check that each tape device known to cciss driver 
	# found by REPORT_PHYSICAL_LUNS

	if [ ! -d /proc/scsi/cciss ]
	then
		return
	fi

	for scsihost in /proc/scsi/cciss/[0-9]*
	do
		if [ ! -f $scsihost ]
		then
			continue
		fi
		ctlrnum="`head -1 $scsihost | sed -e 's/^cciss//' -e 's/[:].*$//'`"

		# For each device known to cciss driver . . .
		grep -v '^cciss' $scsihost |\
			while [ "1" = "1" ] 
			do
				read device
				if [ "$?" != "0" ]
				then
					break;
				fi
				# echo "device=$device"

				cciss_device_really_there "$scsihost" "$ctlrnum" "$device"
				if [ "$?" != 0 ]
				then
					# if device is no longer present, we need to tell cciss driver to rescan 
					echo $scsihost >> $RESCAN_CTLRS
				fi
			done
	done

	# check that each tape device found by REPORT_PHYSICAL_LUNS 
	# is known to cciss driver 

	$CCISS_PHYS_DEVS -t /dev/cciss/c*d0 |\
		while [ "1" = "1" ] 
		do
			read x
			if [ "$?" != "0" ]
			then
				break;
			fi
			check_if_cciss_knows "$x" $RESCAN_CTLRS
		done

	for x in `cat $RESCAN_CTLRS | sort | uniq`
	do
		verbose "Rescanning $x"
		echo rescan > $x
	done
	/bin/rm -f $RESCAN_CTLRS
}

check_proc_scsi_scsi_format()
{
	# Try to make sure that the format of /proc/scsi/scsi is as we
	# expect, because we rely on this format to know which devices to
	# remove, and we really really don't want to accidentally remove a
	# the wrong device, especially a disk. 
	
	local FILE="$1"

	# Make sure every line of /proc/scsi/scsi matches a pattern we expect
	# and comes in an order we expect.  ANY deviation disqualifies it.

	awk '	
	BEGIN { expect=1 }
	/^Attached devices:/ { 
			if (expect != 1) {
				printf("no\n"); 
				exit;
			}
			expect=2;
			next;
		}
	/^Host: scsi[0-9][0-9]* Channel: [0-9][0-9]* Id: [0-9][0-9]* Lun: [0-9]/ {
			if (expect != 2) {
				printf("no\n");
				exit;
			}
			expect=3;
			next;
		}
	/^  Vendor[:].*Model[:].*Rev[:]/ {
			if (expect != 3) {
				printf("no\n");
				exit;
			}
			expect=4;
			next;
		}
	/^  Type[:].*ANSI SCSI revision[:]/ {
			if (expect != 4) {
				printf("no\n");
				exit;
			}
			expect=2;
			next;
		}
	/^.*$/	{
			printf("no\n");
			exit;
		}
	' < $FILE
}

check_midlayer_for_removed_devices()
{
	# See if the SCSI midlayer has devices registered which have disappeared 
	# Scan /proc/scsi/scsi for lines of the form:
	# Host: scsi2 Channel: 00 Id: 00 Lun: 00
	# and check that against /proc/scsi/cciss/* to make 
	# sure they are still around.

	# cciss driver is expected to have been engaged and rescanned
	# or found not to need it before this is called.

	verbose "check_midlayer_for_removed_devices"

	if [ ! -d /proc/scsi/cciss ]
	then
		# there are no (and were never any) cciss controllers 
		# registered as scsi HBAs, nothing to do.
		return;
	fi

	local format_good=`check_proc_scsi_scsi_format /proc/scsi/scsi`
	if [ "$format_good" = "no" ]
	then
		echo "cciss_hotplug: The format of /proc/scsi/scsi is not as expected.  " 1>&2
		echo "cciss_hotplug: Will not remove any devices, as accidentally removing a disk would be bad." 1>&2
		return
	fi

	# exclude all devices from removal but sequential access and medium chagners 
	awk '
		/Attached devices/ { next; }
		/^Host:/ { a=$0; }
		/Vendor:/ { b=$0; }
		/Type:/ { printf("%s | %s | %s\n", a, b, $0); }
	' < /proc/scsi/scsi | egrep 'Sequential-Access|Medium Changer' |\
		while [ "1" = "1" ]
		do
			read x
			if [ "$?" != "0" ]
			then
				break;
			fi
			verbose "$x"
			local xscsihost=`echo $x | awk -F: '{ print $2 }' | sed -e 's/[^0-9][^0-9]*//g'`
			local xbus=`echo $x | awk -F: '{ print $3 }' | sed -e 's/[^0-9][^0-9]*//g'`
			local xtarget=`echo $x | awk -F: '{ print $4 }' | sed -e 's/[^0-9][^0-9]*//g'`
			local xlun=`echo $x | awk -F: '{ print $5 }' | sed -e 's/[^0-9][^0-9]*//g'`

			local scsihost=`echo $xscsihost | awk '{ printf("%d", $1); }'`
			local bus=`echo $xbus | awk '{ printf("%d", $1); }'`
			local target=`echo $xtarget | awk '{ printf("%d", $1); }'`
			local lun=`echo $xlun | awk '{ printf("%d", $1); }'`

			# verbose "scsihost=$scsihost bus=$bus target=$target lun=$lun"

			local dev='c'"$scsihost"'b'"$bus"'t'"$target"'l'"$lun"

			if [ ! -f /proc/scsi/cciss/$scsihost ]
			then
				verbose "$dev is not on a cciss controller"
				continue
			fi

			grep '^'"$dev"' ' /proc/scsi/cciss/"$scsihost" > /dev/null 2>&1
			if [ "$?" != "0" ]
			then
				# didn't find it, so we should remove it from midlayer
				echo "Removing device $dev"	
				echo "scsi remove-single-device $scsihost $bus $target $lun" > /proc/scsi/scsi
			fi
		done
}

check_midlayer_for_missing_devices()
{
	# see if the midlayer is missing devices which have appeared
	# check /proc/scsi/cciss/* and make sure each device listed there
	# has an entry in /proc/scsi/scsi

	verbose "check_midlayer_for_missing_devices"
	if [ ! -d /proc/scsi/cciss ]
	then
		verbose "no cciss controllers are engaged with SCSI"
		return
	fi

	cat /proc/scsi/cciss/* | grep -v '^cciss[0-9]*[:]' |\
		while [ "1" = "1" ]
		do
			read x
			if [ "$?" != "0" ]
			then
				break;
			fi
			device=`echo $x | awk '{ print $1 }'`
			host=`echo $device | sed -e 's/^c//' -e 's/b[0-9].*$//'`
			bus=`echo $device | sed -e 's/^c[0-9]*b//' -e 's/t[0-9].*$//'`
			target=`echo $device | sed -e 's/^c[0-9]*b[0-9]*t//' -e 's/l[0-9].*$//'`
			lun=`echo $device | sed -e 's/^c[0-9]*b[0-9]*t[0-9]*l//'`

			bus2=`echo $bus | awk '{ printf("%02d", $1); }'`
			target2=`echo $target | awk '{ printf("%02d", $1); }'`
			lun2=`echo $lun | awk '{ printf("%02d", $1); }'`

			pattern='Host: scsi'"$host"' Channel: '"$bus2"' Id: '"$target2"' Lun: '"$lun2"
			grep "$pattern" /proc/scsi/scsi > /dev/null 2>&1
			if [ "$?" != "0" ]
			then
				echo "Adding SCSI device $device"
				echo "scsi add-single-device $host $bus $target $lun" > /proc/scsi/scsi
			fi
		done
}

update_scsi_midlayer()
{
	verbose "Updating scsi mid layer"

	check_midlayer_for_removed_devices
	check_midlayer_for_missing_devices
}

check_cciss_phys_devs()
{
	( < /dev/cciss/c0d0 ) > /dev/null 2>&1
	if [ "$?" != "0" ]
	then
		# This script gets run by init scripts, so 
		# don't complain if cciss isn't around, just go away.
		verbose "Can't open /dev/cciss/c0d0" 1>&2
		exit 0
	fi
	$CCISS_PHYS_DEVS > /dev/null 2>&1
	if [ "$?" = "127" -o ! -x $CCISS_PHYS_DEVS  ]
	then
		echo "Can't run $CCISS_PHYS_DEVS.  Check PATH." 1>&2
		exit 1
	fi

}

#
# 	main
#
	if [ "$1" = "--patch" ]
	then
		display_patch
		exit 0
	fi

	check_cciss_phys_devs
	engage_scsi
	rescan_devices
	update_scsi_midlayer

