#!/usr/bin/env bash

#set -x

######################################################################
# BACK UP YOUR SYSTEM before using this on a production machine!!!!!
######################################################################

# four parameters to tweak:
# 1) This "SRCDISK" is a horrible misnomer!
# According to http://www.adminschoice.com/docs/solstice_disksuite.htm :
#    Creating a RAID5 metadevice from a slice that contains an existing
#    file system will erase the data during the RAID5 initialization
#    process.
# However, we still look for $SRCDISK slices in /etc/vfstab, when
# reconfiguring /etc/vfstab with the name device name(s)
SRCDISK=c0t2d0

# 2) The disk that will have data synch'd onto it.  This disk must be
# greater than or equal to in size to $SRCDISK
DSTDISKS="c0t3d0 c0t4d0"

# 3) The slice number of the partition that is to hold metadb's.
# It'll be the same slice on $SRCDISK and all $DSTDISKS.  It should NOT
# have a filesystem on it, or at least, not a filesystem that you don't
# mind losing all the data in.  :)  I heard that 5M was enough, but when
# I tried to create 3 metadb's in a 10M slice, SVM said there wasn't
# enough room.  Creating 2 metadb's in a 10M slice worked though.
METADEV=7

# do we, or do we not, mirror swap partitions?  If we don't mirror, we
# get twice as much swap space, but you have to boot singleuser and
# comment out a line in /etc/vfstab if there's a disk failure.  Legal
# values are true and false
MIRRORSWAP=true

# you probably don't need to tune anything below this, unless perhaps
# you have a computer with a *.uci.edu hostname.

case "$(uname)-$(uname -r)" in
	SunOS-5.8)
		echo Solaris 8, checking for patch...
		if [ -d /var/sadm/patch/108693-25 ]
		then
			echo Good, you have 108693-25.  This script was tested with 1>&2
			echo this patch  1>&2
		elif [ -d /var/sadm/patch/108693-* ]
		then
			minor=$(ls /var/sadm/patch/108693-* | head -1 | sed 's#/var/sadm/patch/108693\-\([0-9][0-9]*\)#\1#')
			if [ "$minor" -lt 25 ]
			then
				echo Warning: This script was tested with 108693-25, but you 1>&2
				echo appear to have an earlier version: 108693-$minor 1>&2
			else # $minor > 25
				echo Warning: This script was tested with 108693-25, but you 1>&2
				echo appear to have a later version: 108693-$minor 1>&2
				echo You are probably in good shape. 1>&2
			fi
		fi
		;;
	SunOS-5.9)
		echo Solaris 9, good 1>&2
		;;
	SunOS-*)
		echo Warning, running on a totally untested version of Solaris 1>&2
		;;
	*)
		echo Sorry, you need SunOS to use this script 1>&2
		exit 1
		;;
esac

if [ -d /usr/opt/SUNWmd ]
then
	PATH=$PATH:/usr/opt/SUNWmd
	export PATH
fi

for prog in metastat metadb metaclear metainit metattach
do
	# note that we're using type, because which didn't give a useful exit
	# status
	if type "$prog" > /dev/null 2>&1
	then
		: Good, we have it
	else
		echo Sorry, you will need "$prog" on your '$PATH' for $0 to work 1>&2
		echo properly.... 1>&2
		exit 1
	fi
done
echo Good, you appear to have all the required programs on your '$PATH'. 1>&2

for prog in metachk
do
	# note that we're using type, because which didn't give a useful exit
	# status
	if type "$prog" > /dev/null 2>&1
	then
		: Good, we have it
	else
		echo Warning, you do not have "$prog" on your '$PATH'. 1>&2
		echo You probably want it. 1>&2
	fi
done

# the stuff in this "if" is just for testing - we delete what we've
# created so we can start over again.  Oh, and also, we override what we
# specified above, for specific machines.  :)
echo
case "`uname -n`" in
	@bingy.nac.uci.edu)
		if metastat > /dev/null 2>&1
		then
			echo Removing previous SVM setup
			metadb -f -d /dev/dsk/c0t2d0s7
			metadb -f -d /dev/dsk/c0t3d0s7
			metadb -f -d /dev/dsk/c0t4d0s7
			metaclear -f /dev/md/dsk/d6
			# this stuff is more relevant for mirroring, where you need 2.
			# but in a 3-disk RAID 5, you don't need 3.
			#metaclear -f /dev/md/dsk/d16
			#metaclear -f /dev/md/dsk/d26
			#metaclear -f /dev/md/dsk/d36
			cp /etc/vfstab.SVM.verybeginning /etc/vfstab
		fi
		echo Overriding defaults for bingy
		SRCDISK=c0t2d0
		DSTDISKS="c0t3d0 c0t4d0"
		METADEV=7
		MIRRORSWAP=true
		;;
	bingy.nac.uci.edu)
		# this one is for the hot spare testing...
		if metastat > /dev/null 2>&1
		then
			echo Removing previous SVM setup
			metadb -f -d /dev/dsk/c0t1d0s7
			metadb -f -d /dev/dsk/c0t2d0s7
			metadb -f -d /dev/dsk/c0t4d0s7
			metadb -f -d /dev/dsk/c0t5d0s7
			metaclear -f /dev/md/dsk/d6
			# this stuff is more relevant for mirroring, where you need 2.
			# but in a 3-disk RAID 5, you don't need 3.
			#metaclear -f /dev/md/dsk/d16
			#metaclear -f /dev/md/dsk/d26
			#metaclear -f /dev/md/dsk/d36
			cp /etc/vfstab.SVM.verybeginning /etc/vfstab
		fi
		echo Overriding defaults for bingy
		SRCDISK=c0t2d0
		DSTDISKS="c0t1d0 c0t4d0"
		METADEV=7
		MIRRORSWAP=true
		;;
	meter.eng.uci.edu)
		# this one is for the hot spare testing...
		if metastat > /dev/null 2>&1
		then
			# Slot 0 is c0t0d0 - filled Thu Apr 7, 2005
			# Slot 1 is c0t8d0 - disk 1
			# Slot 2 is c0t9d0 - filled Thu Apr 7, 2005
			# Slot 3 is c0t10d0 - disk 4
			# Slot 4 is c0t11d0 - filled Thu Apr 7, 2005
			# Slot 5 is c0t12d0 - disk 5
			echo Removing previous SVM setup
			# these three disks have an identical number of blocks in them, Thu Apr 7, 2005
			metadb -f -d /dev/dsk/c0t8d0s7
			metadb -f -d /dev/dsk/c0t10d0s7
			metadb -f -d /dev/dsk/c0t12d0s7
			metaclear -f /dev/md/dsk/d6
			cp /etc/vfstab.SVM.verybeginning /etc/vfstab
		fi
		echo Overriding defaults for bingy
		SRCDISK=c0t8d0
		DSTDISKS="c0t10d0 c0t12d0"
		METADEV=7
		MIRRORSWAP=true
		;;
	*)
		echo Using defaults, no host case
		;;
esac

# $1 is a partition
# returns just the slice number (EG 0 for slice 0)
function get_slice
{
	var="$(echo $1 | sed 's/^.*s\([0-9][0-9]*\)$/\1/')"
	echo "$var"
}

# $1 is a partition
# returns just the target number (EG 2 for target 2)
function get_target
{
	var="$(echo $1 | sed 's/^.*t\([0-9][0-9]*\).*$/\1/')"
	echo "$var"
}

# $1 is a partition
# returns just the controller number (EG 1 for controller 1)
function get_controller
{
	var="$(echo $1 | sed 's/^.*c\([0-9][0-9]*\).*$/\1/')"
	echo "$var"
}

# $1 destination disk
# $2 source partition
# returns corresponding dest partition
function dst_from_src
{
	s=s"$(get_slice $2)"
	echo "$1""$s"
}

# useless cat, but oh well :)
SRCPARTITIONS="$(cat /etc/vfstab | grep "^/dev/dsk/${SRCDISK}s" | \
	if [ "$MIRRORSWAP" = true ]
	then
		# just pass all dsk device files
		awk ' { print $1 } '
	else
		# pass all disk device files but swap partitions
		awk ' { if ($4 != "swap") print $1 }'
	fi | \
	sed 's#/dev/dsk/##')"

if [ "$SRCPARTITIONS" = "" ]
then
	(
		echo Error: No source partitions in /etc/vfstab
		echo Is the disk you are trying to use for seed slice numbers in vfstab\?
	) 1>&2
	exit 1
fi

echo SRCDISK is "$SRCDISK"
echo DSTDISKS is "$DSTDISKS"
echo METADEV is "$METADEV"
echo MIRRORSWAP is "$MIRRORSWAP"
# we want this one rewritten to all one line, so no quotes...
echo SRCPARTITIONS is $SRCPARTITIONS
echo
echo "`tput smso 2> /dev/null`"ALL DATA on $SRCDISK $DSTDISKS will
echo be lost!"`tput rmso`"
echo
echo Hit interrupt now if you do not want these values!
echo Sleeping for 10 seconds...
sleep 10

echo
echo 'We begin!  :)'
echo

# unmount our source partitions...  Not required for root mirroring, but
# is required for RAID 5 (with SVM in both cases)
for srcpart in $SRCPARTITIONS
do
	echo Trying to umount $srcpart... 1>&2
	if umount -f $srcpart
	then
		echo Succeeded... 1>&2
	else
		echo Failed, but continuing anyway... 1>&2
	fi
done

# save a copy of the DSTDISK's previous partitioning, in case there's
# lost disk space
for DSTDISK in $DSTDISKS
do
	prtvtoc /dev/rdsk/"$DSTDISK"s2 > /var/tmp/${DSTDISK}s2
done

# copy partition table.  s2 is the entire disk, by convention, starting
# with the disk label
method=1
if type dup-label > /dev/null
then
	method=2
fi
case "$method" in
	0)
		# this complains about cylinder boundaries if your disks don't
		# have pretty similar geometries
		for DSTDISK in $DSTDISKS
		do
			prtvtoc /dev/rdsk/"$SRCDISK"s2 | fmthard -s - /dev/rdsk/"$DSTDISK"s2
		done
		;;
	1)
		# this works, but it also has the disadvantage of copying the disk
		# type to the other disk.  So a 200G maxtor could end up telling
		# admins it's a 100G seagate, or something like that.  However, sun
		# format's autoconfigure option can restore the label.
		#
		# Does this cause bad magic number errors in dmesg sometimes?
		echo Bummer, no dup-label program available, using dd 1>&2
		for DSTDISK in $DSTDISKS
		do
			dd if=/dev/dsk/"$SRCDISK"s2 of=/dev/dsk/"$DSTDISK"s2 count=16
		done
		;;
	2)
		# AFAIK, this is less confusing to future admins :)
		echo Cool, you have dup-label 1>&2
		for DSTDISK in $DSTDISKS
		do
			if ! dup-label /dev/dsk/"$SRCDISK"s2 /dev/dsk/"$DSTDISK"s2
			then
				echo dup-label "$SRCDISK"s2 "$DSTDISK"s2 failed 1>&2
				exit 1
			fi
		done
		;;
esac

# Set up metadb's.  Writing this to an actual filesystem is a no-no
number=$(expr $(echo $DSTDISKS | wc -w) + 1)
metadevices="/dev/dsk/${SRCDISK}s$METADEV $(echo $DSTDISKS | tr ' ' '\012' | sed -e 's#$#s'"$METADEV"'#' -e 's#^#/dev/dsk/#')"
echo '$metadevices' is $metadevices
# since we have >= 3 disks (a requirement for RAID 5), we only need
# one metadb per device, hence the -c1 (but 1 is the default anyway)
for metadev in $metadevices
do
	if ! metadb \
		-a \
		-f \
		-c 1 \
		$metadev
	then
		echo error "metadb'ing" $metadev 1>&2
		exit 1
	fi
done

# set up meta devices.
# dx's are mirror devices
# d1x's are source devices
# dnx's, n >= 2, are destination devices
for srcpart in $SRCPARTITIONS
do
	slice="$(get_slice $srcpart)"
	# do src device
	srcmetadev=d1"$slice"
	# example from http://www.adminschoice.com/docs/solstice_disksuite.htm
	## metainit d45 -r c2t3d0s2 c3t0d0s2 c4t0d0s2
	# d45: RAID is setup
	# it appears that -f (force, even if filesystem is mounted) does not
	# work with RAID 5
	if metainit d"$slice" -r ${SRCDISK}s${slice} $(for DSTDISK in $DSTDISKS; do echo "${DSTDISK}s${slice}"; done)
	then
		:
	else
		echo metainit failed 1>&2
		exit 1
	fi
done

# backup vfstab.  mostrecent is changed every time we run this script.
# verybeginning is only created the first time we run this script.
touch /etc/lvm/md.tab
for file in vfstab lvm/md.tab
do
	cp /etc/"$file" /etc/"$file".SVM.mostrecent
	if [ -f /etc/"$file".SVM.verybeginning ]
	then
		:
	else
		cp /etc/"$file" /etc/"$file".SVM.verybeginning
	fi
done

# transform this:
#/dev/dsk/c1t0d0s5 /dev/rdsk/c1t0d0s5 /export/home ufs 2 yes logging
# ...into:
#/dev/md/dsk/d05 /dev/md/rdsk/d05 /export/home ufs 2 yes logging
# ...just for example
while read line
do
	if echo "$line" | grep '^#' > /dev/null
	then
		# preserve comments
		echo "$line"
	else
		set $line
		# a little sanity checking
		if [ "$#" = 7 ] && \
			echo "$1" | grep "^/dev/dsk/${SRCDISK}s" > /dev/null &&
			echo "$2" | grep "^/dev/rdsk/${SRCDISK}s" > /dev/null
		then
			echo "# $line"
			DSK=/dev/md/dsk/d"$(get_slice $1)"
			RDSK=/dev/md/rdsk/d"$(get_slice $2)"
			echo "$DSK" "$RDSK" "$3" "$4" "$5" "$6" "$7"
		elif [ "$#" = 7 ] && \
			echo "$4" | egrep "^swap$" > /dev/null && \
			echo "$1" | egrep "^/dev/dsk/${SRCDISK}s" > /dev/null
		then
			if [ "$MIRRORSWAP" = true ]
			then
				echo "# Swap is mirrored"
				echo "/dev/md/dsk/d$(get_slice $1) $2 $3 $4 $5 $6 $7"
			else
				# set up swap on all disks.  We intentionally do not mirror
				# them.  $SRCDISK most likely won't have swap partitions,
				# but we try to do them anyway.  We set up identical
				# partitions on each of the $DSTDISKS, and swap on all of them.  Be
				# careful not to do swap -files-, only swap partitions
				echo "# swap partitions are not mirrored, but we swap on both"
				echo "# disks"
				echo "$1 $2 $3 $4 $5 $6 $7"
				# a swap line looks like:
				# /dev/dsk/c0t0d0s1 - - swap - no -
				for DSTDISK in $DSTDISKS
				do
					echo "/dev/dsk/${DSTDISK}s$(get_slice $1) $2 $3 $4 $5 $6 $7"
				done
			fi
		elif [ "$#" = 7 ] && \
			echo "$1" | egrep "/dev/md/dsk/|/dev/md/rdsk" > /dev/null
		then
			# Echo any other md lines
			echo "$line"
		else
			# and just copy through anything else, unadorned
			echo "$line"
		fi
			
	fi
done < /etc/vfstab > /etc/vfstab.new

mv /etc/vfstab.new /etc/vfstab

# this appeases metachk
metastat -p > /etc/lvm/md.tab

# this is similar to sync, but more effective.  It doesn't return until
# buffers are flushed, and it also rolls data out of the logs into the
# filesystem proper on log structured filesystems
echo Running lockfs to sync up buffered/logged data...
lockfs -fa

echo
echo "Done."
echo
echo "You should now inspect /etc/vfstab, /etc/system, and run the"
echo "metastat command, and if all looks well, you should reboot."
echo
echo "A copy of your DSTDISK's previous partitionings are in"
echo "/var/tmp/*s2".  If your DSTDISKS is larger than your
echo "SRCDISK, you may be able to recover some (non-RAID'd) disk space"
echo "on $DSTDISK with some fancy partitioning, but that's beyond the"
echo "scope of this program, at least at this time."