#!/bin/ksh -h
# @(#) backoutpatch 5.3 95/10/10 SMI
#
# Exit Codes:
#		0	No error
#		1	Usage error
#		2	Attempt to backout a patch that hasn't been applied
#		3	Effective UID is not root
#		4	No saved files to restore
#		5	pkgrm failed
#		6	Attempt to back out an obsoleted patch
#		7	Attempt to restore CPIO archived files failed
#		8	Invalid patch id format
#		9	Prebackout script failed
#		10	Postbackout script failed
#		11	Suspended due to administrative defaults
#

# Set up the path to use with this script.

PATH=/usr/sbin:/usr/bin:$PATH
export PATH

umask 007

# Global Files

TMPSOFT=/tmp/soft.$$
ADMINFILE=/tmp/admin.$$
LOGFILE=/tmp/backoutlog.$$
RESPONSE_FILE=/tmp/response.$$

force=no
pkginstlist=
pkglist=
diPatch="no"
ObsoletedBy="none"
ThisPatchFnd="no"
PatchedPkgs=""
InstPkgs=""
RebootRqd="no"

ROOTDIR="/"
PATCHDB="/var/sadm/patch"
PKGDB="/var/sadm/pkg"
SOFTINFO="/var/sadm/softinfo"
CONTENTS="/var/sadm/install/contents"
TMP_LIB_DIR="/tmp/TmpLibDir.$$"
PKGDBARG=""
PATCH_PID=""

PatchIdFormat='^[A-Z]*[0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9]$'

#
# Description:
#	Execute the prebackout script if it is executable. Fail if the
#	return code is not 0.
#
# Parameters:
#	$1	- package database directory
#	$2	- patch number
# Globals Set:
#	none
function execute_prebackout
{
	typeset -i retcode=0
	if [ -x $1/$2/prebackout ]
	then
		echo "Executing prebackout script..."
		$1/$2/prebackout
		retcode=$?
		if (( retcode != 0 ))
		then
			echo "prebackout script exited with return code $retcode."
			echo "Backoutpatch exiting."
			exit 9
		fi
	fi
}

#
# Description:
#	Execute the postbackout script if it is executable. Fail if the
#	return code is not 0.
#
# Parameters:
#	$1	- package database directory
#	$2	- patch number
# Globals Set:
#	none
function execute_postbackout
{
	typeset -i retcode=0
	if [ -x $1/$2/postbackout ]
	then
		echo "Executing postbackout script..."
		$1/$2/postbackout
		retcode=$?
		if (( retcode != 0 ))
		then
			echo "postbackout script exited with return code $retcode."
			echo "Backoutpatch exiting."
			exit 10
		fi
	fi
}

#
# Description:
#	Return the base code of the provided patch. The base code
#	returned will include the version prefix token (usu "-").
#
# Parameters Used:
#	$1	- patch number
#
function get_base_code {
	ret_value=${1:%[0-9]}
	last_value=$1

	while [[ $ret_value != $last_value ]]
	do
		last_value=$ret_value
		ret_value=${last_value%[0-9]}
	done

	cur_base_code=${ret_value%?}
}

#
# Description:
#	Return the version number of the provided patch.
#
# Parameters Used:
#	$1	- patch number
#	$2	- base code
#
function get_vers_no {
	cur_vers_no=${1:#$2?}
}

#
# Description:
#	Give a list of applied patches similar in format to the showrev -p
#	command. Had to write my own because the showrev command won't take
#	a -R option.
#
# Parameters:
#	$1	- package database directory
#
# Globals used
#	PatchNum
#
# Globals Set:
#	diPatch
#	ObsoletedBy
#	ThisPatchFnd
#	PatchedPkgs
#
# Revision History
#	1995-08-01	Added PATCH_OBSOLETES and expanded the tests for
#			direct instance patches since all necessary
#			is reviewed at this time, this function also
#			tests for obsolescence, dependencies and
#			incompatibilities.
#
function eval_inst_patches
{
	typeset -i PatchFound=0
	typeset -i ArrayCount=0

	set -A PkgArrElem
	set -A PatchArrElem
	set -A ObsArrElem

	olddir=$(pwd)

	#
	# First get the old-style patches and obsoletions
	#
	if [ -d $1 -a -d $PATCHDB ]
	then
		cd $1
		patches=
		patches=$(grep -l SUNW_PATCHID ./*/pkginfo | \
		    xargs sed -n 's/^SUNW_PATCHID=//p' | sort -u)

		if [ "$patches" != "" ]
		then
			for apatch in $patches
			do
				outstr="Patch: $apatch Obsoletes: "

				# Scan all the installed packages for this
				# patch number and return the effected
				# package instances
				patchvers=$(grep -l "SUNW_PATCHID=$apatch" \
				    ./*/pkginfo | sed 's,^./\(.*\)/pkginfo$,\1,' )

				# If there's a PATCH_INFO entry then this
				# is really a direct instance patch
				for package in $patchvers
				do
					break;
				done

				$(grep "PATCH_INFO_$apatch" $package/pkginfo 1>/dev/null 2>&1)
				if [[ $? -eq 0 ]]
				then
					continue
				fi

				PatchFound=1

				obsoletes_printed="n"
				for vers in $patchvers
				do
					if [ "$obsoletes_printed" = "n" ]
					then
						outstr="$outstr$(sed -n \
						    's/SUNW_OBSOLETES=//p' \
						    ./$vers/pkginfo) Packages: "
						outstr="$outstr$vers $(sed -n \
						    's/VERSION=//p' \
						    ./$vers/pkginfo)"
						obsoletes_printed="y"
					else
						outstr="$outstr, $vers $(sed \
						    -n 's/VERSION=//p' \
						    ./$vers/pkginfo)"
					fi
				done

				# The current patch is a progressive
				# instance patch
				if [[ $apatch = "$PatchNum" ]]
				then
					diPatch="no"
					ThisPatchFnd="yes"
				fi
			done
		fi
	fi

	#
	# Now get the direct instance patches
	#
	# DIPatches is a non-repeating list of all patches applied
	# to the system.
	#
	typeset -i TempCount=0

	InstPkgs=$(pkginfo -R $ROOTDIR | nawk ' { print $2; } ')

	for package in $InstPkgs
	do
		DIPatches=$(pkgparam -R $ROOTDIR $package PATCHLIST)
		for patch in $DIPatches
		do
			Obsoletes=$(pkgparam -R $ROOTDIR $package PATCH_INFO_$patch | nawk ' { print substr($0, match($0, "Obsoletes:")+11) } ')
			if [ -n "$Obsoletes" ]
			then
				PatchArrElem[$ArrayCount]=$patch;
				ObsArrElem[$ArrayCount]="$Obsoletes";
				PkgArrElem[$ArrayCount]=$package;
			else
				PatchArrElem[$ArrayCount]=$patch;
				PkgArrElem[$ArrayCount]=$package;
				ObsArrElem[$ArrayCount]="";
			fi
			ArrayCount=ArrayCount+1;

			# The current patch is a direct instance patch
			if [[ "$patch" = "$PatchNum" ]]
			then
				diPatch="yes"
				ThisPatchFnd="yes"
			fi

			# Is this patch obsoleted according to a pkginfo
			# file
			for obs_entry in "$Obsoletes"
			do
				if [[ "$obs_entry" = "$PatchNum" ]]
				then
					ObsoletedBy=$patch
				fi
			done
		done
	done

	typeset -i TestCount=0
	while [[ $TestCount -lt $ArrayCount ]]
	do
		typeset -i TempCount=TestCount+1

		# Scan all entries matching the current one
		PatchArrEntry=${PatchArrElem[$TestCount]}	# Current one
		ObsArrEntry=${ObsArrElem[$TestCount]}
		PkgArrEntry=${PkgArrElem[$TestCount]}

		if [[ "$PatchArrEntry" = "used" ]]
		then
			TestCount=TestCount+1
			continue
		fi

		while [[ $TempCount -lt $ArrayCount ]]
		do
			#
			# If this is another line describing this patch
			#
			if [[ ${PatchArrElem[$TempCount]} = $PatchArrEntry ]]
			then
				dont_use=0;

				PatchArrElem[$TempCount]="used"
				for pkg in $PkgArrEntry
				do
					if [[ $pkg = ${PkgArrElem[$TempCount]} ]]
					then
						dont_use=1;
						break;
					fi

				done

				if (( dont_use == 0 ))
				then
					PkgArrEntry="$PkgArrEntry ${PkgArrElem[$TempCount]}"
				fi

				dont_use=0;

				for obs in $ObsArrEntry
				do
					if [[ $obs = ${ObsArrElem[$TempCount]} ]]
					then
						dont_use=1;
						break;
					fi

				done

				if (( dont_use == 0 ))
				then
					ObsArrEntry="$ObsArrEntry ${ObsArrElem[$TempCount]}"
				fi
			fi
			TempCount=TempCount+1
		done

		if [[ $PatchArrEntry = "$PatchNum" ]]
		then
			export PatchedPkgs="$PkgArrEntry"
		fi

		# Now make it comma separated lists
		PkgArrEntry=$(echo $PkgArrEntry | sed s/\ /,\ /g)
		ObsArrEntry=$(echo $ObsArrEntry | sed s/\ /,\ /g)

		TestCount=TestCount+1
	done

	cd $olddir
}

# Description:
#	Print out the usage message to the screen
# Parameters:
#	none

function print_usage
{
cat<<EOF

   Usage: backoutpatch [-f] [-V] [-R <root_path>] [-S <service>] <patch number>
   Options:
        -f  force the backout regardless of whether the patch was
            superceded
        -V  print version number only
        -R  <root_path>
            Define the full path name of a subdirectory to use as the 
            root_path.  All package system information files are assumed 
            to be located in a directory tree starting in the specified 
            root_path.  All patch files generated from the installpatch 
            will be located in the same directory tree.  Cannot be 
            specified with the -S option
        -S  <service>
            Specify an alternate service (e.g. Solaris_2.3) for patch 
            package processing references.

EOF
}

# Description:
#	Patch obsolecense message, printed if the patch being backed
#	out was superceded by other patches 
# Parameters:
#	$1	- patch ID
#	$2	- patch revision number
#
function print_obsolete_msg
{
	outstr="This patch was obsoleted by patch $1"
	if [[ "$2" = "none" ]]
	then
		outstr="$outstr."
	else
		outstr="$outstr-$2."
	fi
	echo $outstr
	echo "Patches must be backed out in the order in"
	echo "which they were installed. Backoutpatch exiting."
}

# Description:
#	Parse the arguments and set all affected global variables
# Parameters:
#	Arguments to backoutpatch
# Globals Set:
#	force
#	PatchNum
#	ROOTDIR
#	PATCHDB
#	PKGDB
#	SOFTINFO
#	PKGDBARG
#	CONTENTS

function parse_args
{
	service_specified="n"
	rootdir_specified="n"
	while [[ "$1" != "" ]]
	do
		case $1 in
		-f)	force="yes"
			shift;;
		-B)	shift
			if [[ -d $1 ]]
			then
				PATCH_UNDO_ARCHIVE=$1
			else
				echo "No backout archive found."
				exit 1
			fi
			shift;;
		-V)	echo "@(#) backoutpatch 5.3 95/10/10"
			exit 0
			shift;;
		-S)	shift
			if [[ "$service_specified" != "n" ]]
			then
				echo "Only one service may be defined."
				print_usage
				exit 1
			elif [[ "$rootdir_specified" != "n" ]]
			then
				echo "The -S and -R options are mutually exclusive."
				print_usage
				exit 1
			fi
			get_OS_version "$SOFTINFO"
			if [[ "$1" != "$prodver" ]]
			then
				if [[ -d "/export/$1/var/sadm/pkg" ]]
				then
					ROOTDIR=/export/$1
					PATCHDB=$ROOTDIR/var/sadm/patch
					PKGDB=$ROOTDIR/var/sadm/pkg
					SOFTINFO=$ROOTDIR$SOFTINFO
					PKGDBARG="-R $ROOTDIR"
					CONTENTS=$ROOTDIR$CONTENTS
					service_specified="y"
				else
					echo "The $1 service cannot be found on this system."
					print_usage
					exit 1
				fi
			fi
			shift;;
		-R)	shift
			if [[ "$rootdir_specified" != "n" ]]
			then
				echo "Only one client may be defined."
				print_usage
				exit 1
			elif [[ "$service_specified" != "n" ]]
			then
				echo "The -S and -R options are mutually exclusive."
				print_usage
				exit 1
			fi
			if [[ -d "$1" ]]
			then
				ROOTDIR=$1
				PATCHDB=$ROOTDIR/var/sadm/patch
				PKGDB=$ROOTDIR/var/sadm/pkg
				SOFTINFO=$ROOTDIR$SOFTINFO
				PKGDBARG="-R $ROOTDIR"
				CONTENTS=$ROOTDIR$CONTENTS
				rootdir_specified="y"
			else
				echo "The $1 directory cannot be found on this system."
				print_usage
				exit 1
			fi
			shift;;
		-*)	print_usage
			exit 1;;
		 *)	break;;
		esac
	done
	PatchNum=$1
	#
	# If there is no patch number specified, exit with an error.
	#
	if [[ "$PatchNum" = "" ]]
	then
		echo "No patch number was specified."
		print_usage;
		exit 1
	fi
}

# Description:
#	Make sure the effective UID is '0'
# Parameters:
#	none
function validate_uid
{
	typeset -i uid
	uid=$(id | sed 's/uid=\([0-9]*\)(.*/\1/')
	if (( uid != 0 ))
	then
		echo "You must be root to execute this script."
		exit 3
	fi
}

# Description:
#	Get the product version <name>_<version> of local Solaris installation
# Parameters:
#	$1	- softinfo directory pathname
# Globals Set:
#	prodver
function 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:
#	Build the admin script for pkgadd
# Parameters:
#	none
# Globals Used:
#	ADMINFILE
function build_admin
{
	if [[ "$PatchMethod" = "direct" && -f /var/sadm/install/admin/patch ]]
	then
		ADMINFILE=/var/sadm/install/admin/patch
	else
		cat >$ADMINFILE <<EOF
mail=
instance=unique
partial=nocheck
runlevel=nocheck
idepend=quit
rdepend=quit
space=quit
setuid=nocheck
conflict=nocheck
action=nocheck
basedir=default
EOF
fi
}

# Description:
# 	Restore old versions of files
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- package command relocation argument
#	$4	- path name of contents file
function restore_orig_files
{
	olddir=
	file=
	ownerfound=
	srch=
	cfpath=
	instlist=
	filelist=
	if [[ ! -f $1/$2/.nofilestosave ]]
	then
		echo "Restoring previous version of files..."
		olddir=$(pwd)
		cd $ROOTDIR
		# Must retain backwards compatibility to restore
		# archives which were not stored as files
		if [[ -f $olddir/save/archive.cpio ]]
		then 
			filelist=$(cat $olddir/save/archive.cpio | cpio -it 2>/dev/null)
			cpio -idumv -I $olddir/save/archive.cpio
		else 
			if [[ -f $olddir/save/archive.cpio.Z ]]
			then
				filelist=$(zcat $olddir/save/archive.cpio.Z | \
							cpio -it 2>/dev/null)
				zcat $olddir/save/archive.cpio.Z | cpio -idumv
			else
				filelist=$(find . -print | sed "s/^.//")
				find  . -print | cpio -pdumv / 
			fi
		fi
		if [[ $? -ne 0 ]]
		then
			echo "Restore of old files failed."
			echo "See README file for instructions."
			rm -f /tmp/*.$$
			remove_libraries
			exit 7
		fi
		echo "Making package database consistent with restored files:"
		rm -f /tmp/fixfile.$$ > /dev/null 2>&1
		for file in $filelist
		do
			if [[ ! -f $file || -h $file ]]
			then
				continue
			fi
			# if file failed validation when the patch was 
			# installed, don't do an installf on it.  It should 
			# continue to fail validation after the patch is 
			# backed out.
			file1=$(expr $file : '\(\/.*\)')
			if [[ "$file1" = "" ]]
			then
				file1="/"$file
			fi
			srch="^$file1\$"
			if [[ -f $olddir/.validation.errors ]] && \
				grep "$srch" $olddir/.validation.errors >/dev/null 2>&1
			then 
				continue
			fi
			# The following commands find the file's entry in the
			# contents file, and return the first field of the 
			# entry. If the file is a hard link, the first field 
			# will contain an "=".  This will cause the -f test to 
			# fail and we won't try to installf the file.
			srch="^$file1[ =]"
			cfpath=$(grep "$srch" $CONTENTS | sed 's/ .*//')
			if [[ "$cfpath" = "" || ! -f "$ROOTDIR$cfpath" ]]
			then
				continue
			fi
			ownerfound=no
			# Parsing pkgchk output is complicated because all text
			# may be localized. Currently the only line in the 
			# output which contains a tab is the line of packages 
			# owning the file, so we search for lines containing a 
			# tab.  This is probably reasonably safe. If any of the
			# text lines end up with tabs due to localization, the 
			# pkginfo check should protect us from calling installf
			# with a bogus package instance argument.
			pkgchk $3 -lp $file1 | grep '	' | \
			while read instlist
			do
				for i in $instlist
				do
					pkginfo $3 $i >/dev/null 2>&1
					if [[ $? -eq 0 ]]
					then
						echo $i $file1 >> /tmp/fixfile.$$
						ownerfound=yes
						break
					fi
				done
				if [[ $ownerfound = "yes" ]]
				then
					break
				fi
			done
		done
		if [[ -s /tmp/fixfile.$$ ]]
		then
			sed 's/^\([^ ]*\).*/\1/' /tmp/fixfile.$$ | sort -u | \
			while read pkginst
			do
				grep "^${pkginst} " /tmp/fixfile.$$ | \
				sed 's/^[^ ]* \(.*\)/\1/' | \
				if [[ "$ROOTDIR" != "/" ]]
				then
					installf $PKGDBARG $pkginst -
					installf $PKGDBARG -f $pkginst
				else
					installf $pkginst -
					installf -f $pkginst
				fi
			done
		fi
		cd $olddir
	fi
}

#
# Description:
#	Change directory to location of patch
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
# Globals Set:
#	patchdir
#	PatchBase
#	PatchVers
function activate_patch
{
	eval_inst_patches $PKGDB

	if [[ $ThisPatchFnd = "yes" ]]
	then
		patchdir=$1/$2

		# For direct instance patches, this may not be here
		if [[ -d $patchdir ]]
		then
			cd $patchdir
		fi

		#
		# Get the patch base code (the number up to the version prefix) 
		# and the patch revision number (the number after the version prefix).
		#
		get_base_code $PatchNum
		PatchBase=$cur_base_code
		get_vers_no $PatchNum $cur_base_code
		PatchVers=$cur_vers_no
	else
		echo "Patch $2 has not been applied to this system."
 		if [[ -d $1/$2 ]]
		then
 			echo "Will remove directory $1/$2"
 			rm -r $1/$2
 		fi

		exit 2
	fi

}

# Description:
#	Find the package instances for this patch
# Parameters:
#	$1	- package database directory
#	$2	- patch number
# Globals Set:
#	pkginstlist

function get_pkg_instances
{
	pkginst=
	j=
	for j in $1/*
	do
		if grep -s "SUNW_PATCHID *= *$2" $j/pkginfo > /dev/null 2>&1
		then
			pkginst=$(basename $j)
			pkginstlist="$pkginstlist $pkginst"
		fi
	done
}

# Description:
# 	Check to see if this patch was obsoleted by another patch.
# Parameters:
#	$1	- patch database directory
#	$2	- patch ID
#	$3	- patch revision

function check_if_obsolete
{
	if [[ "$diPatch" = "yes" ]]
	then
		if [[ "$ObsoletedBy" = "none" ]]
		then
			return
		else
			print_obsolete_msg "$ObsoletedBy" "none"
			exit 6
		fi
	else
		Patchid=
		oldbase=
		oldrev=
		opatchid=
		obase=
		obsoletes=
		i=
		j=
		if [[ -d $1 ]]
		then
			cd $1
			for i in * X
			do
				if [[ $i = X || "$i" = "*" ]]
				then
					break
				elif [[ ! -d $i ]]
				then
					continue
				fi
				cd $i
				for j in */pkginfo X
				do
					if [[ "$j" = "X" || "$j" = "*/pkginfo" ]]
					then
						break
					fi
					Patchid=$(sed -n 's/^[ 	]*SUNW_PATCHID[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $j)
					if [[ "$Patchid" = "" ]]
					then
						continue
					fi
					oldbase=${Patchid%-*}
					oldrev=${Patchid#*-}
					if [[ $oldbase = $2 && $3 -lt $oldrev ]]
					then
						print_obsolete_msg "$2" "$oldrev"
						exit 6
					fi
					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,
							# might be supported someday 
							# (we don't use the revision 
							# field for obsoletion testing)
							obase=$opatchid
						fi
						if [[ $obase = $2 && $2 != $oldbase ]]
						then
							print_obsolete_msg "$Patchid" "none"
							exit 6
						fi
					done
				done
				cd $1
			done
		fi
	fi
}

# Description:
#	Check to see if originally modified files were saved. If not,
#	the patch cannot be backed out.
# Parameters:
#	$1	- patch database directory
#	$2	- patch number

function check_if_saved
{
	if [[ ! -f $1/$2/.oldfilessaved && \
		 ! -f $1/$2/.nofilestosave ]]
	then
		echo "Patch $2 was installed without backing up the original"
		echo "files. It cannot be backed out."
		exit 4
	fi
}

# Description:
#	Get the list of packages 
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
# Globals Set:
#	pkglist

function get_package_list
{
	pkg=
	i=
	cd $1/$2
	for i in */pkgmap
	do
		pkg=`expr $i : '\(.*\)/pkgmap'`
		pkglist="$pkglist $pkg"
	done
}

# Description:
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- softinfo directory
#	$4	- product version
# Globals Used:
#	TMPSOFT

function cleanup
{
	rm -f /tmp/*.$$

	if [[ -d $1 ]]
	then
		cd $1
		if [[ -f softinfo_sed ]]
		then
			sed -f softinfo_sed $3/$4 > $TMPSOFT
			mv $3/$4 $3/sav.$4
			cp $TMPSOFT $3/$4
		fi
		rm -fr ./$2/*
		rm -fr $2
	fi
}

# Description:
#	Remove appropriate patch packages from the system 
#	NOTE: this will not restore the overwritten or removed files, but will
#	      remove any files which were added by the patch.
# Parameters:
#	$1	- patch database directory
#	$2	- patch number
#	$3	- packaging command relocation argument 
# Globals Used:
#	ADMINFILE
#	pkginstlist

function remove_patch_pkgs
{
	pkgrmerr=
	i=
	for i in $pkginstlist
	do
		echo "\nRemoving patch package for $i:"
		pkgrm $3 -a $ADMINFILE -n $i>$LOGFILE 2>&1
		pkgrmerr=$?
		cat $LOGFILE >>$1/$2/log
		cat $LOGFILE | grep -v "^$"
		rm -f $LOGFILE
		if [[ $pkgrmerr != 0 && $pkgrmerr != 2 && \
			$pkgrmerr != 10 && $pkgrmerr != 20 ]]
		then
			echo "pkgrm of $i package failed with return code $pkgrmerr."
			echo "See $1/$2/log for details."
			rm -fr /tmp/*.$$
			remove_libraries
			exit 5
		fi
	done
}

# Description:
#	Copy required libraries to TMP_LIB_DIR, set and
#	export LD_PRELOAD.
# Parameters:
#	none
# Environment Variables Set:
#	LD_PRELOAD
#
function move_libraries
{
	typeset -i Rev
    Rev=$(uname -r | sed -e 's/\..*$//')
    if (( Rev >= 5 ))
    then

        if [[ ! -d $TMP_LIB_DIR ]]
        then
            mkdir -p -m755 $TMP_LIB_DIR
        fi

        LD_PRELOAD=
        for Lib in libc libdl libelf libintl libw libadm
        do
            cp /usr/lib/${Lib}.so.1 ${TMP_LIB_DIR}/${Lib}.so.1

            chown bin ${TMP_LIB_DIR}/${Lib}.so.1
            chgrp bin ${TMP_LIB_DIR}/${Lib}.so.1
            chmod 755 ${TMP_LIB_DIR}/${Lib}.so.1

            LD_PRELOAD="${LD_PRELOAD} ${TMP_LIB_DIR}/${Lib}.so.1"
        done
        export LD_PRELOAD
    fi
}


# Description:
#	remove the TMP_LIB_DIR directory
# Parameters:
#	none
# Environment Variables Set:
#	LD_PRELOAD
#
function remove_libraries
{
	LD_PRELOAD=
	export LD_PRELOAD
	rm -rf $TMP_LIB_DIR
}

# Description:
#	unobsolete direct instance patches that this one obsoleted
# Parameters:
#	none
# Environment Variables Used:
#	ROOTDIR
#	InstPkgs
#	PatchNum
#
function di_unobsolete
{
	cd $ROOTDIR
	cd var/sadm/pkg
	for pkg in $InstPkgs; do
		PATCHLIST=$(pkgparam -R $ROOTDIR $pkg PATCHLIST)
		for Patchno in $PATCHLIST; do
			if [[ -f $pkg/save/$Patchno/obsolete || -f $pkg/save/$Patchno/obsolete.Z ]]
			then
				egrep -s $PatchNum $pkg/save/$Patchno/obsoleted_by
				if [[ $? -eq 0 ]]
				then
					cat $pkg/save/$Patchno/obsoleted_by | nawk -v patchno=$PatchNum '
						$0 ~ patchno	{ next; }
						{ print; } ' > $pkg/save/$Patchno/obsoleted_by.new
					if [[ -s $pkg/save/$Patchno/obsoleted_by.new ]]
					then
						mv $pkg/save/$Patchno/obsoleted_by.new $pkg/save/$Patchno/obsoleted_by
					else
						rm -f $pkg/save/$Patchno/obsoleted_by.new $pkg/save/$Patchno/obsoleted_by
						if [[ -f $pkg/save/$Patchno/obsolete ]]
						then
							mv $pkg/save/$Patchno/obsolete $pkg/save/$Patchno/undo
						else
							mv $pkg/save/$Patchno/obsolete.Z $pkg/save/$Patchno/undo.Z
						fi
					fi
				fi
			fi
		done
	done
}

# Description:
#	backout a patch applied using direct instance patching
# Parameters:
#	none
# Environment Variable Set:
#
function di_backout
{
	typeset -i Something_Backedout=0
	typeset -i exit_code=0

	cd $ROOTDIR/var/sadm/pkg
	if [[ $ThisPatchFnd = "no" ]]
	then
		echo "Patch $PatchNum has not been applied to this system."
		exit 2
	fi

	#
	# First scan for the undo files and make sure, none of them have
	# been obsoleted
	#
	for pkg in $PatchedPkgs; do
		if [[ -f $pkg/pkginfo ]]
		then
			if [[ -d $pkg/save/$PatchNum ]]
			then
				if [[ -f $pkg/save/$PatchNum/obsolete || -f $pkg/save/$PatchNum/obsolete.Z ]]
				then
					ObsoletedBy=$(cat $pkg/save/$PatchNum/obsoleted_by)
					print_obsolete_msg "$ObsoletedBy" "none"
					exit 6
				elif [[ ! -f $pkg/save/$PatchNum/undo && ! -f $pkg/save/$PatchNum/undo.Z ]]
				then
					echo "Patch $PatchNum was installed without backing up the original"
					echo "files. It cannot be backed out."
					exit 4
				fi
			else
				echo "Patch $PatchNum was installed without backing up the original"
				echo "files. It cannot be backed out."
				exit 4
			fi
		else
			echo "Patch $PatchNum was installed without backing up the original"
			echo "files. It cannot be backed out."
			exit 4
		fi
	done

	#
	# With no obsoletions detected, we pkgadd the undo packages.
	#
	for pkg in $PatchedPkgs; do
		if [[ -f $pkg/save/$PatchNum/undo.Z ]]
		then
			uncompress $pkg/save/$PatchNum/undo.Z 1> $LOGFILE 2>&1
		fi

		# Get the prior patch list since the checkinstall script
		# doesn't have permission to make this enquiry.
		OLDLIST=$(pkgparam -R $ROOTDIR $pkg PATCHLIST)

		echo OLDLIST=\'$OLDLIST\' > $RESPONSE_FILE

		pkgadd -n -r $RESPONSE_FILE -R $ROOTDIR -a $ADMINFILE -d $pkg/save/$PatchNum/undo all 1>> $LOGFILE 2>&1

		exit_code=$?

		# If it's a suspend (exit code 4), then the
		# message type is the appropriate installpatch
		# exit code and the appropriate message follows.
		# A suspend means, nothing has been installed.
		if (( exit_code == 4 ))	# suspend
		then
			Message=$(egrep PaTcH_MsG $LOGFILE | sed s/PaTcH_MsG\ //)
			if [[ $Message = "" ]]
			then
				exit_code == 5
			else
				Msg_Type=$(echo $Message | nawk ' { print $1 } ')
				Message=$(echo $Message | sed s/$Msg_Type\ //)
				echo "$Message" >> $LOGFILE
				echo "$Message"
				exit $Msg_Type
			fi
		fi

		if ((exit_code == 5 ))
		then	# administration
			echo "Backout has been halted due to administrative defaults."
			cat $LOGFILE
			exit 11
		elif (( exit_code == 10 || exit_code == 20 ))
		then
			echo "NOTE: After backout the target host will need to be rebooted."
			RebootRqd="yes"
		elif (( exit_code != 0 ))
		then
			egrep ERROR $LOGFILE
			exit 7
		else
			Something_Backedout=1
		fi
	done

	if (( Something_Backedout == 1 ))
	then
		di_unobsolete
	else
		echo "Patch number $PatchNum backout packages were not found."
	fi
}

#########################################################
#							#
# 			Main routine			#
#							#
#########################################################

# -	Parse the argument list and set globals accordingly
# -	Make sure the user is running as 'root'
# -	Get the product version <name>_<version> of the local
#	Solaris installation
# - 	activate the patch

parse_args $*
validate_uid
get_OS_version "$SOFTINFO"

activate_patch "$PATCHDB" "$PatchNum"

if [[ "$diPatch" != "yes" ]]
then
	echo $PatchNum | grep $PatchIdFormat >/dev/null
	if [[ $? -ne 0 ]]
	then
		echo "Invalid patch id format: $PatchNum"
		exit 8
	fi
fi

#
# Check to see if this patch was obsoleted by another patch
#
if [[ "$force" = "no" || "$diPatch" = "yes" ]]
then
	check_if_obsolete "$PATCHDB" "$PatchBase" "$PatchVers"
fi
execute_prebackout "$PATCHDB" "$PatchNum"

# -	Check to see if original files were actually saved
# -	Generate list of packages to be removed
# -	Find the package instances for this patch
# -	Build admin file for later use by pkgrm
# -	pkgrm patch packages
# -	Restore the original files which were overwritten by the patch
# -	Update the prodver file & cleanup tmp files

build_admin

if [[ "$diPatch" = "yes" ]]
then
	di_backout
	rm -f $RESPONSE_FILE
else
	check_if_saved "$PATCHDB" "$PatchNum"

	get_package_list "$PATCHDB" "$PatchNum"

	get_pkg_instances "$PKGDB" "$PatchNum"

	trap 'remove_libraries' HUP INT QUIT TERM
	move_libraries

	remove_patch_pkgs "$PATCHDB" "$PatchNum" "$PKGDBARG"

	restore_orig_files "$PATCHDB" "$PatchNum" "$PKGDBARG" "$CONTENTS"

	remove_libraries

fi

execute_postbackout "$PATCHDB" "$PatchNum"

cleanup "$PATCHDB" "$PatchNum" "$SOFTINFO" "$prodver"

echo "Patch $PatchNum has been backed out."

exit 0
