#!/bin/sh -hp

# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.

# ident	"@(#)patch_common_lib.sh	1.3	07/10/03 SMI"

## This is the script version of a common patch utility library. Put functions
## here that are common across the patch tools.

#
# Global Variables
#

SAFEMODE_METHOD="overlay"

# Location of file used to store list of files that need to be deleted by
# the patch-finish:delete service after a safe_mode patch has been applied.
DELETE_LIST=/var/sadm/patch/.delete_list

#
# Needed utilities
#
ISAINFO=/usr/bin/isainfo
MDB=/usr/bin/mdb
MODLOAD=/usr/sbin/modload
SVCADM=/usr/sbin/svcadm
SVCCFG=/usr/sbin/svccfg
SVCPROP=/usr/bin/svcprop

#
# Public Functions...
#

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description: 
# safemode initialization function.
#
# Parameters:
#		None
# Globals Used:
#		SAFEMODE_METHOD
# Globals Set:
#		SAFEMODE_DIR
#		SAFEMODE_OVERLAY_DIR
#		SAFECMDDIR
#		SAFEMODE_OVERLAY_LOCK
#		NO_MOUNTCMD_MSG
#		NO_UMOUNTCMD_MSG
#		NO_MV_MSG
#		NO_CP_MSG
#		SAFE_MOUNT
#		SAFE_UMOUNT
#		SAFE_MV
#		SAFE_CP
#		SAFEMODE_ROOT
# Returns:
#		0 - success
#		1 - failure
#
InitSafemode()
{

	# Check that the ENV variables we need are defined.

	[ "$SAFEMODE_METHOD" != "overlay" ] && return 1

	# Safe mode install related globals
	SAFEMODE_DIR=/var/run/.patchSafeMode
	SAFEMODE_OVERLAY_DIR=/var/run/.patchSafeModeOrigFiles
	SAFECMDDIR=$SAFEMODE_DIR/safecmd_dir
	SAFEMODE_OVERLAY_LOCK=$SAFEMODE_DIR/.patch.safemode.lock

	NO_MOUNTCMD_MSG="Exiting! $SAFECMDDIR/mount does not exist"
	NO_UMOUNTCMD_MSG="Exiting! $SAFECMDDIR/umount does not exist"
	NO_MV_MSG="Exiting! $SAFECMDDIR/mv does not exist"
	NO_CP_MSG="Exiting! $SAFECMDDIR/cp does not exist"
	SAFE_MOUNT="eval LD_LIBRARY_PATH=$SAFECMDDIR $SAFECMDDIR/ld.so.1 \
						$SAFECMDDIR/mount"
	SAFE_UMOUNT="eval LD_LIBRARY_PATH=$SAFECMDDIR $SAFECMDDIR/ld.so.1 \
						$SAFECMDDIR/umount"
	SAFE_MV="eval LD_LIBRARY_PATH=$SAFECMDDIR $SAFECMDDIR/ld.so.1 \
						$SAFECMDDIR/mv"
	SAFE_CP="eval LD_LIBRARY_PATH=$SAFECMDDIR $SAFECMDDIR/ld.so.1 \
						$SAFECMDDIR/cp"
	SAFEMODE_ROOT=$SAFEMODE_DIR/root

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description: 
# safemode install function. Install patch package objects in a safe manner.
# 
# Parameters:
#		$1 - source of object
#		$2 - target of object
#		$3 - copy of object to move into place
# Globals Used:
#		SAFEMODE_METHOD
#		SAFE_UMOUNT
#		SAFE_MOUNT
#		SAFE_MV
#		SAFE_CP
#		SAFEMODE_ROOT
#		SAFEMODE_OVERLAY_DIR
#
# Globals Set:
#		None
# Returns:
#		0 - success
#		1 - failure
#
InstallSafemodeObject()
{
	src=$1
	dst=$2
	tmpDst=$dst.$$
	
	[ "$SAFEMODE_METHOD" != "overlay" ] && return 1

	[ ! -f "$src" ] && return 1

	# Create a temporary copy of the $src object.

	/usr/bin/cp -p $src $tmpDst || return 1
 
	# exit if mount, umount, mv and cp are not in sandbox.

	[ ! -x "$SAFECMDDIR/mount" ] && return 1
	[ ! -x "$SAFECMDDIR/umount" ] && return 1
	[ ! -x "$SAFECMDDIR/mv" ] && return 1
	[ ! -x "$SAFECMDDIR/cp" ] && return 1

	dstActual=`echo $dst | sed "s#^$SAFEMODE_ROOT##"`
	filepath=`/usr/bin/dirname "$dstActual"`

	# Create a directory structure under overlay dir.

	tmpDir=$SAFEMODE_OVERLAY_DIR$filepath
	tmpFile="${tmpDir}/`/usr/bin/basename $dstActual`"

	[ ! -d "$tmpDir" ] && /usr/bin/mkdir -m 755 -p $tmpDir

	# Grab cksums of the new object being delivered and of the
	# object stored away in the overlay dir (if one already exists)

	cksumTmpDst=`/usr/bin/cksum $tmpDst 2>/dev/null | \
	    /usr/bin/cut -f 1 2>/dev/null`

	if [ -f "$tmpFile" ]; then
		cksumTmpFile=`/usr/bin/cksum $tmpFile 2>/dev/null | \
		    /usr/bin/cut -f 1 2>/dev/null`
	fi

	# umount $dstActual in case the old mounts exist.
	# Only umount the files which are 'lofs' mounted as part
        # of previous safemode install. This is achieved by
	# checking if $dstActual exists in $SAFEMODE_OVERLAY_DIR
	# If it exist then umount it.  
	# If a 'lofs' mount is created by some othe mechanism then this
	# code will not umount it. Instead the file will be copied into
	# the safemode directory and will be used to overlay mount the
	# patched object.
	# This does not take care of the case where the file which
	# is overlay mounting another file is being patched.
	# This is a very rare case and does not exist in the current
	# KU so we will not deal with it here. 
	#  
	[ -f "$tmpFile" ] && \
		$SAFE_UMOUNT "$dstActual" >/dev/null 2>&1
	
	# If $dst already exists in the overlay dir then
	# don't copy it a second time. The existing one in
	# the overlay dir is the one we need to continue to overlay
	# on top this object, no matter how many times we patch
	# it before a reboot occurs.

	if [ ! -f "$tmpFile" ]; then

		# If this is a new object i.e. if $dstActual does
		# not exist on the system then don't perform the
		# $SAFE_CP operation.

		if [ -f "$dstActual" ]; then
			$SAFE_CP -p $dstActual $tmpDir || return 0
		fi
	fi

	# Move delivered object into place.

	$SAFE_MV -f $tmpDst $dstActual || return 0

	# If this object exists in the overlay dir  (i.e. this
	# object existed on the system before we patched it), overlay
	# mount it on top of the newly delivered object.

	if [ -f "$tmpFile" ]; then

		# If the newly delivered object is exactly the same
		# as the file stored away in the overlay dir, then
		# don't overlay mount it.  Rather, just delete it from
		# the overlay dir.  This happens when patchadd calls
		# patchrm to clean up a failed patchadd.

		if [ -z "$cksumTmpDst" -o -z "$cksumTmpFile" -o \
		    "$cksumTmpDst" != "$cksumTmpFile" ]; then

			# Objects are different, do the ovelay.

			$SAFE_MOUNT -O "$tmpFile" "$dstActual" || return $?
		else
			# Objects are identical, don't do the overlay and
			# just remove the object from the overlay dir.

			/usr/bin/rm -f "$tmpFile" > /dev/null 2>&1
		fi
	fi

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description: 
# Safe mode patch install sandbox setup
# Create appropriate dirs. if they don't already exist.
# Copy mount, umount, mv commands and the libraries they need.
#
# Parameters:
#		None
# Globals Used:
#		SAFECMDDIR
#		SAFEMODE_OVERLAY_DIR
# Globals Set:
#		None
# Returns:
#		0 - success
#		1 - failure
#
CreateSafeModeSandbox() {

	if [ ! -d "$SAFECMDDIR" ]; then
		/usr/bin/mkdir -p -m 0755 $SAFECMDDIR >/dev/null
		if [ $? != 0 ]; then
			return 1
		fi
	fi
	if [ ! -d "$SAFEMODE_OVERLAY_DIR" ]; then
		/usr/bin/mkdir -p -m 0755 $SAFEMODE_OVERLAY_DIR >/dev/null
		if [ $? != 0 ]; then
			return 1
		fi
	fi
	
	cmdsUsed="/usr/lib/fs/lofs/mount /sbin/umount /usr/bin/mv /usr/bin/cp"

	for cmdObj in $cmdsUsed ; do
		[ ! -f $SAFECMDEDIR/`/usr/bin/basename $cmdObj` ] &&
			/usr/bin/cp -p $cmdObj $SAFECMDDIR >/dev/null 2>&1
	done
	
	if [ -x /usr/bin/ldd ]; then
		libsUsed="/lib/ld.so.1 `/usr/bin/ldd $SAFECMDDIR/* | \
			/usr/bin/sort -u | /usr/bin/sed -n 's/^.*=>[^\/]*//p'`"
	else
		libsUsed="/lib/ld.so.1 /lib/libavl.so.1 /lib/libc.so.1 \
			/lib/libcmdutils.so.1 /lib/libm.so.2 /lib/libsec.so.1"
	fi
	
	for libObj in $libsUsed ; do
		[ ! -f $SAFECMDDIR/`/usr/bin/basename $libObj` ] &&
			/usr/bin/cp -p $libObj $SAFECMDDIR >/dev/null 2>&1
	done

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description: 
# Cleanup safemode installation.
# 
# Parameters:
#		$1 - source of object
#		$2 - target of object
#		$3 - copy of object to move into place
# Globals Used:
#		SAFEMODE_METHOD
# Globals Set:
#		None
# Returns:
#		0 - success
#		1 - failure
#
CleanupSafemode()
{

	[ "$SAFEMODE_METHOD" != "overlay" ] && return 1

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
#   Checks to see if a filepath is a kernel module by checking to see
#   if filepath:
#
#	begins with /kernel, /platform, or /usr/kernel
#	doesn't contain 'cpu' in its path and isn't named 'cpu'
#	doesn't contain 'kmdb' in its path isn't named 'kmdb'
#	doesn't end with a '.conf'
#
# Parameters:
#		$1 - filepath to check
# Globals Used:
#		None
# Globals Set:
#		None
# Returns:
#		0 - True, filepath is a kernel module
#		1 - False, filepath is not a kernel module
#
IsKernelModule() {

	filepath=$1

	# Validate parameters.
	if [ -z "$filepath" ]; then
		return 1
	fi

	echo $filepath | \
	    /usr/bin/egrep "^/kernel|^/platform|^/usr/kernel" | \
	    /usr/bin/egrep -v "/cpu/|/cpu$" | \
	    /usr/bin/egrep -v "/kmdb/|/kmdb$" | \
	    /usr/bin/grep -v ".conf$" > /dev/null
		
	if [ $? -eq 0 ]; then
		return 0
	fi

	return 1
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
#   This function removes kernel modules from the system.  It first
#   makes sure module unloading is disabled, and then loads the modules
#   before actually removing the kernel module files.
# Parameters:
#		$1 - filelist of the kernel modules to remove
# Globals Used:
#		ISAINFO
#		MDB
#		MODLOAD
# Globals Set:
#		None
# Returns:
#		None
#
RmKernelModules() {

	filelist=$1
	isa_info=`$ISAINFO -b`

	# If empty filelist, then return.
	if [ ! -s "$filelist" ]; then
		return
	fi

	if [ -x $MDB ]; then
		# We need to suppress messages from the krtld first because
		# we might get module load warnings.
		if [ "$isa_info" = "64" ]; then
			SAVED_ADDR=`echo '_kobj_printf/J' | $MDB -k | \
			    /usr/bin/cut -f2 -d ':'` 
			echo '_kobj_printf/Z systrace_stub' | $MDB -kw > \
			    /dev/null 2>&1
		else
			SAVED_ADDR=`echo '_kobj_printf/X' | $MDB -k | \
			    /usr/bin/cut -f2 -d ':'`
			echo '_kobj_printf/W systrace_stub' | $MDB -kw > \
			    /dev/null 2>&1
		fi

		# Disable module unloading.
		echo "moddebug/W20000" | $MDB -kw /dev/ksyms /dev/mem > \
		    /dev/null 2>&1
	fi

	# Load and remove the kernel modules.
	while read line ; do
		if [ -f $line ]; then
			$MODLOAD $line > /dev/null 2>&1
			/usr/bin/rm -f $line > /dev/null 2>&1
		fi
	done < $filelist

	# Turn messages from the krtld back on.
	if [ -x $MDB ]; then
		if [ "$isa_info" = "64" ]; then
			echo '_kobj_printf/Z '$SAVED_ADDR | $MDB -kw > \
			    /dev/null 2>&1
		else
			echo '_kobj_printf/W '$SAVED_ADDR | $MDB -kw  > \
			    /dev/null 2>&1
		fi
	fi
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
#   Update the system/patch-finish:<instance> service.  Either enable it to run
#   on the next boot up, or disable it.
#
# Parameters:
#		action	- { enable | disable }
#		instance	- name of patch-finish instance to enable
#
# Globals Used:
#		SVCADM
#		SVCCFG
#		SVCPROP
#
# Globals Set:
#		None
#
# Returns:
#		0 for success
#		> 0 for failure
UpdatePatchFinishService() {

	action=$1
	service_inst="system/patch-finish:$2"

	# Validate action
	if [ "$action" != "enable" -a "$action" != "disable" ]; then
		return 1
	fi

	# Make sure instance exists.
	$SVCPROP -q $service_inst > /dev/null 2>&1
	if [ $? != 0 ]; then
		return 1
	fi

	# Temporarily disable it.
	$SVCADM disable -t $service_inst > /dev/null 2>&1
	if [ $? != 0 ]; then
		return 1
	fi

	# Set 'general/enabled' value based on 'action'
	if [ "$action" = "enable" ]; then
		value="true"
	else
		value="false"
	fi

	# Persistently enable/disable it.
	$SVCCFG -s $service_inst setprop general/enabled=$value > /dev/null 2>&1
	if [ $? != 0 ]; then
		return 1
	fi

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
#   If we're in the global zone, check to see if we need
#   safemode patch processing for deletes files.
#
#   NOTE that the patch system has a locking mechanism which
#   ensures that only one patchadd/patchrm is running at a time.
#   So the manipulation of the DELETE_LIST file below yields
#   safe results.
# Parameters:
#		$1 - patch package's pkginfo file
#		$2 - path to patch package's deletes file
#		$3 - path to patch package's pkgmap file
#		$4 - true/false flag indicating if we're in safemode
# Globals Used:
#		DELETE_LIST
#		WORKDIR
# Globals Set:
#		None
# Returns:
#		0 for success
#		> 0 for failure
#
SetupSafemodeDeleteList() {

	pkginfo=$1
	deletes_file=$2
	pkgmap=$3
	safemode=$4

	# Validate parameters.
	if [ -z "$pkginfo" -o -z "$deletes_file" -o -z "$pkgmap" -o \
	    -z "$safemode" ]; then
		return 1
	fi

	# Get basedir from pkginfo
	basedir=`pkgparam -f $pkginfo BASEDIR`

	# Make sure globals used are defined.
	if [ -z "$WORKDIR" ]; then
		return 1
	fi

	kernel_mod_list=${WORKDIR}/kernel_mod_list.$$

	# If we're running in safemode, process the deletes
	# file if one exists for this patch pkg.
	if [ "$safemode" = "true" ]; then

		# If this patch pkg is deleting files, then
		# append them into the DELETE_LIST file which
		# will be consumed by the
		# system/patch-finish:delete service on the
		# way back up from the next reboot.
		if [ -f $deletes_file ]; then
			while read path ; do

				# Prepend the basedir
				# to the paths
				if [ -z "$basedir" ]; then
					filepath=$path
				elif [ "$basedir" = "/" ]; then
					filepath=$basedir$path
				else
					filepath=$basedir/$path
				fi

				# If filepath is a kernel module
				# then add it to the kernel module list to
				# be processed at the end of this loop,
				# otherwise add it to the DELETE_LIST.
				IsKernelModule $filepath
				if [ $? = 0 ]; then
					echo $filepath >> $kernel_mod_list
				else
					echo $filepath >> $DELETE_LIST
				fi

			done < $deletes_file

			# Remove kernel modules from the deletes file now.
			if [ -f $kernel_mod_list -a \
			    -s $kernel_mod_list ]; then

				RmKernelModules $kernel_mod_list

				# Remove the temporary kernel module list file.
				/usr/bin/rm -f $kernel_mod_list
			fi

			# Enable the system/patch-finish:delete
			# service to run on the next reboot.
			# If this fails, return failure.
			UpdatePatchFinishService enable delete
			if [ $? != 0 ]; then
				return 1
			fi
		fi
	fi

	# If a DELETE_LIST file exists, check if this patch
	# pkg is reintroducing a file that's in it.  If so,
	# we need to remove it from the DELETE_LIST.
	if [ -f $DELETE_LIST ]; then
		while read part ftype class pathname rest ; do

			# Ignore the header line and files of
			# type i, b, and p.
			if [ "$part" = ":" -o \
			    "$ftype" = "i" -o \
			    "$ftype" = "b" -o \
			    "$ftype" = "p" ]; then
				continue;
			fi

			# For links, grab the path from its
			# 'path=dest' entry in the pkgmap.
			if [ "$ftype" = "s" -o \
			    "$ftype" = "l" ]; then
				pathname=`echo $pathname | \
				    /usr/bin/cut -f 1 -d =`
			fi

			if [ -z "$basedir" ]; then
				file=$pathname
			elif [ "$basedir" = "/" ]; then
				file="$basedir$pathname"
			else
				file="$basedir/$pathname"
			fi

			/usr/bin/grep "^$file$" $DELETE_LIST > /dev/null
			if [ $? = 0 ]; then
				# Found file in DELETE_LIST.
				# Remove it.
				/usr/bin/grep -v "^$file$" \
				    $DELETE_LIST > \
				    $DELETE_LIST.$$
				/usr/bin/mv $DELETE_LIST.$$ $DELETE_LIST
			fi

			# If DELETE_LIST is empty, remove it
			# and disable the
			# system/patch-finish:delete service.
			if [ ! -s $DELETE_LIST ]; then
				/usr/bin/rm -f $DELETE_LIST
				UpdatePatchFinishService disable delete
				break
			fi

		done < $pkgmap
	fi

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
#   Add a pkginfo parameter and value to a pkginfo file.
# Parameters:
#		$1 - parameter and value to add to a packages pkginfo file
#		$2 - path to pkginfo file
# Globals Used:
#		None
# Globals Set:
#		None
# Returns:
#		0 for success
#		> 0 for failure
#
AddParamToPkginfo() {

	parameterAndValue=$1
	pkginfoPath=$2

	[ -z "$parameterAndValue" -o -z "$pkginfoPath" ] && return 1

	echo "$parameterAndValue" >> $pkginfoPath

	return 0
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Description:
# This function performs removef of an object.
# It is used in patch_postinstall and postinstall scripts
# Parameters:
#               $1 - pkginst
#               $2 - client path
# Globals Used:
#               None
# Globals Set:
#               None
# Returns:
#               None
#
HandleSafemodeDeleteObject()
{
        pkgInst=$1
        clientPath=$2

        /usr/sbin/removef $pkgInst $clientPath >/dev/null 2>&1
}
