#! /usr/bin/sh
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
#
# This is a clean script for removable disks
# 
# 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, disk_clean, will work using stdin/stdout, or
# using dialogs.  A symbolic link disk_clean.windowing points to
# disk_clean.
#
 
# ####################################################
# ################  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";;
	  rmdisk)  TITLE="Removable Disk";;
	  floppy)  TITLE="Floppy";;
	  *)       TITLE="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 "$2" "$3" \
		    "\n\nDevice has been placed in allocation error state." \
		    "\nPlease inform system administrator."
	fi
	exit 1
}

#
# 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
}

#
# Find the first disk slice containing a file system
#
find_fs()
{
	# The list of files in device_maps(4) is in an unspecified order.
	# To speed up the fstyp(1M) scanning below in most cases, perform
	# the search for filesystems as follows:
	# 1) Select only block device files of the form "/dev/dsk/*".
	# 2) Sort the list of files in an order more likely to yield
	#    matches: first the fdisk(1M) partitions ("/dev/dsk/cNtNdNpN")
	#    then the format(1M) slices ("/dev/dsk/cNtNdNsN"), in ascending
	#    numeric order within each group.
	DEVall="`echo $FILES | \
	    /usr/bin/tr ' ' '\n' | \
	    /usr/bin/sed '/^\/dev\/dsk\//!d; s/\([sp]\)\([0-9]*\)$/ \1 \2/;' | \
	    /usr/bin/sort -t ' ' -k 2,2d -k 3,3n | \
	    /usr/bin/tr -d ' '`"
	for DEVn in $DEVall ; do
		fstyp_output="`/usr/sbin/fstyp -a $DEVn 2>&1`"
		if [ $? = 0 ]; then
			FSPATH=$DEVn
			gen_volume_label="`echo "$fstyp_output" | \
			    sed -n '/^gen_volume_label: .\(.*\).$/s//\1/p'`"
			if [ "$gen_volume_label" != "" ]; then
				FSNAME="`echo $gen_volume_label | \
				    /usr/xpg4/bin/tr '[:upper:] ' '[:lower:]_'`"
			fi
			# For consistency, hsfs filesystems detected at
			# /dev/dsk/*p0 are mounted as /dev/dsk/*s2
			FSTYPE=`echo "$fstyp_output" | /usr/bin/head -1`
			if [ "$FSTYPE" = hsfs -a \
			    `/usr/bin/expr $FSPATH : '.*p0'` -gt 0 ]; then
				FSPATH=`echo $FSPATH | /usr/bin/sed 's/p0$/s2/'`
			fi
			return
		fi
	done
}

#
# Find all mountpoints in use for a set of device special files.
# Usage: findmounts devpath ...
#

findmounts() {
	/usr/xpg4/bin/awk -f - -v vold_root="$VOLD_ROOT" -v devs="$*" /etc/mnttab <<\
	    "ENDOFAWKPGM"
	BEGIN {
		split(devs, devlist, " ");
		for (devN in devlist) {
			dev = devlist[devN];
			realdevlist[dev] = 1;
			sub(/.*\//, "", dev);
			sub(/s[0-9]$/, "", dev);
			if (vold_root != "") {
				vold_dir[vold_root "/dev/dsk/" dev] = 1;
				vold_dir[vold_root "/dev/rdsk/" dev] = 1;
			}
		}
	}

	{
		for (dev in realdevlist) {
			if ($1 == dev) {
				mountpoint = $2;
				print mountpoint;
			}
		}
		for (dev in vold_dir) {
			if (substr($1, 1, length(dev)) == dev) {
				mountpoint = $2;
				print mountpoint;
			}
		}
	}
ENDOFAWKPGM
}

#
# Allocate a device.
# Ask the user to make sure the disk is properly labeled.
# Ask if the disk should be mounted.
#
do_allocate()
{
	if [ $VOLUME_MEDIATYPE = floppy ]; then
		# Determine if media is in drive
		eject_msg="`eject -q $DEVFILE 2>&1`"
		eject_status="$?"
		case $eject_status in
		1) # Media is not in drive
			okcancel "Insert disk in $DEVICE."
			if [ $? != 0 ]; then
				exit 0
			fi;;
		3) # Error 
			error_exit $DEVICE \
			    "Error checking for media in drive.";;
		esac
	else
		okcancel "Insert disk in $DEVICE."
		if [ $? != 0 ]; then
			exit 0
		fi
	fi
    
	yesno "Do you want $DEVICE mounted?"
	if [ $? != 0 ]; then
		exit 0
	fi

	if [ $VOLUME_MEDIATYPE = cdrom -o $VOLUME_MEDIATYPE = rmdisk ]; then
		# Get the device path and volume name of a partition
		find_fs
		if [ "$FSPATH" != "" ]; then
			VOLUME_PATH=$FSPATH	
		fi
		if [ "$FSNAME" != "" ]; then
			VOLUME_NAME=$FSNAME
		fi
	fi
	VOLUME_ACTION=insert

	# Give ourself write permission on device file so file system gets
	# mounted read/write if possible.
	# rmmount only cares about permissions not user...
	chown $VOLUME_USER $VOLUME_PATH
	chmod 700 $VOLUME_PATH

	# Do the actual mount.  VOLUME_* environment variables are inputs to
	# rmmount.
	rmmount_msg="`/usr/sbin/rmmount 2>&1`"
	rmmount_status="$?"
	if [ $rmmount_status -eq 0 ]; then
		EXIT_STATUS=$CLEAN_MOUNT
	elif [ $rmmount_status -gt 0 -a $VOLUME_MEDIATYPE != cdrom ]; then
		# Try again in readonly mode. cdrom is always mounted ro, so
		# no need to try again.
		echo "Read-write mount of $DEVICE failed. Mounting read-only."
		VOLUME_ACTION=remount; export VOLUME_ACTION
		VOLUME_MOUNT_MODE=ro; export VOLUME_MOUNT_MODE
		`/usr/sbin/rmmount`
		if [ $? -eq 0 ]; then
			EXIT_STATUS=$CLEAN_MOUNT
		fi
	fi

	# Set permissions on directory used by vold, sdtvolcheck, etc.
	if [ -d /tmp/.removable ]; then
		chown root /tmp/.removable
		chmod 777 /tmp/.removable
	fi
}


do_deallocate()
{
	if [ $VOLUME_MEDIATYPE = cdrom -o $VOLUME_MEDIATYPE = rmdisk ]; then
		if [ -h /$VOLUME_MEDIATYPE/$DEVICE ]; then
			# Get the device path and volume name of a partition
			VOLUME_PATH=`ls -l /$VOLUME_MEDIATYPE/$DEVICE|\
			    cut -d '>' -f2`
			VOLUME_DEVICE=`mount -p|grep $VOLUME_PATH|\
			    cut -d ' ' -f1`
		fi
	fi

	if [ -d "$VOLUME_PATH" ]; then
		VOLUME_ACTION=eject
		# Do the actual unmount.
		# VOLUME_* environment variables are inputs to rmmount.
		rmmount_msg="`/usr/sbin/rmmount 2>&1`"
		rmmount_status="$?"

		# Remove symbolic links to mount point
		for name in /$VOLUME_MEDIATYPE/*; do
			if [ -h $name ]; then
				target=`ls -l $name | awk '{ print $NF; }'`
				target_dir=`dirname $target`
				target_device=`echo $target_dir | \
				    sed -e 's/^.*-\(.*\)$/\1/'`
				if [ "$target_device" = "$DEVICE" ]; then
					rm -f $name
				fi
			fi
		done
	else
		rmmount_status=0
	fi

	case $rmmount_status in
	1) # still mounted
		error_exit $DEVICE "Error unmounting $DEVICE" "$rmmount_msg";;
	0) # not mounted
		# Eject the media
		if [ "$FLAG" = "f" ] ; then
			eject_msg="`eject -f $DEVICE 2>&1`"
		else
			eject_msg="`eject $DEVICE 2>&1`"
		fi
		eject_status="$?"
		case $eject_status in
		0|1|4) # Media has been ejected
			case $VOLUME_MEDIATYPE in
			floppy|cdrom|rmdisk)
				msg "Please remove the disk from $DEVICE.";;
			esac;;
		3) # Media didn't eject
			msg $DEVICE "Error ejecting disk from $DEVICE" \
			    "$eject_msg";;
		esac
	esac
}

#
# Reclaim a device
#
do_init()
{
	eject_msg="`eject -f $DEVICE 2>&1`"
	eject_status="$?"

	case $eject_status in
	0) # Media has been ejected 
		if [ "$silent" != "y" ]; then
			ok_msg
		fi
		exit 0;;
	1) # Media not ejected
		if [ "$silent" != "y" ]; then
			error_msg
		fi
		exit 0;;
	3) # Error 
		if [ "$silent" != "y" ]; then
			error_msg
		fi
		msg $DEVICE "Error ejecting disk from $DEVICE" \
		"$eject_msg"
		exit 2;;
	esac
}


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

trap "" INT TERM QUIT TSTP ABRT

PATH="/usr/bin:/usr/sbin"
MODE="allocate"
SILENT=n
WDWMSG="/etc/security/lib/wdwmsg"
VOLUME_ZONE_PATH="/"
USAGE="Usage: disk_clean [-s|-f|-i|-I] devicename -[A|D] [username] [zonename] [zonepath]"
EXIT_STATUS=0
CLEAN_MOUNT=4
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 1;;
      esac
done

shift `expr $OPTIND - 1`

DEVICE=$1
MODE="deallocate"
if [ "$2" = "-A" ]; then
	MODE="allocate"
elif [ "$2" = "-D" ]; then
	MODE="deallocate"
fi

#get the device_maps information
MAP=`/usr/sbin/list_devices -s -l $DEVICE`
FILES=`echo $MAP | cut -f4 -d:`	# e.g., /dev/dsk/c0t6d0s0 /dev/dsk/c0t6d0s1 ...
DEVFILE=`echo $FILES | cut -f1 -d" "` 		# e.g., "/dev/dsk/c0t6d0s0"

# Set VOLUME_ variables that are inputs to rmmount

VOLUME_DEVICE=`echo $FILES | cut -f2 -d" "` 	# e.g., "/dev/dsk/c0t6d0s1"
MEDIATYPE=`echo $MAP | cut -f3 -d: | cut -f2 -d" "`
					 	# e.g., "cdrom" or "floppy"
if [ "$MEDIATYPE" = "sr" ]; then
	VOLUME_MEDIATYPE="cdrom"
elif [ "$MEDIATYPE" = "fd" ]; then
	VOLUME_MEDIATYPE="floppy"
elif [ "$MEDIATYPE" = "rmdisk" ]; then
	VOLUME_MEDIATYPE="rmdisk"
fi

VOLUME_PATH=$DEVFILE				# e.g., "/dev/dsk/c0t6d0s0"
if [ "$MACH" = "i386" ] && [ "$MEDIATYPE" = "rmdisk" ]; then
	VOLUME_PATH=`echo $DEVFILE | sed -e 's/s0/p0/'`
fi

SYMDEV=`echo $DEVICE | sed -e 's/_//'`		# e.g., "cdrom" or "floppy"
SYMNUM=`echo $SYMDEV | sed -e 's/[a-z]*//g'`
SYMDEV=`echo $SYMDEV | sed -e 's/[0-9]*//g'`
if [ "$SYMDEV" = "sr" ]; then
	VOLUME_SYMDEV="cdrom"$SYMNUM
elif [ "$SYMDEV" = "fd" ]; then
	VOLUME_SYMDEV="floppy"$SYMNUM
elif [ "$SYMDEV" = "rmdisk" ]; then
	VOLUME_SYMDEV="rmdisk"$SYMNUM
else
	VOLUME_SYMDEV=$SYMDEV$SYMNUM
fi

VOLUME_ZONE_NAME=$4

VOLUME_ZONE_PATH=$5

if [ "$MODE" = "allocate" ]; then
	if [ -n "$3" ]; then			# e.g., "joeuser"
		VOLUME_USER=$3
	else
		VOLUME_USER=`/usr/xpg4/bin/id -u -nr`
	fi
else
	# If there's a directory for the device under /<mediatype>, get the 
	# user name from there, to use in cleaning up that directory. Otherwise,
	# the user name isn't actually used in deallocation.
	if [ -d ${VOLUME_ZONE_PATH}/${VOLUME_MEDIATYPE}/*-${DEVICE} ]; then
		VOLUME_USER=`ls -ld ${VOLUME_ZONE_PATH}/${VOLUME_MEDIATYPE}/*-${DEVICE} | awk '/^d/{print $3}'`
	else
		if [ -n "$3" ]; then
			VOLUME_USER=$3
		else
			VOLUME_USER=`/usr/xpg4/bin/id -u -nr`
		fi
	fi
fi    

VOLUME_NAME=unnamed_${VOLUME_MEDIATYPE}
					# e.g., "joeuser-cdrom0/unnamed_cdrom"

if [ "$VOLUME_MEDIATYPE" = "rmdisk" ]; then
	VOLUME_PCFS_ID=1
else
	VOLUME_PCFS_ID=
fi

export VOLUME_ACTION VOLUME_DEVICE VOLUME_MEDIATYPE VOLUME_NAME VOLUME_PCFS_ID
export VOLUME_PATH VOLUME_SYMDEV VOLUME_USER VOLUME_ZONE_NAME VOLUME_ZONE_PATH

USERDIR=${VOLUME_USER}-${DEVICE}	# e.g., "joeusr-cdrom0"

msg_init

if [ "$MODE" = "allocate" ]; then
	MSGDEV=tty
  	do_allocate
else
    if [ "$FLAG" = "i" ] ; then
	MSGDEV=console
	do_init
    else
	MSGDEV=tty
	do_deallocate
    fi
fi

exit $EXIT_STATUS
