#!/bin/ksh -p
#
# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#
# ident "@(#)ut_disk_clean.ksh	1.12	07/06/18 SMI"
#
#
# This is a clean script for removable disks connected to a Sun Ray DTU.
# 
# Following is the syntax for calling the script:
#	scriptname [-s|-f|-i|-I] devicename [-A|-D] username zonename zonepath
#
#    	-s for standard cleanup by a user
# 	-f for forced cleanup by an administrator
# 	-i for boot-time initialization (when the system is booted with -r) 
# 	-I to suppress error/warning messages; the script is run in the '-i'
#	   mode
#
# $1:	devicename - device to be allocated/deallocated, e.g., sr0
#
# $2:	-A if cleanup is for allocation, or -D if cleanup is for deallocation.
#
# $3:	username - run the script as this user, rather than as the caller.
#
# $4:	zonename - zone in which device to be allocated/deallocated
#
# $5:	zonepath - root path of zonename
#
# A clean script for a removable media device should prompt the user to 
# insert correctly labeled media at allocation time, and ensure that the
# media is ejected at deallocation time.
#
# Unless the clean script is being called for boot-time
# initialization, it may communicate with the user via stdin and
# stdout.  To communicate with the user via CDE dialogs, create a
# script or link with the same name, but with ".windowing" appended.
# For example, if the clean script specified in device_allocate is
# /etc/security/xyz_clean, that script must use stdin/stdout.  If a
# script named /etc/security/xyz_clean.windowing exists, it must use
# dialogs.  To present dialogs to the user, the dtksh script
# /etc/security/lib/wdwmsg may be used.
#
# This particular script, ut_disk_clean, will work using stdin/stdout, or
# using dialogs.  A symbolic link ut_disk_clean.windowing points to
# ut_disk_clean.
#
#  EXIT VALUES
#     The following exit values are returned:
#
#     0             Successful completion.
#     1             Any error.
#     2             A system error.
#     3             Mount failed (not an error).
#     4             Media sucessfully mounted
#
 
# ####################################################
# ################  Local Functions  #################
# ####################################################
 
#
# Set up for windowing and non-windowing messages
#
msg_init()
{
    if [ `basename $0` != `basename $0 .windowing` ]; then
	WINDOWING="yes"
	case $VOLUME_MEDIATYPE in
	   cdrom) TITLE="CD-ROM Drive";;
	  rmdisk) TITLE="Removable Disk";;
	  floppy) TITLE="Floppy Disk";;
	     zip) TITLE="Zip Drive";;
	     jaz) TITLE="Jaz Drive";;
	     dvd) TITLE="DVD Drive";;
	    disk) TITLE="Removable Disk";;
	       *) TITLE="Removable Disk";;
	esac
	
	if [ "$MODE" = "allocate" ]; then
	    TITLE="$TITLE Allocation"
	else
	    TITLE="$TITLE Deallocation"
	fi
    else
	WINDOWING="no"
    fi
}

#
# Display a message for the user.  For windowing, user must press OK button 
# to continue. For non-windowing, no response is required.
#
msg() {
    if [ "$WINDOWING" = "yes" ]; then
	$WDWMSG "$*" "$TITLE" OK
    elif [ "$silent" != "y" ]; then
	echo "$*" > /dev/${MSGDEV}
    fi
}

ok_msg() {
	if [ "$WINDOWING" = "yes" ]; then
		$WDWMSG "$*" "$TITLE" READY
	else
		form=`gettext "Media in %s is ready. Please store safely."`
		printf "${form}\n" $PROG $DEVICE > /dev/{MSGDEV}
	fi
}

error_msg() {
	if [ "$WINDOWING" = "yes" ]; then
		$WDWMSG "$*" "$TITLE" ERROR
	else
		form=`gettext "%s: Error cleaning up device %s."`
		printf "${form}\n" $PROG $DEVICE > /dev/${MSGDEV}
	fi
}

#
# Ask the user an OK/Cancel question.  Return 0 for OK, 1 for Cancel.
#
okcancel() {
    if [ "$WINDOWING" = "yes" ]; then
	$WDWMSG "$*" "$TITLE" OK Cancel
    elif [ "$silent" != "y" ]; then
	get_reply "$* (y to continue, n to cancel) \c" y n
    fi
}

#
# Ask the user an Yes/No question.  Return 0 for Yes, 1 for No
#
yesno() {
    if [ "$WINDOWING" = "yes" ]; then
	$WDWMSG "$*" "$TITLE" Yes No
    elif [ "$silent" != "y" ]; then
	get_reply "$* (y/n) \c" y n
    fi
}

#
# Display an error message, put the device in the error state, and exit.
#
error_exit() {
	if [ "$silent" != "y" ]; then
		msg "$1" "$2" \
		    "\n\nDevice has been placed in allocation error state." \
		    "\nPlease inform system administrator."
	fi
	exit ${exit_ERROR}
}

#
# Clean up state, then return with cancel exit code
#
cancel_exit() {

	if [ "$VOLUME_DEVICE" != "" ] ; then
		chown root:root $VOLUME_DEVICE
		chmod 0 $VOLUME_DEVICE
	fi

	exit ${exit_CANCEL}
}

#
# get_reply prompt choice ...
#
get_reply() {
	prompt=$1; shift
	while true
	do
		echo $prompt > /dev/tty
		read reply
		i=0
		for choice in $*
		do
			if [ "$choice" = "$reply" ]
			then
				return $i
			else
				i=`expr $i + 1`
			fi
		done
	done
}

#
# Check if media is present in a device.
#
#    calling:
#      $1 - path to block device to check (we convert to raw device name)
#
#    returns:
#	0 - media is in the drive
#	1 - media is not in the drive (or has been ejected)
#	2 - error
#	3 - device is gone
#
check_media_present()
{
	raw_device="`echo $1 | sed 's/\/dsk\//\/rdsk\//g'`"

	media_status="`$UTVOLCHECK -t $UTVOLCHECK_POLL_TIME -i $UTVOLCHECK_POLL_INTERVAL -d $raw_device | awk '{print $2 $3 $4}'`"

	case "$media_status" in
	       "hasmedia") return 0;;
	     "hasnomedia") return 1;;
	     "Nosuchfile") return 3;;
	esac

	return 2

}

# find mount path
# $1 = block device path
#
function getmountpoint
{

	TMPMOUNTPOINT="`mount | nawk '($p == NBLOCKDEV) { print $d }' p=3 d=1 NBLOCKDEV=$1`"

	if [[ -z $TMPMOUNTPOINT && "X$OS" = "XSunOS" && "X$ISA" = "Xi386" ]] then
		# check the p1 device
		dev="$1"
		dev="`echo $dev | sed 's/p0$/p1/g'`"
		TMPMOUNTPOINT="`mount | nawk '($p == NBLOCKDEV) { print $d }' p=3 d=1 NBLOCKDEV=$dev`"
	fi

	if [[ -z $TMPMOUNTPOINT && "X$OS" = "XSunOS" && "X$ISA" = "Xsparc" ]] then
		# look for pcfs suffixes
		for PSUFFIX in :c :d :e :f
		do
			TMPMOUNTPOINT="`mount | nawk '($p == NBLOCKDEV) { print $d }' p=3 d=1 NBLOCKDEV=$1$PSUFFIX`"
			if [[ -n $TMPMOUNTPOINT ]] then
				break
			fi
		done
	fi

	print $TMPMOUNTPOINT
}

#
# Allocate a device.
#
do_allocate()
{

	# create the mount point in the local zone and set it to be
	# owned by the user.
	# utmountd will create the tree below this point.
	# XXX we should really clean this all up when we deallocate
	# XXX the device, why doesn't utmountd and friends do the
	# XXX clean up?
	mkdir -p $ZONE_MOUNT_ROOT/$USERNAME
	chown ${UID}:${GID} $ZONE_MOUNT_ROOT/$USERNAME
	chmod 700 $ZONE_MOUNT_ROOT/$USERNAME

	# change the ownership of the block device so that the user
	# owns it, mode doesn't matter, only UID:GID. this is done
	# here to work around a bug in the TX DA framework where the
	# device itself is allocated but not yet owned by the user,
	# this causes utmountd to complain and not do the mount.
	# XXX we might want to modify utmountd to not check the
	# XXX UID:GID of the block device, although if we move to
	# XXX using rmmount, this point will be moot
	chown ${UID}:${GID} $VOLUME_DEVICE

	# Determine if media is in drive
	done=0
	while [ $done != 1 ]
	  do
	    check_media_present $VOLUME_DEVICE
	    eject_status="$?"
	    case $eject_status in
	        1) # Media is not in drive
		okcancel "Insert media in ${DEVICE}."
		    if [ $? != 0 ]; then
			cancel_exit	# no error, but no mount either
		    fi;;
	      2|3) # Error
		    error_exit "${DEVICE} :" "Error checking for media in drive.";;
	        0) # Media is in the drive
		    done=1;;
	    esac
	  done

	# now call utmntpipe to tell utmountd about this device
	# this will cause the device to be mounted in the local
	# zone under a mount point owned by the user
	$UTMNTPIPE -C device -m $I_CTLMIN -U $UID -X $I_XID -d $I_DEVROOT -M $I_MEDIA -Z $ZONE_MOUNT_ROOT
	if [ $? != 0 ] ; then
	    error_exit "${DEVICE} :" "Error mounting device."
	else
	    done=20	# max wait time for mount to happen
	    while [ $done -gt 0 ]
	      do
	        MNTPT="`getmountpoint $VOLUME_DEVICE`"
	        if [ -z $MNTPT ] ; then
	            sleep 1
		    done="`expr $done - 1`"
	        else
		    done=0
	            EXIT_STATUS=${exit_CLEANMOUNT}
		    LZ_MNTPT="`echo $MNTPT | awk -F/ '{print "/"$5"/"$6"/"$7}'`"
	            msg "${DEVICE} : Allocated at label ${VOLUME_ZONE_LABEL}." \
		    "\nMounted at ${LZ_MNTPT}."
		fi
	      done

	      if [ -z $MNTPT ] ; then
	            error_exit "${DEVICE} :" "Error mounting device."
	      fi
	fi

}

#
# Deallocate a device
#
do_deallocate()
{

	# call utmntpipe to tell utmountd to unmount this device
	# XXX may need to retry this if we get an error since utmountd
	# XXX doesn't accept more than a single connection on the socket
	$UTMNTPIPE -C detach -m $I_CTLMIN -U $UID -X $I_XID -d $I_DEVROOT -M $I_MEDIA -Z $ZONE_MOUNT_ROOT
	mount_status="$?"

	case $mount_status in

	    1) # error
	        error_exit "${DEVICE} :" "Error unmounting device" ;;
	    0) # not mounted, eject the media
	    	export UTDEVROOT=$I_DEVROOT
		DEV_EJECT_NAME=${I_MEDIA}${I_CTLMIN}
	        if [ "$FLAG" = "f" ] ; then
	            eject_msg="`$UTEJECT $DEV_EJECT_NAME 2>&1`"
                else
		    eject_msg="`$UTEJECT $DEV_EJECT_NAME 2>&1`"
		fi

		check_media_present $VOLUME_DEVICE

		eject_status="$?"

		case $eject_status in

		    0) # Media didn't eject
			 msg "${DEVICE} : Error ejecting the media or device" \
			 "\nPlease manually eject the media if necessary." ;;
			 # EXIT_STATUS=${exit_ERROR} ;;
		    1) # Media has been ejected
		    	 msg "${DEVICE} : Deallocated from label ${VOLUME_ZONE_LABEL}." \
			 "\nPlease remove the media or device." ;;
		    2) # Error getting media present status
		    	 msg "${DEVICE} : Error determining device status"
			 EXIT_STATUS=${exit_ERROR} ;;
		    3) # No device (not an error)
		    	 msg "${DEVICE} : No device or media to eject" ;;

		esac
	esac
}

#
# Reclaim a device
#
do_init()
{
	# eject media unconditionally, this will do no harm if no media present
	export UTDEVROOT=$I_DEVROOT
	DEV_EJECT_NAME=${I_MEDIA}${I_CTLMIN}

	if [ "$FLAG" = "f" ] ; then
		eject_msg="`$UTEJECT $DEV_EJECT_NAME 2>&1`"
	else
		eject_msg="`$UTEJECT $DEV_EJECT_NAME 2>&1`"
	fi

	check_media_present $VOLUME_DEVICE

	eject_status="$?"

	case $eject_status in

	    1) # Media has been ejected
		 if [ "$silent" != "y" ]; then
		 	ok_msg
		 fi
		 exit ${exit_SUCCESS} ;;
	    *) # Error
		 if [ "$silent" != "y" ]; then
		 	error_msg
		 fi
		 msg "${DEVICE} :" "Error ejecting the media or device" \
		 "\nPlease remove the media or device." ;;
		 # exit ${exit_ERROR} ;;

	esac
}

# ####################################################
# ################ Begin main program ################
# ####################################################

# define script exit return values
exit_SUCCESS=0
exit_ERROR=1
exit_SYSERROR=2
exit_CANCEL=3
exit_CLEANMOUNT=4

VOLUME_DEVICE=""

trap "cancel_exit" INT TERM QUIT TSTP ABRT

# setup global variables
PATH="/usr/bin:/usr/sbin"
# Set OS specific variables
OS=`uname -s`
ISA=`uname -p`
MODE="allocate"
SILENT=n
WDWMSG="/etc/security/lib/wdwmsg"
VOLUME_ZONE_PATH="/"
USAGE="Usage: ut_disk_clean [-s|-f|-i|-I] devicename -[A|D] [username] [zonename] [zonepath]"
UTMNTPIPE=/opt/SUNWut/lib/utmntpipe
UTEJECT=/opt/SUNWut/bin/uteject
UTVOLCHECK=/opt/SUNWut/lib/utvolcheck
UTVOLCHECK_POLL_TIME=1		# XXX figure out why utvolcheck doesn't like other values
UTVOLCHECK_POLL_INTERVAL=3	# XXX figure out why utvolcheck doesn't like other values
EXIT_STATUS=${exit_SUCCESS}
MACH=`uname -p`
FLAG=i

	#
	# Parse the command line arguments
	#
	while getopts ifsI c
	  do
	    case $c in
	    	i) FLAG=$c;;
		f) FLAG=$c;;
		s) FLAG=$c;;
		I) FLAG=i
		   silent=y;;
	       \?) echo $USAGE
	       exit ${exit_ERROR};;
	    esac
	  done

	shift `expr $OPTIND - 1`

	# get the non-switch arguments from the CLI
	DEVICE="$1"
	CMD="$2"
	USERNAME="$3"
	VOLUME_ZONE_NAME="$4"
	VOLUME_ZONE_PATH="$5"
	VOLUME_ZONE_LABEL="`getlabel -S $VOLUME_ZONE_PATH | nawk -F ':\t' '{print $2}'`"

	# setup mode of this script - allocate or deallocate
	MODE="deallocate"
	if [ "$CMD" = "-A" ]; then
	    MODE="allocate"
	elif [ "$CMD" = "-D" ]; then
	    MODE="deallocate"
	fi

	# get the device_maps information
	MAP=`/usr/sbin/list_devices -s -l $DEVICE`
	ALLDEVINFO=`/usr/sbin/list_devices -s -a -l $DEVICE`
	FILES=`echo $MAP | cut -f4 -d:`
	DEVFILE=`echo $FILES | cut -f1 -d" "`

	# Set VOLUME_ variables that are inputs to utmntpipe.
	# map device types from database types to types that
	# we use internally.
	# if a media type is specified in the device entry in
	# the TX DA database, that will override the value of
	# VOLUME_MEDIATYPE that is set here.
	VOLUME_DEVICE=`echo $FILES | cut -f2 -d" "`
	MEDIATYPE=`echo $MAP | cut -f3 -d: | cut -f2 -d" "`
	if [ "$MEDIATYPE" = "sr" ]; then
	    VOLUME_MEDIATYPE="cdrom"
	elif [ "$MEDIATYPE" = "fd" ]; then
	    VOLUME_MEDIATYPE="floppy"
	elif [ "$MEDIATYPE" = "rmdisk" ]; then
	    VOLUME_MEDIATYPE="rmdisk"
	fi

	# parse the output of list_devices for our device to extract
	# the KVPs that we are interested in - these will be fed to
	# utmntpipe when we do a mount or unmount
	devinfo="`echo $ALLDEVINFO | sed 's/;/:/g'`"

	oldIFS="$IFS"
	IFS=:

	# go through each KVP and extract their values.
	for KVP in $devinfo
	  do

	    key="`echo $KVP | awk -F= '{print $1}'`"
	    value="`echo $KVP | awk -F= '{print $2}'`"

	    case $key in
	    	   xdpy) I_XID=$value ;;
	        devroot) I_DEVROOT=$value ;;
	          media) I_MEDIA=$value ; VOLUME_MEDIATYPE=$I_MEDIA ;;
	         ctlmin) I_CTLMIN=$value ;;
	    esac
	  done

	IFS="$oldIFS"

	# initialize the message subsystem
	msg_init

	# get user and group IDs of the user for which we are doing
	# the allocation or deallocation on beahlf of
	UID="`/usr/xpg4/bin/id -u $USERNAME`"
	GID="`/usr/xpg4/bin/id -g $USERNAME`"

	# this is the root of where all removable media devices will be
	# mounted in the local zone.
	ZONE_MOUNT_ROOT="${VOLUME_ZONE_PATH}/rmdisk"

	# perform the requested operation
	if [ "$MODE" = "allocate" ]; then
	    MSGDEV=tty
	    do_allocate
	else
	    if [ "$FLAG" = "i" ] ; then
	        MSGDEV=console
		do_init
	    else
	    	MSGDEV=tty
		# XXX also check for any outstanding instances of this
		# XXX script that may be running for this device, since
		# XXX the script could be blocked in a GUI popup, which
		# XXX will hose the whole TX DA subsystem.
		# XXX This would be the case if a user had selected a
		# XXX device for allocation or deallocation and the GUI
		# XXX had put up a popup that the user had to respond
		# XXX to, and they left their desktop in this state
		# XXX rather than dismissing the popup. The problem is
		# XXX that TX uses a global lock for the database and
		# XXX that lock is aquired before this script is invoked.
		# XXX The comment here is to remind me to implement a
		# XXX work-around in the script in case TX doesn't have
		# XXX a fix anytime soon.
		do_deallocate
	    fi
	fi

exit $EXIT_STATUS

