#!/bin/ksh
#
# ident "@(#)utdiskadm.ksh	1.22	05/06/23 SMI"
#
# Copyright 2004-2005 Sun Microsystems, Inc.  All Rights Reserved.
# Use is subject to license terms.
#
# utdiskadm:
# Sun Ray disk administration utilities
#
# Note on device names:
# Except for the mount and unmount operations (-m and -u options) all other
# options that accept a device name argument can handle device aliases
# as well as the full device path name
# A device alias is the name of the device in $UTDEVROOT/dev/rdsk without
# the partition or slice suffix
# The mount option (-m) can accept a partition name. A partition name is the
# name of a device link without the path prefix
# Given below are some examples
#
# Device Path				Device Alias	Partition Name
# -----------				------------	--------------
# $UTDEVROOT/dev/rdsk/zip3s0		zip3		zip3s0
# $UTDEVROOT/dev/rdsk/disk8s2		disk8		disk8s2
# $UTDEVROOT/dev/rdsk/disk8s6		disk8		disk8s6
#

PATH="/bin:/usr/bin:/usr/sbin"

IDCMD="/usr/xpg4/bin/id"

# Sun Ray utilities
UTDISKCHECKCMD=""
UTDOMOUNTCMD=""
UTVOLCHECKCMD=""

# various Sun Ray paths
SUNWUTPATH=""
SUNWUTSESSIONS=""
SUNWUTUNITS=""
SUNWUTMNT=""
IEEEPREFIX="IEEE802."
PROGNAME=""
UTUNITDIR=""

# list of filesystem types to try fstyp
FST_LIST="ufs pcfs hsfs udfs"

# list of filesystem types to try mount
FSM_LIST="pcfs ufs hsfs udfs"

# caller info
UTUSERNAME=""
UTUSER_ID=""

# caller is root
CALLERROOT=""

# only one operation allowed (see set_opt)
OPERATION=""

# flags that are set while parsing command line
ARG_DEV=""
ARG_MNTPATH=""
ARG_LIST=""
ARG_STALE=""
ARG_ALL=""


# find block devices in given path that are currently mounted anywhere
# $1 = device path pattern
#
function getallmntdevs
{
	mount | nawk '($3 ~ NBLOCKDEVPAT) { print $3 }' NBLOCKDEVPAT=$1
}


# find mount point
# $1 = block device path
#
function getmountpoint
{
	TMPMOUNTPOINT="`mount | nawk '($3 == NBLOCKDEV) { print $1 }' NBLOCKDEV=$1`"
	if [[ -z $TMPMOUNTPOINT ]] then
		# look for pcfs suffixes
		for PSUFFIX in :c :d :e :f
		do
			TMPMOUNTPOINT="`mount | nawk '($3 == NBLOCKDEV) { print $1 }' NBLOCKDEV=$1$PSUFFIX`"
			if [[ -n $TMPMOUNTPOINT ]] then
				break
			fi
		done
	fi

	print $TMPMOUNTPOINT
}


# find block device mounted on this directory
# $1 = mount point
#
function getmountdev
{
	# should be valid directory name
	if [[ -d $1 ]] then
		mount | nawk '($1 == NMNTPATH) { print $3 }' NMNTPATH=$1
	fi
}


# return device alias (not path, different from partition alias)
# $1 must be either full raw device path or device alias only.
# Partial paths or partition aliases are unacceptable
# valid device alias is returned only if caller has access rights to device
#
function getdevalias
{
	# start with device path
	DEVPATH=$1

	# check whether unit directory is valid
	if [[ -z $UTUNITDIR ]] then
		DEVALIAS=""
		print $DEVALIAS
		return
	fi

	# is this a valid raw device path
	if [[ ! -c $DEVPATH ]] then
		# could be a device alias
		# try appending suffix for whole disk device
		# and prepending with full raw path prefix
		if [[ "X$ISA" = "Xi386" ]] then
			DEVPATH=$UTUNITDIR/dev/rdsk/$1"p0"
		elif [[ "X$ISA" = "Xsparc" ]] then
			DEVPATH=$UTUNITDIR/dev/rdsk/$1"s2"
		fi

		# check again
		if [[ ! -c $DEVPATH ]] then
			# invalid device name
			DEVALIAS=""
			print $DEVALIAS
			return
		fi
	fi

	# strip path prefix to get base name
	DEVPATHBASE=${DEVPATH##*/}

	# remove partition suffix (we need device alias, not partition alias)
	if [[ "X$ISA" = "Xi386" ]] then
		DEVALIAS=${DEVPATHBASE%p[0-4]}
	elif [[ "X$ISA" = "Xsparc" ]] then
		DEVALIAS=${DEVPATHBASE%s[0-9]}
	fi

	if [[ -z $DEVALIAS ]] then
		# cannot determine device alias, return NULL
		print $DEVALIAS
		return
	fi

	# note that DEVPATH above could've been valid path to non-backup slice
	# rebuild path to backup slice by using current session identifier
	if [[ "X$ISA" = "Xi386" ]] then
		DEVPATH=$UTUNITDIR/dev/rdsk/$DEVALIAS"p0"
	elif [[ "X$ISA" = "Xsparc" ]] then
		DEVPATH=$UTUNITDIR/dev/rdsk/$DEVALIAS"s2"
	fi


	# $DEVPATH should be a raw device
	if [[ ! -c $DEVPATH ]] then
		DEVALIAS=""
		print $DEVALIAS
		return
	fi

	# Check whether the user has access privileges to this disk
	$UTDISKCHECKCMD $DEVPATH
	if [[ ${?} -ne 0 ]] then
		# utdiskcheck would have printed error message
		DEVALIAS=""
	fi

	# return device name only
	print $DEVALIAS
}


# volcheck
# $1 = device name
#
function dovolcheck
{
	# build raw device path from given name
	DEVICE_ALIAS="`getdevalias $1`"

	if [[ -z $DEVICE_ALIAS ]] then
		print -u2 "$PROGNAME: Invalid device name $1"
		return 1
	fi

	# perform volcheck on backup slice
	# build path by using current session identifier
	# path verified above
	if [[ "X$ISA" = "Xi386" ]] then
		DEVPATH=$UTUNITDIR"/dev/rdsk/"$DEVICE_ALIAS"p0"
	elif [[ "X$ISA" = "Xsparc" ]] then
		DEVPATH=$UTUNITDIR"/dev/rdsk/"$DEVICE_ALIAS"s2"
	fi


	# found valid path to full device
	$UTVOLCHECKCMD -d $DEVPATH

	return $?
}


# eject
# $1 = device name
#
function doeject
{
	# build raw device path from given name
	DEVICE_ALIAS="`getdevalias $1`"

	if [[ -z $DEVICE_ALIAS ]] then
		print -u2 "$PROGNAME: Invalid device name $1"
		return 1
	fi

	# perform eject on backup slice
	# build path by using current session identifier
	# path verified above
	if [[ "X$ISA" = "Xi386" ]] then
		DEVPATH=$UTUNITDIR"/dev/rdsk/"$DEVICE_ALIAS"p0"
	elif [[ "X$ISA" = "Xsparc" ]] then
		DEVPATH=$UTUNITDIR"/dev/rdsk/"$DEVICE_ALIAS"s2"
	fi

	# perform eject operation
	eject $DEVPATH

	return $?
}


# mount partition on mount point
# $1 = partition name
# $2 = mount point (optional)
# $1 must be either full block device path or partition alias only.
# Partial paths or device aliases are unacceptable
#
function domount
{
	# start with device path
	BLOCKDEVPATH=$1

	# check whether unit directory is valid
	if [[ -z $UTUNITDIR ]] then
		print -u2 "$PROGNAME: Invalid Sun Ray session"
		return 1
	fi

	# is this a full block device path
	if [[ ! -b $BLOCKDEVPATH ]] then
		# could be partition alias
		# try prepending with full block path prefix
		BLOCKDEVPATH=$UTUNITDIR/dev/dsk/$1
	fi

	# blockdev path can be in either of 2 forms
	# $UTDEVROOT/dev/dsk/name or $UTUNITDIR/dev/dsk/name
	# always use the $UTUNITDIR form so other utils know
	# what to search for when scanning mnttab

	UNITDEVSUFFIX="`echo $BLOCKDEVPATH \
				| sed -n 's:^.*/\(dev/dsk/.*\):\1:p'`"
	BLOCKDEVPATH=$UTUNITDIR/$UNITDEVSUFFIX
	BLOCKSESSPATH=$UTDEVROOT/$UNITDEVSUFFIX

	# check again
	if [[ ! -b $BLOCKDEVPATH ]] then
		# invalid device name
		print -u2 "$PROGNAME: Invalid partition name $1"
		return 1
	fi

	# check session directory path as well
	if [[ ! -b $BLOCKSESSPATH ]] then
		# invalid partition name
		print -u2 "$PROGNAME: Invalid partition name $1"
		return 1
	fi

	# translate block device path to raw device path
	RAWDEVPATH="`echo $BLOCKDEVPATH \
			| sed -n 's:/dev/dsk/:/dev/rdsk/:p'`"

	if [[ ! -c $RAWDEVPATH ]] then
		print -u2 "$PROGNAME: Invalid partition name $1"
		return 1
	fi

	# Check whether the user has access privileges to this disk
	$UTDISKCHECKCMD $RAWDEVPATH
	if [[ ${?} -ne 0 ]] then
		# utdiskcheck would have printed error message
		return 1
	fi

	# is this partition mounted already
	MNT_PATH="`getmountpoint $BLOCKDEVPATH`"
	if [[ -n $MNT_PATH ]] then
		print -u2 "$1: Device busy"
		return 1
	fi

	# check session directory path as well
	MNT_PATH="`getmountpoint $BLOCKSESSPATH`"
	if [[ -n $MNT_PATH ]] then
		print -u2 "$1: Device busy"
		return 1
	fi

	# if mount point is given, see whether it exists
	# otherwise mount on volume label named directory
	MNT_ARGS=""
	MNT_PATH="$2"
	if [[ -n $MNT_PATH ]] then
		if [[ (-d $MNT_PATH) && (-r $MNT_PATH) && (-w $MNT_PATH) \
			&& (-x $MNT_PATH) ]] then
			# mount path is accessible by user
			MNT_ARGS="-p $MNT_PATH"
		else
			print -u2 "$MNT_PATH: Invalid mount point"
			return 1
		fi
	else
		# set volume label option in mounter command line
		MNT_ARGS="-l"
	fi

	# Look for a filesystem from our list
	STATUS=0
	for FS in $FST_LIST
	do
		FSTYPE="`/usr/lib/fs/$FS/fstyp $RAWDEVPATH`"
		STATUS=$?
		if [[ ( $STATUS -eq 0 ) && ( -n $FSTYPE ) ]] then
			# run mount command
			$UTDOMOUNTCMD -m -f $FSTYPE -b $BLOCKDEVPATH \
						$MNT_ARGS -i $UTUSER_ID
			STATUS=$?
			if [[ $STATUS -eq 0 ]] then
				break
			fi
		fi
	done

	if [[ ( $STATUS -ne 0 ) ]] then
		# try to mount a filesystem from our list
		for FS in $FSM_LIST
		do
			# run mount command
			$UTDOMOUNTCMD -m -f $FS -b $BLOCKDEVPATH $MNT_ARGS \
								-i $UTUSER_ID
			STATUS=$?
			if [[ $STATUS -eq 0 ]] then
				break
			fi
		done
	fi

	return $STATUS
}


# unmount partition
# $1 = mount point
#
function dounmount
{
	# $1 should be full mount path

	MNT_PATH=$1
	if [[ ! -d $MNT_PATH ]] then
		print -u2 "Invalid mount point: $1"
		return 1
	fi

	# is this directory a mount point ?
	BLOCKDEVPATH="`getmountdev $MNT_PATH`"
	if [[ -z $BLOCKDEVPATH ]] then
		print -u2 "Invalid mount point: $1"
		return 1
	fi
	if [[ ! -b $BLOCKDEVPATH  ]] then
		# remove pcfs suffix (one char after colon at end of string)
		BLOCKDEVPATH="`echo $BLOCKDEVPATH | sed -n 's:\(.*\)\:.$:\1:p'`"
		if [[ ! -b $BLOCKDEVPATH  ]] then
			print -u2 "$PROGNAME: Invalid mount point: $1"
			return 1
		fi
	fi
	# translate block device path to raw device path
	RAWDEVPATH="`echo $BLOCKDEVPATH \
			| sed -n 's:/dev/dsk/:/dev/rdsk/:p'`"

	if [[ ! -c $RAWDEVPATH ]] then
		print -u2 "$PROGNAME: Invalid mount point: $1"
		return 1
	fi

	# Check whether the user has access privileges to this disk
	$UTDISKCHECKCMD $RAWDEVPATH
	if [[ ${?} -ne 0 ]] then
		# utdiskcheck would have printed error message
		return 1
	fi

	$UTDOMOUNTCMD -u -p $MNT_PATH -i $UTUSER_ID

	return $?
}


# prepare device for removal
# $1 = device name
#
function doremove
{
	# build raw device path from given name
	DEVICE_ALIAS="`getdevalias $1`"

	if [[ -z $DEVICE_ALIAS ]] then
		print -u2 "$PROGNAME: Invalid device name $1"
		return 1
	fi

	# build path to backup slice by using current session identifier
	# path verified above
	BLOCKDEVPATTERN=$UTUNITDIR"/dev/dsk/"$DEVICE_ALIAS"[ps]"

	# start by assuming device is not busy
	DEVBUSY=""
	UNMOUNT_DONE=""

	# unmount all partitions
	# match UTUNITDIR pattern first
	for PARTNAME in `ls $BLOCKDEVPATTERN* 2>/dev/null`
	do
		MNTPATH="`getmountpoint $PARTNAME`"

		if [[ -z $MNTPATH ]] then
			# try session path
			UNITDEVSUFFIX="`echo $PARTNAME \
				| sed -n 's:^.*/\(dev/dsk/.*\):\1:p'`"
			BLOCKSESSPATH=$UTDEVROOT/$UNITDEVSUFFIX
			MNTPATH="`getmountpoint $BLOCKSESSPATH`"
		fi
		if [[ -n $MNTPATH ]] then
			dounmount $MNTPATH
			if [[ ${?} -ne 0 ]] then
				print -u2 "$PROGNAME: unmount $MNTPATH failed"
				DEVBUSY="1"
			else
				UNMOUNT_DONE="1"
			fi
		fi
	done

	if [[ -n $DEVBUSY ]] then
		return 1
	fi

	return 0
}


# list stale mount points
# root sees all stale mount points on system with -a option
# user sees only current sessions stale mount points
#
function showstale
{
	# check for super user privileges
	if [[ -n $ARG_ALL && -z $CALLERROOT ]] then
		print -u2 "$PROGNAME: -a allowed only for super-user"
		showusage $PROGNAME
		return 1
	fi
	if [[ -n $ARG_ALL ]] then
		# look for all Sun Ray devices
		SUNWUTUNITPREFIX=$SUNWUTUNITS"/"$IEEEPREFIX
	else
		# look for local Sun Ray devices
		# check whether $UTDEVROOT is valid
		if [[ -z $UTUNITDIR ]] then
			print -u2 "$PROGNAME: Invalid Sun Ray session"
			return 1
		fi
		SUNWUTUNITPREFIX="$UTUNITDIR"
	fi
	# search mnttab with unit prefix
	for i in `getallmntdevs $SUNWUTUNITPREFIX`
	do
		# remove pcfs suffix (one char after colon at end of string)
		BLOCKDEVPATH="`echo $i | sed -n 's:\(.*\)\:.$:\1:p'`"
		if [[ -n $BLOCKDEVPATH ]] then
			i=$BLOCKDEVPATH
		fi
		# List all mount points for which block device does not exist
		if [[ ! -b $i ]] then
			MNTPT="`getmountpoint $i`"
			if [[ -d $MNTPT ]] then
				# print busy status
				fuser -cu $MNTPT
			else
				# mount point no longer exists
				print $MNTPT
			fi
		fi
	done

	return 0
}


# print details of device as part of device listing service
# $1 = full block device path (in unit directory format (../IEEE...)
#
function showdevdetails
{
	PARTNAME=$1
	RAWDEVLINK="`echo $PARTNAME \
			| sed -n 's:/dev/dsk/:/dev/rdsk/:p'`"
	$UTDISKCHECKCMD $RAWDEVLINK
	if [[ ${?} -eq 0 ]] then
		# This is an active Sun Ray storage device
		MNTPATH="`getmountpoint $PARTNAME`"

		if [[ -z $MNTPATH && -n $UTUNITDIR ]] then
			# mnttab entry could be in session directory format
			# check session directory path
			UNITDEVSUFFIX="`echo $PARTNAME \
				| sed -n 's:^.*/\(dev/dsk/.*\):\1:p'`"
			BLOCKSESSPATH=$UTDEVROOT/$UNITDEVSUFFIX

			# verify that device exists first
			if [[ -b $BLOCKSESSPATH ]] then
				MNTPATH="`getmountpoint $BLOCKSESSPATH`"
			fi
		fi

		# strip path prefix to get partition alias
		PARTALIAS=${PARTNAME##*/}

		# remove partition suffix to get device alias
		SDEVALIAS=${PARTALIAS%s[0-9]}
		PDEVALIAS=${PARTALIAS%p[0-4]}

		if [[ $SDEVALIAS != $PARTALIAS ]] then
			DEVALIAS=$SDEVALIAS
		elif [[ $PDEVALIAS != $PARTALIAS ]] then
			DEVALIAS=$PDEVALIAS
		fi

		# Note that field size is 16
		# print device alias and pad with space upto field size
		printf "%-16s%-16s%s\n" $DEVALIAS $PARTALIAS $MNTPATH
	fi
}


# print device list
#
function showlist
{
	# check for super user privileges
	if [[ -n $ARG_ALL && -z $CALLERROOT ]] then
		print -u2 "$PROGNAME: -a allowed only for super-user"
		showusage $PROGNAME
		return 1
	fi

	# if all option not specified, must be valid Sun Ray session
	if [[ -z $ARG_ALL && -z $UTUNITDIR ]] then
		print -u2 "$PROGNAME: Invalid Sun Ray session"
		return 1
	fi

	printf "Device          Partition       Mount Path\n"
	printf "------          ---------       ----------\n"

	if [[ -n $ARG_ALL ]] then
		# list all devices on system
		SUNWUTUNIT=$SUNWUTUNITS"/"$IEEEPREFIX

		for PARTNAME in `ls $SUNWUTUNIT*/dev/dsk/* 2>/dev/null`
		do
			showdevdetails $PARTNAME
		done
	else
		# list devices for this session only
		BLOCKDEVPATTERN=$UTUNITDIR"/dev/dsk/"

		for PARTNAME in `ls $BLOCKDEVPATTERN* 2>/dev/null`
		do
			showdevdetails $PARTNAME
		done
	fi

	return 0
}


# print usage message on stderr
# $1 = program name
# $2 = if set, send output to stdout (default is stderr)
#
function showusage
{
	if [[ -n $2 ]] then
		# if $2 is set, send output to stdout
		OUT=""
	else
		# else send output to stderr
		OUT="-u2"
	fi
	print $OUT "usage:"
	print $OUT "\t $1 -h"
	print $OUT "\t $1 { -l | -s } [-a]"
	print $OUT "\t $1 { -c | -e | -r } device_name"
	print $OUT "\t $1 -m partition_name [ -p mount_path ]"
	print $OUT "\t $1 -u mount_point"
}


# print help message
# $1 = program name
#
function showhelp
{
	# show usage message on stdout
	showusage $1 "1"

	# explain options
	print "Options:"
	print " -c	# check device for presence of media"
	print " -e	# eject media from removable media devices"
	print " -h	# print this help message"
	print " -l	# list devices on current session"
	print " -l -a	# list all devices on system (root only)"
	print " -m	# mount partition_name on default mount point"
	print " -p	# (with -m) mount partition_name on given mount_path"
	print " -r	# prepare device for removal"
	print " -s	# list stale mount points"
	print " -s -a	# list all stale mount points on system (root only)"
	print " -u	# unmount mount_point"
}


# only one operation allowed as command line option
# check whether caller has issued multiple options
# $1 = option
#
function set_op
{
	if [[ -z $OPERATION ]] then
		OPERATION=$1
	elif [[ $OPERATION != $1 ]] then
		print -u2 "conflicting options  -${OPERATION} and -${1}"
		showusage $PROGNAME
		exit 1
	fi
}


# main
#

PROGNAME="`basename $0`"

# setup global variables

# Sun Ray paths
UTDISKCHECKCMD="/etc/opt/SUNWut/basedir/lib/utdiskcheck"
UTDOMOUNTCMD="/etc/opt/SUNWut/basedir/lib/utdomount"
UTVOLCHECKCMD="/etc/opt/SUNWut/basedir/lib/utvolcheck"
SUNWUTPATH="/tmp/SUNWut"
SUNWUTSESSIONS=$SUNWUTPATH"/sessions"
SUNWUTUNITS=$SUNWUTPATH"/units"
SUNWUTMNT=$SUNWUTPATH"/mnt"

# save $UTDEVROOT if it is valid
if [[ -d $UTDEVROOT && -r $UTDEVROOT && -x $UTDEVROOT ]]
then
	UTUNITDIR=`(cd $UTDEVROOT; /bin/pwd)`
fi

# is caller root (some options are valid only for root)
if [[ "`$IDCMD -u -r`" = "0" ]] then
	CALLERROOT="1"
fi

# user name and id
UTUSERNAME="`logname`"
UTUSER_ID="`$IDCMD -u $UTUSERNAME`"

# caller should have a valid user name to progress
# these variables are used to build path names, so they should be valid
if [[ ( -z $UTUSERNAME ) || ( -z $UTUSER_ID ) ]] then
	print -u2 "$PROGNAME: Invalid user"
	exit 1
fi

ISA=`uname -p`
if [[ -z $ISA ]] then
	print -u2 "$PROGNAME: Unknown isa"
	exit 1
fi


# parse options
#
while getopts :ac:e:hlm:p:r:su: vopts
do
	case $vopts in
	a)	if [[ -z $ARG_ALL ]] then
			ARG_ALL="1"
		else
			print -u2 "only one instance of -a is allowed"
			showusage $PROGNAME
			exit 1
		fi
		;;
	c)	set_op "c"
		ARG_DEV="$OPTARG"
		;;
	e)	set_op "e"
		ARG_DEV="$OPTARG"
		;;
	h)	set_op "h"
		;;
	l)	set_op "l"
		;;
	m)	set_op "m"
		ARG_DEV="$OPTARG"
		;;
	p)	if [[ -z $ARG_MNTPATH ]] then
			ARG_MNTPATH="$OPTARG"
		else
			print -u2 "only one instance of -p is allowed"
			showusage $PROGNAME
			exit 1
		fi
		;;
	r)	set_op "r"
		ARG_DEV="$OPTARG"
		;;
	s)	set_op "s"
		;;
	u)	set_op "u"
		ARG_DEV="$OPTARG"
		;;
	\?)	print -u2 "$PROGNAME: Invalid option -$OPTARG"
		showusage $PROGNAME
		exit 1
		;;
	:)	print -u2 "$PROGNAME: Required argument missing"
		showusage $PROGNAME
		exit 1
		;;
	esac
done

# reset param numbering to catch junk args
shift `expr $OPTIND - 1`

if [[ $# -gt 0 ]] then
	print -u2 "$PROGNAME: extra arguments found"
	print -u2 "\tArguments: $*"
	showusage $PROGNAME
	exit 1
fi

# check for illegal option combinations
case $OPERATION in
"")	print -u2 "$PROGNAME: option required"
	showusage $PROGNAME
	exit 1
	;;
c | e | h | m | r | u)
	# -a not allowed for this list
	if [[ -n $ARG_ALL ]] then
		print -u2 "$PROGNAME: -a not valid with -$OPERATION"
		showusage $PROGNAME
		exit 1
	fi
	;;
esac
case $OPERATION in
c | e | h | l | r | s)
	# -p not allowed for this list
	if [[ -n $ARG_MNTPATH ]] then
		print -u2 "$PROGNAME: -p not valid with -$OPERATION"
		showusage $PROGNAME
		exit 1
	fi
	;;
esac

# execute command
case $OPERATION in
c)	dovolcheck $ARG_DEV
	;;

e)	doremove $ARG_DEV
	if [[ ${?} -eq 0 ]] then
		doeject $ARG_DEV
	fi
	;;

h)	showhelp $PROGNAME
	;;

l)	showlist
	;;

m)	domount $ARG_DEV $ARG_MNTPATH
	;;

r)	doremove $ARG_DEV
	if [[ ${?} -eq 0 ]] then
		print "$ARG_DEV is ready for unplugging"
	fi
	;;

s)	showstale
	;;

u)	dounmount $ARG_DEV
	;;
esac

exit $?
