#!/bin/sh -h
# @(#) installpatch 3.8 94/01/12 SMI
# @(#) installpatch 1.3.1.3 94/01/04 SMI
# is_server feature is disabled for now....
#
# Exit Codes:	0	No error
#		1	Usage error
#		2	Attempt to apply a patch that's already been applied
#		3	Effective UID is not root
#		4	Attempt to save original files failed
#		5	pkgadd failed
#		6	Patch is obsoleted
#		7	Invalid package directory
#		8	Attempting to patch a package that is not installed
#		9	Cannot access /usr/sbin/pkgadd (client problem)
#		10	Package validation errors
#		11	Error adding patch to root template
#		12	Patch script terminated due to signal
#		13	Symbolic link included in patch
#

umask 007

# Global Files
EXISTFILES=/tmp/existfiles.$$
PATCHFILES=/tmp/patchfiles.$$
PKGCOFILE=/tmp/pkgchk.out.$$ 
VALERRFILE=/tmp/valerr.$$
ADMINTFILE=/tmp/admin.tmp.$$
ADMINFILE=/tmp/admin.$$
KBYTESFILE=/tmp/kbytes_required.$$
LOGFILE=/tmp/pkgaddlog.$$

client=no
#force="no"
is_a_root_pkg=no
#is_server="no"
patchdir=
patchnum=
patchbase=
patchrev=
pkglist=
printpatches="no"
rootlist=
saveold="yes"
validate="yes"

ROOTDIR="/"
PATCHDB="/var/sadm/patch"
PKGDB="/var/sadm/pkg"
SOFTINFO="/var/sadm/softinfo"
PKGDBARG=""

# Description:
#	Validate the patch directory, and parse out the patch number and
#	patch revision from the first pkginfo file found in the patch
#	packages.
# Parameters:
#	$1	- patch directory
# Globals Set:
#	patchnum
#	patchbase
#	patchrev

activate_patch()
{
	cd $1
	for i in */pkginfo; do
#
# Find the patch number in one of the pkginfo files. If there is no pkginfo
# file having a SUNW_PATCHID=xxxxxx entry, send an error to the user and
# exit.
#
		patchnum=`grep '^SUNW_PATCHID' $i 2>/dev/null | sed 's/.*=[ 	]*\([^ 	]*\).*/\1/'`
		break;
	done
	if [ "$patchnum" = "" ]; then
		echo "$1 packages are not proper patch packages."
		echo 'See "Instructions for applying the patch" in the README file.'
		exit 7
#
# Get the patch base number (the patch number up to the -) and the patch
# revision number (the patch number after the -).
#
	else
		patchbase=`expr $patchnum : '\(.*\)-.*'`
		patchrev=`expr $patchnum : '.*-\(.*\)'`
	fi
}

# Description:
#	Build the admin file for later use by non-interactive pkgadd
# Parameters:
#	none
# Globals Used:
#	ADMINTFILE

build_admin_file() {
cat > $ADMINTFILE << EOF
mail=
instance=unique
partial=nocheck
runlevel=nocheck
idepend=nocheck
rdepend=nocheck
space=quit
setuid=nocheck
conflict=nocheck
action=nocheck
EOF
}

# Description:
#	See if there is any work to be done. If none of the packages to
#	which the patch applies are installed and there is no spooling work
#	to do for the client root templates, then you're done.
# Parameters:
#	$1	- client status
#	$2	- were any of the packages root packages?
# Globals Used:
#	pkglist
#	rootlist

check_for_action() {
	if [ "$pkglist" = "" -a "$rootlist" = "" ]; then
#
# In the first case, the system is not a client, however, there are still
# no packages to patch. This will only occur if the packages in question
# have not been installed on the system.
#
		if [ $1 = no -o $2 = yes ] ; then
 			echo "None of the packages to patch are installed on this system."
			echo "Installpatch is terminating."
			exit 8
#
# In the second case, the system is a client system. There are two types of
# packages for client systems: root packages (those packages installed on
# the client machines) and packages installed only on the server. Installpatch
# will exit if the machine is a client, and there are no root packages to be
# patched.
#
		else
			echo "This patch is not applicable to client systems."
			echo "Installpatch is terminating."
			exit 0
		fi
	fi
}

# Description:
#	Check to see if the patch has already been applied
# Parameters:
#	$1	- patch database directory
#	$2	- patch number

check_if_applied() {
	if [ -f $1/$2/.applied ]; then
		echo "Patch $2 has already been applied. See README file for instructions."
		echo "Installpatch is terminating."
		rm -f /tmp/*.$$
		exit 2
	else
		rm -fr $1/$2
	fi
}

# Description:
#	Check to see if the patch is obsoleted by an earlier release
# Parameters:
#	$1	- patch database directory

check_if_obsolete() {
	obase=
	opatchid=
	obsoletes=
	oldbase=
	oldrev=
	Patchid=
	i=
	j=
	currentdir=`pwd`
	if [ -d $1 ]; then
		cd $1;
#
# Search each directory in the patch database directory.
#
		for i in * X; do
			if [ $i = X  -o "$i" = "*" ]; then
				break;
			fi
			if [ ! -d $i ]; then
				continue;
			fi
			cd $i
			for j in */pkginfo X; do
#
# If there is a patch directory having a patch that obsoletes the patch
# being installed (the same patch with a higher revision), then exit. 
# The new patch should be installed rather than the old patch.
#
				if [ "$j" = "X" -o "$j" = "*/pkginfo" ]; then
					break;
				fi
				Patchid=`sed -n 's/^[ 	]*SUNW_PATCHID[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $j`
				if [ "$Patchid" = "" ] ; then
					continue
				fi
				oldbase=`expr $Patchid : '\(.*\)-.*'`
				oldrev=`expr $Patchid : '.*-\(.*\)'`
				if [ $oldbase = $patchbase -a $patchrev -lt $oldrev ]; then
					print_obsolete_msg "$Patchid"
					exit 6
				fi
#
# Now search all patches in the patch database for those that specifically
# obsolete the current patch.
#
				obsoletes=`sed -n 's/^[	 ]*SUNW_OBSOLETES[ 	]*=[ 	]*\([^ 	]*\)[ 	]*$/\1/p' $j`
				while [ "$obsoletes" != "" ] ; do
					opatchid=`expr $obsoletes : '\([0-9\-]*\).*'`
					obsoletes=`expr $obsoletes : '[0-9\-]*[ ,]*\(.*\)'`
					# patchrevent infinite loop.  If we couldn't
					# find a valid patch id, just quit.
					if [ "$opatchid" = "" ] ; then
						break;
					fi
					obase=`expr $opatchid : '\(.*\)-.*'`
					if [ "$obase" = "" ] ; then
						# no revision field in opatchid, which might
						# be supported someday (since we don't use
						# the revision field for obsoletion
						# testing anyway)
						obase=$opatchid
					fi
					if [ $obase = $patchbase ] ; then
						print_obsolete_msg
						exit 6
					fi
				done
			done
			cd ..
		done
		cd $currentdir
	fi
}

# Description:
# 		CURRENTLY DISABLED
# Parameters:
#		none

check_if_server() {
	echo > /dev/null
      # if grep -s "SPOOLED_ROOT" $SOFTINFO/$prodver > /dev/null 2>&1
      # then
      #		is_server="yes"
      # fi
}

# Description:
#	Determine if the patch contains any symbolic links. If so, die with
#	an error and a message to the user. I assume the patch will be tested
#	at least once in-house before getting to a non-sun user, so an
#	external user should NEVER see a symbolic link message.
# Parameters:
#	None
# Globals Set:
#	None.
# Globals Used:
#	$patchdir
#
check_for_symbolic_link() {
	rm -f /tmp/symlink.$$ > /dev/null 2>&1
	olddir=`pwd`
	cd $patchdir
	for ii in * X; do
		if [ "$ii" = X ]; then
			break
		fi
		if [ ! -d "$ii" ]; then
			continue
		fi
		symlinks=
		symlinks=`sed -n '/^[^ 	]*[ 	]*s[ 	]/p' $1/$2/$ii/pkgmap`
		if [ "$symlinks" != "" ]; then
			echo "Symbolic link in package $ii" >> /tmp/symlink.$$
		fi
	done
	if [ -s /tmp/symlink.$$ ]; then
		echo
		cat /tmp/symlink.$$
		echo
		echo "Symbolic links can't be part of a patch."
		echo "Installpatch is terminating."
		rm -f /tmp/*.$$
		exit 13
	fi
	cd $olddir
}

# Description:
#	Find package instance of originally-installed package. Extract the
#	PKGID, ARCH, and VERSION by scanning the pkginfo files of each patch
#	package. Check to see if the packages that are being patched were 
#	actually installed on the system in the first place.
# Parameters:
#	$1	- package database directory
#	$2	- patch database directory
#	$3	- patch number
# Globals Set:
#	pkglist
#	is_a_root_pkg
# Globals Use:
#	pkglist

check_pkgs_installed() {
	i=
	j=
	pkginst=
	finalpkglist=
	minver=
	Pkgpatchver=
	Pkgarch=
	Pkgabbrev=
	Pkgver=
	Pkgtype=
	OrigPkgver=
#
# Search the installed pkginfo files for matches with the list of packages
# to be patched. The package names are listed in global pkglist. These names
# correspond to the package database subdirectory names.
#
	for i in $pkglist; do
#
# Get the package abbreviation from the pkginfo file.
#
		Pkgabbrev=`sed -n 's/^[ 	]*PKG[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $i/pkginfo`
#
# Get the package architecture from the pkginfo file.
#
		Pkgarch=`sed -n 's/^[ 	]*ARCH[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $i/pkginfo`
#
# Get the package version number.
#
		Pkgver=`sed -n \
		   -e 's/^[ 	]*VERSION[ 	]*=[ 	]*\([^ 	]*\)\.[0-9][0-9]*[ 	]*$/\1/p' \
		   -e 's/^[ 	]*VERSION[ 	]*=[ 	]*\([^ 	]*\),PATCH=.*$/\1/p' $i/pkginfo `
		minver=`expr $Pkgver : '\(.*\)\.0$'`
		while [ "$minver" != "" ] ; do
		        Pkgver=$minver
		        minver=`expr $Pkgver : '\(.*\)\.0$'`
		done
		Pkgpatchver=`sed -n 's/^[ 	]*VERSION[ 	]*=[ 	]*\([^ 	]*\)[^ 	]*$/\1/p' $i/pkginfo`
		Pkgtype=`sed -n 's/^[ 	]*SUNW_PKGTYPE[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $i/pkginfo`
		if [ "$Pkgtype" = "root" ] ; then
			is_a_root_pkg=yes
		fi
		# echo ""
		for j in $1/$Pkgabbrev X; do
			if [ "$j" = "X" ]; then
				break
			fi
			if [ ! -d "$j" ] && \
			   [ ! -d "$j.*" ]; then
				echo "Package not patched:" >> $LOGFILE
				echo "PKG=$Pkgabbrev" >> $LOGFILE
				echo "Original package not installed" >> $LOGFILE
			fi
		done
		for j in $1/$Pkgabbrev $1/$Pkgabbrev.* X; do
			if [ "$j" = "X" ] ; then
				break
			fi
			if [ ! -d $j ] ; then
				continue;
			fi
		        OrigPkgver=`sed -n 's/^VERSION=\(.*\)$/\1/p' $j/pkginfo`
		        minver=`expr $OrigPkgver : '\(.*\)\.0$'`
		        while [ "$minver" != "" ] ; do
		                OrigPkgver=$minver
		                minver=`expr $OrigPkgver : '\(.*\)\.0$'`
		        done
		        if grep -s "^PKG=$Pkgabbrev$" $j/pkginfo >/dev/null 2>&1 \
		           && grep -s "^ARCH=$Pkgarch$" $j/pkginfo >/dev/null 2>&1 \
		           && [ "$OrigPkgver" = "$Pkgver" ] ;
		        then
		                pkginst=`basename $j`
		                finalpkglist="$finalpkglist $i,$pkginst"
		                break;
		        else
				echo "Package not patched:" >> $LOGFILE
				echo "PKG=$Pkgabbrev" >> $LOGFILE
				echo "ARCH=$Pkgarch" >> $LOGFILE
				echo "VERSION=$Pkgver" >> $LOGFILE
				tmp=""
		           	tmp=`grep "^ARCH=$Pkgarch$" $j/pkginfo 2>/dev/null`
				if [ "$tmp" = "" ]; then
					echo "Architecture mismatch" >> $LOGFILE
				fi
		           	if  [ "$OrigPkgver" != "$Pkgver" ] ; then
					echo "Version mismatch" >> $LOGFILE
				fi
				echo "" >> $LOGFILE
			fi
		done
		#
		#  If j is X, matching package instance was never found.  If
		#  in force mode, add package to list anyway.
		# if [ "$j" = "X"]; then
		#	if [ "$force" = "yes" ] ; then
		#        	finalpkglist="$finalpkglist $i,NO_CURRENT_INSTANCE"
		#	fi
		# fi
		# if [ "$is_server" = "yes" ]; then
		#        if [ "$Pkgarch" = "sparc" ]; then
		#                Pkgarch="sparc.all"
		#        fi
		#        org_template=$Pkgabbrev"_"$Pkgver"_"$Pkgarch
		#        if grep "SPOOLED_ROOT=$Pkgarch:/export/root/templates/$prodver/$org_template" $SOFTINFO/$prodver >/dev/null 2>&1
		#        then
		#                spoolsize=`/usr/bin/du -ks $i | sed 's/ .*//'`
		#                rootlist="$rootlist $i,$Pkgabbrev"_"$Pkgpatchver"_"$Pkgarch",$spoolsize
		#        fi
		# fi
	done
	pkglist=$finalpkglist
}

# Description:
#	If validation is being done, and pkgchk reported ERRORs, bail out.
#	If no validation is being done, keep a list of files that failed
#	validation. If this patch needs to be backed out, don't do an installf
#	on these files. Any files that failed validation before the patch was
#	applied should still fail validation after the patch is backed out.
#	This will be the .validation.errors file in the patch directory.
# Parameters:
#	$1	- validation status [ "yes" or "no" ]
# Globals Used:
#	PKGCOFILE
#	VALERRFILE

check_validation() {
	if [ "$1" = "yes" -s $PKGCOFILE ]; then
		if grep ERROR $PKGCOFILE >/dev/null ; then
			echo "The following validation error was found: \n"
			cat $PKGCOFILE
			echo
			echo "See the README file for instructions regarding"
			echo "patch validation errors."
			echo "Installpatch is terminating."
			rm -f /tmp/*.$$
			exit 10
		fi
	fi
	if [ "$1" != "yes" -a -s $PKGCOFILE ]; then
		grep ERROR $PKGCOFILE | sed 's/ERROR: //' >$VALERRFILE
	fi
}

# Description:
# 	Create a spooling area in the sadm/patch/<patchID> tree for files
# 	which are being replaced by the patch. Store the validation error
# 	file with it.
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
# Globals Used:
#	VALERRFILE

create_archive_area() {
	if [ ! -d $1/$2/save ] ; then
		echo "Creating patch archive area..."
		mkdir -p -m 750 $1/$2/save
		chown -h -f -R root $1/$2
		chgrp -h -f -R sys $1/$2
	fi
	if [ -s $VALERRFILE ] ; then
		cp $VALERRFILE $1/$2/.validation.errors
	fi
}

# Description:
#	Scan the patch package maps for a list of affected files.
# Parameters:
#	$1	- package database directory
#	$2	- relocation argument for packaging routines
# Globals Used:
#	PKGCOFILE
#	PATCHFILES
#	pkglist

gen_install_filelist() {
	pkgfiles=/tmp/pkgfiles.$$
	resfiles=/tmp/resolvedfiles.$$
	macrofiles=/tmp/pkgmacros.$$
	pkginst=
	pkginfofile=
	patchpkg=
	basedir=
	i=
	rm -f $PATCHFILES
	echo "Generating list of files to be patched..."
	for i in $pkglist; do
		patchpkg=`expr $i : '\(.*\),.*'`
		pkginst=`expr $i : '.*,\(.*\)'`
		if [ "$pkginst" = "NO_CURRENT_INSTANCE" ] ; then
#
# Install a package to be patched if it isn't currently installed????
#
			pkginfofile="$patchpkg/pkginfo"
		else
			pkginfofile="$1/$pkginst/pkginfo"
		fi
#
# parse out the base directory from the pkginfo file. First, remove the BASEDIR
# keyword together with the = and any whitespace. Then remove any / at the end
# of the line. Finally, replace any /a partitions with /
#
		basedir=`grep '^BASEDIR' $pkginfofile | \
		    sed -e 's@.*=\ *@@' -e 's@/$@@' -e 's@/a$@/@'`
#
# Parse out the pkgmap files to get the file names. First, get rid of all
# checksum ifno. Then get rid of all info file entries.
# Replace all BASEDIR values with emptiness.
# Delete all entries that are the BASEDIR without a file (directory entries).
# Get the file name. If it's a symbolic link, keep the link, don't follow
# it to the file. Last, prepend the basedir to the file name and add it to
# the pkgfile list. 
#
		sed -e '/^:/d' \
		    -e '/^[^ ][^ ]* i/d' \
		    -e 's, \$BASEDIR/, ,' \
		    -e '/ \$BASEDIR /d' \
		    -e 's/^[^ ]* . [^ ]* \([^ ]*\).*$/\/\1/' \
		    -e 's/=.*//' \
		    -e "s,^.,$basedir&," $patchpkg/pkgmap > $pkgfiles
	
		if [ -s $pkgfiles ]; then
			# resolve any macros in the list of files
			(rm -f $macrofiles $resfiles
			cat $pkginfofile |
			while read i
			do
				echo `echo $i| sed -e 's/^\(.*\)=.*/\1/'`=\"`echo $i|sed -e 's/^\(.*\=\)//'`\" | grep -v '^PATH' >> $macrofiles
			done
			. $macrofiles
		        cat $pkgfiles |
		        while read i
		        do
				eval echo $i >> $resfiles
			done)
			if [ -s $resfiles ]; then
				/usr/sbin/pkgchk $2 -i $resfiles 2>&1 | \
					grep -v '^WARNING' >> $PKGCOFILE
			fi
		 	cat $resfiles >> $PATCHFILES
		fi
	done
}

# Description:
#	Generate a list of files which are "to be patched." Determine their
#	total size in bytes to figure out the space requirements of backing
#	them up.
# Parameters:
#	none
# Globals Used:
#	PATCHFILES
#	EXISTFILES
#	KBYTESFILE

gen_patch_filelist() {
	tmp_total=0
	size=
	kbytes_total=0
	kb=0
	if [ -s $PATCHFILES ] ; then
		cat $PATCHFILES |
		(while read j
		do
			if ls -d $j >/dev/null 2>&1
			then
				echo $j >> $EXISTFILES
				size=`wc -c $j`
				size=`echo $size | sed 's/\ .*//'`
				if [ "$size" != "" ]; then
					tmp_total=`expr $tmp_total + $size`
				fi
				if [ $tmp_total -ge 1024 ]; then
					kb=`expr $tmp_total / 1024`
					tmp_total=`expr $tmp_total - $kb \* 1024`
					kbytes_total=`expr $kbytes_total + $kb`
				fi
			fi
		done;
		if [ "$tmp_total" != "" ]; then
			kbytes_total=`expr $kbytes_total + 1`
		fi
		echo $kbytes_total > $KBYTESFILE)
		
	else
		rm -f $EXISTFILES
	fi
}

# Description:
# 	Assemble a list of the package package IDs contained in the patch
#	(at least one diretory with a pkginfo file must exist due to checks
#	in activate_patch)
# Parameters:
#	none
# Globals Set:
#	pkglist

gen_patchpkg_list() {
	pkg=
	for i in */pkginfo X; do
		if [ "$i" = "X" ]; then
			break
		fi
		pkg=`expr $i : '\(.*\)/pkginfo'`
		pkglist="$pkglist $pkg"
	done
}

# Description:
#	Get the product version <name>_<version> of local Solaris installation
# Parameters:
#	$1	- softinfo directory path
# Globals Set:
#	prodver

get_os_version() {
	Product=
	Instver=
	Product=`sed -n 's/^OS=\(.*\)/\1/p' $1/INST_RELEASE`
	Instver=`sed -n 's/^VERSION=\(.*\)/\1/p' $1/INST_RELEASE`
	prodver=$Product"_"$Instver
}

# Description:
# 	Actually install patch packages which apply to the system
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- patch directory
#	$4	- package add relocation argument
#	$5	- package database directory
# Globals Used:
#	ADMINTFILE
#	ADMINFILE
#	pkglist

install_patch_pkgs() {
	i=
	ij=
	pkginst=
	pkginfofile=
	pkgadderr=
	patchpkg=
	basedir=

#
#	Write out the contents of the logfile if there were any
#	messages. Do this now, because the $1/$2 directory may not
#	exist before this point.
#
	if [ -f $LOGFILE ]; then
		cat $LOGFILE >> $1/$2/log
		rm -f $LOGFILE
	fi
	echo "Installing patch packages..."
	for ij in $pkglist; do
		i=`expr $ij : '\(.*\),.*'`
		pkginst=`expr $ij : '.*,\(.*\)'`
		if [ "$pkginst" = "NO_CURRENT_INSTANCE" ] ; then
			pkginfofile="$patchpkg/pkginfo"
		else
			pkginfofile="$5/$pkginst/pkginfo"
		fi
		basedir=`grep '^BASEDIR' $pkginfofile | sed -e 's@.*=\ *@@' -e 's@/a/@/@' -e 's@/a$@/@'`
		mkdir -m 750 $1/$2/$i
		cp $i/pkgmap $1/$2/$i/pkgmap
		cp $i/pkginfo $1/$2/$i/pkginfo
 		cp $ADMINTFILE $ADMINFILE
		echo basedir=$basedir >>$ADMINFILE
	
		echo "\nDoing pkgadd of $i package:"
		/usr/sbin/pkgadd $4 -S -a $ADMINFILE -d $3 $i >$LOGFILE 2>&1
		#/usr/sbin/pkgadd $4 -S -a $ADMINFILE -n -d $3 $i >$LOGFILE 2>&1
		pkgadderr=$?
		cat $LOGFILE >> $1/$2/log
		cat $LOGFILE | grep -v "^$"
		rm -f $LOGFILE
		if [ $pkgadderr != 0 -a $pkgadderr != 2 -a \
		     $pkgadderr != 10 -a $pkgadderr != 20 ]; then
			echo "Pkgadd of $i package failed with error code $pkgadderr."
			echo "See /tmp/log.$2 for reason for failure."
			cp $1/$2/log /tmp/log.$2
			echo "Backing out patch:"
			cd $3
			./backoutpatch $2
			echo "Installpatch is terminating."
			rm -fr /tmp/*.$$
			exit 5
		fi
	done
}

# Description:
# 	Parse the arguments and set all affected global variables
# Parameters:
#	Argument list passed into installpatch 
# Globals Set:
#	validate
#	saveold
#	force
#	printpatches
#	patchdir
#	ROOTDIR
#	PATCHDB
#	PKGDB
#	SOFTINFO
#	PKGDBARG
# Globals Used:
#	prodver
#	SOFTINFO
#	PKGDB
#	PATCHDB

parse_args() {
	while [ "$1" != "" ]
	do
		case $1 in
		-u)	validate="no"; shift;;
		-d)	saveold="no"; shift;;
		-p)	printpatches="yes"; shift;;
#		-f)	force="yes"; shift;;
		-S)	shift
			get_os_version $SOFTINFO
			if [ "$1" != "$prodver" ]; then
				if [ -d "/export/$1$PKGDB" ]; then
					ROOTDIR=/export/$1
					PATCHDB=$ROOTDIR$PATCHDB
					PKGDB=$ROOTDIR$PKGDB
					SOFTINFO=$ROOTDIR$SOFTINFO
					PKGDBARG="-R $ROOTDIR"
				else
					echo "The $1 service cannot be found on this system."
					print_usage
					exit 1
				fi
			fi
			shift;;
		-V)	echo "@(#) installpatch 1.3.1.3 94/01/04"; exit 0;;
		-*)	print_usage; exit 1;;
		 *)	break;;
		esac
	done
	if [ "$printpatches" = "yes" ]; then
		print_applied_patches $PATCHDB
		exit 0
	fi
	if [ "$1" = "" ]; then
		echo "No patch directory specified."
		print_usage
		exit 1
	fi
	if [ ! -d $1 ]; then
		echo "Patch directory '$1' does not exist."
		print_usage
		exit 1
	fi
	patchdir=$1
	echo "@(#) installpatch 1.3.1.3 94/01/04"
}

# Description:
#	Scan all pkginfo files for installed packages for a PATCH
#	identifier string and print out the associated package info
# Parameters:
#	$1 	- patch database directory

print_applied_patches() {
	listfile=/tmp/patchlist.$$
	grep SUNW_PATCHID $1/*/*/pkginfo | sed 's/.*SUNW_PATCHID=//' | sort -u >$listfile
	if [ -s $listfile ]; then
		echo "Patches currently applied:"
		cat $listfile
	else
		echo "No patches applied to this system."
	fi
	rm -fr $listfile
}

# Description:
#	Print the patch obsolecensce message
# Parameters:
#	$1	- number of patch which obsoleted this patch

print_obsolete_msg() {
	echo "This patch is obsoleted by patch $1 which has already"
	echo "been applied to this system. Patch installation is aborted."
}

# Description:
#	Print the list of patch packages which were applied, and those
#	which were not.
# Parameters:
#	none
# Globals Used:
#	pkglist

print_results() {
	i=
	p=
	echo "\nPatch packages installed:"
	if [ -s "$pkglist" ]; then
		echo "		none"
	else
		for i in $pkglist; do
			p=`expr $i : '\(.*\),.*'`
			echo "	$p"
		done
	fi
}

# Description:
#	Usage message
# Parameters:
#	none

print_usage() {
cat << EOF

   Usage: installpatch [-udpVv] [-S <service>] <patch directory>
 Options:
          -u	Turn off file validation.  Allows the patch to be
		applied even if some of the files to be patched have
		been modified since original installation.
   	  -d	Don't back up the files to be patched.  This means
		that the patch can't be backed out.
   	  -p	Print a list of the patches currently applied
   	  -V	Print script version number
   	  -S <service>
		Specify an alternate service (e.g. Solaris_2.3) for
		patch package processing references.
	  -v	Verbose option. Print out what you are doing while you
		are doing it.

EOF
}

# Description:
# 	Archive files which will be overwritten by the patch application,
#	if the patch actually affects any existing files.
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- patch directory
#	$4	- save old files [ "yes" or "no" ]
# Globals Used:
#	EXISTFILES
#	KBYTESFILE

save_overwritten_files() {
	kbytes_avail=
	kbytes_required=
	bytes=
	if [ ! -s $EXISTFILES ] ; then
		touch $1/$2/.nofilestosave
	else
		if [ "$4" = "yes" ]; then
			echo "Saving a copy of existing files to be patched..."
			# Is there enough space? Use sed to extract the fourth field of
			# df output (can't use awk because it may not be installed).
			kbytes_avail=`df -k $1 | tail -1 | \
			  sed 's/^[^ 	]*[ 	]*[^ 	]*[ 	]*[^ 	]*[ 	]*\([^ 	]*\).*/\1/'`
			bytes=`cat $KBYTESFILE`
			kbytes_required=`expr $bytes`
			if [ $kbytes_required -gt $kbytes_avail ]; then
				echo "Insufficient space in $1 to save old files."
				echo "Space required in kilobytes:  $kbytes_required"
				echo "Space available in kilobytes:  $kbytes_avail"
				cd $3
				./backoutpatch -s $2
				rm -f /tmp/*.$$
				rm -fr $1/$2
				exit 4
			fi
			cd /
			if [ -x /usr/bin/compress ]; then
				echo "	File compression being used"
				cpio -oL <$EXISTFILES | compress -c > $1/$2/save/archive.cpio.Z
			else
				echo "	No file compression being used"
				cpio -oL -O $1/$2/save/archive.cpio <$EXISTFILES
			fi
			if [ $? != 0 ]; then
				echo "Save of old files failed. \c"
				echo "See README file for instructions."
				cd $3
			        ./backoutpatch -s $2
				echo "Installpatch is terminating."
				rm -f /tmp/*.$$
				rm -fr $1/$2
				exit 4
			else
				chmod 600 $1/$2/save/archive.cpio*
				touch $1/$2/.oldfilessaved
			fi
			cd $3
		fi
	fi
}

# Description:
#	Finish up the patch
# Parameters:
#	$1	- patch database directory
#	$2	- patch number

set_patch_status() {
	mv -f /tmp/ACTION.$patchnum $1/$2 >/dev/null 2>&1
	cp -p README.$2 backoutpatch $1/$2 >/dev/null 2>&1
	touch $1/$2/.applied
	rm -f /tmp/*.$$
}

# Description:
# 	Spool a copy of the patch into the client root template area
#			NOT IMPLEMENTED AT THIS TIME
# Parameters:
#	none

spool_client_patch() {
	echo > /dev/null
      #	if [ "$is_server" = "yes" ]
      #	then
      #		echo "Spooling patch packages to root templates for future clients"
      #		echo "[NOTE: existing clients will need patches applied individually]"
      #		for i in $rootlist; do
      #			patchpkg=`expr $i : '\([^,]*\),.*'`
      #			spoolloc=`expr $i : '[^,]*,\([^,]*\),.*'`
      #			spoolsize=`expr $i : '[^,]*,[^,]*,\(.*\)'`
      #			arch=`expr $spoolloc : '[^_]*_[^_]*_\(.*\)'`
      #			ver=`expr $spoolloc : '[^_]*_\([^_]*\)_.*'`
      #			abbrev=`expr $spoolloc : '\([^_]*\)_.*'`
      #			echo $patchpkg $spoolloc $spoolsize $arch $ver $abbrev
      #	
      #			if [ ! -d /export/root/templates/$prodver/$spoolloc ]; then
      #				mkdir -m 750 -p /export/root/templates/$prodver/$spoolloc
      #			fi
      #			find $patchpkg -print | cpio -pd /export/root/templates/$prodver/$spoolloc
      #			if [ $? != 0 ]; then
      #				echo "Error while adding patch to root template."
      #				echo "See README file for instructions."
      #				rm -fr /export/root/templates/$prodver/$spoolloc
      #				cd $patchdir
      #				./backoutpatch -s $patchnum
      #				echo "Installpatch is terminating."
      #				rm -fr $PATCHDB/$patchnum
      #				rm -f /tmp/*.$$
      #				exit 11
      #			fi
      #
      #			echo SPOOLED_ROOT=$arch":"/export/root/templates/$prodver/$spoolloc >> $SOFTINFO/$prodver
      #			echo ROOT=$arch:$abbrev,$spoolsize,$ver >> $SOFTINFO/$prodver
      #			echo /SPOOLED_ROOT=$arch":".export.root.templates.$prodver.$spoolloc/d >> $PATCHDB/$patchnum/softinfo_sed
      #			echo /ROOT=$arch:$abbrev,$spoolsize,$ver/d >> $PATCHDB/$patchnum/softinfo_sed
      #			echo "/export/root/templates/$prodver/$spoolloc" >> $PATCHDB/$patchnum/spooled_dirs
      #		done
      #	fi
}

# Description:
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- patch directory

trap_backoutsaved() {
	echo "Interrupt signal detected. Backing out patch:"
	cd $3
	./backoutpatch -s $2
	echo "Installpatch is terminating."
	rm -fr $1/$2
	rm -f /tmp/*.$$
	exit 12
}

# Description:
# Parameters:
#	$1	- patch directory
#	$2	- patch number

trap_backout() {
	echo "Interrupt signal detected. Backing out patch:"
	cd $1
	./backoutpatch $2
	echo "Installpatch is terminating."
	rm -f /tmp/*.$$
	exit 12
}

# Description:
# Parameters:
# 	$1	- patch database directory
#	$2	- patch number

trap_notinstalled() {
	echo "Interrupt signal detected. Patch not installed."
	echo "Installpatch is terminating."
	rm -f /tmp/*.$$
	rm -fr $1/$2
	exit 12
}

# Description:
#	 Make sure effective UID is '0'
# Parameters:
#	none

validate_uid() {
	uid=`/usr/bin/id | sed 's/uid=\([0-9]*\)(.*/\1/'`
	if [ "$uid" != "0" ] ; then
		echo "You must be root to execute this script."
		exit 3
	fi
}

# Description:
#	Assume that any system on which the SUNWcsu package is NOT
#	installed is a client. It is a safe bet that this criterion
#	will remain valid through Solaris 2.3. Later releases may require
#	that this test be changed. Make sure pkgadd is executable too.
# Parameters:
#	none
# Globals Set:
#	client

verify_client() {
	/usr/bin/pkginfo -q SUNWcsu
	if [ $? != 0 ]; then
		client=yes
		sum /usr/sbin/pkgadd > /dev/null 2>&1
		if [ $? != 0 ]; then
			echo "The /usr/sbin/pkgadd command is not executable."
			echo "See the README file for instructions for making this"
			echo "command executable."
			exit 9
		fi
	fi
}

############################################
#		Main Routine		   #
############################################

#
# -	Get the product version <name>_<version> of local Solaris
#	installation (sets the prodver global variable)
# -	Parse the argument list and set globals accordingly
# -	Make sure the user is running as 'root'
#
echo
parse_args $*
validate_uid
get_os_version "$SOFTINFO"

#
# Change to the patch directory and set globals according to the patchID
# found in the pkginfo files of the patch packages
#
activate_patch "$patchdir"

#
# -	Check to see if patch has already been applied, and exit if already
# 	applied successfully
# -	Check whether patch has been obsoleted by a prior patch
# -	Determine if system being patch is a client, and make sure pkgadd
#	is usable servers must export to clients for read-only root access)
# -	Determine if system being patched is a server (CURRENTLY DISABLED)
#
# -	Make sure the patch doesn`t contain any symbolic links. If it does,
#	print out an error. Sustaining engineering will need to re-engineer
#	the patch to replace a symbolic link in one of the pre or post install
#	scripts.
#
check_if_applied "$PATCHDB" "$patchnum"

check_if_obsolete "$PATCHDB"

verify_client

check_if_server

check_for_symbolic_link "$patchdir"

trap 'trap_notinstalled "$PATCHDB" "$patchnum"' 1 2 3 15

# -	Generate list of patch packages to be installed
# -	Find out those packages which are already installed on
#	the system
# -	Check to see if there is any work to do
# -	Generate list of files to be installed
# -	Check for validation errors
# -	Generate a list of files to be patched that already exist
# -	Create patch archive and save area

gen_patchpkg_list

check_pkgs_installed "$PKGDB" "$PATCHDB" "$patchnum"

check_for_action "$client" "$is_a_root_pkg"

gen_install_filelist "$PKGDB" "$PKGDBARG"

check_validation "$validate"

gen_patch_filelist

create_archive_area "$PATCHDB" "$patchnum"

trap 'trap_backoutsaved "$PATCHDB" "$patchnum" "$patchdir"' 1 2 3 15

# -	Save current versions of files to be patched
# -	On servers, spool the patch into /export/root/templates for
#	future clients (CURRENTLY DISABLED)
# -	Build admin file for later use by pkgadd

save_overwritten_files "$PATCHDB" "$patchnum" "$patchdir" "$saveold"

spool_client_patch

build_admin_file

trap 'trap_backout "$patchdir" "$patchnum"' 1 2 3 15

# -	Install the patch packages
# -	Print results of install
# -	Save ACTION file if exists, README file and backoutpatch
#	script

install_patch_pkgs "$PATCHDB" "$patchnum" "$patchdir" "$PKGDBARG" "$PKGDB"

print_results

set_patch_status "$PATCHDB" "$patchnum"

echo "\nPatch installation completed."
echo "See $PATCHDB/$patchnum/log for more details."

exit 0
