#!/bin/sh
# @(#) installpatch 3.7 93/08/11 SMI
# (internal revision info): $Revision: 1.28 $ $Date: 1993/04/23 22:48:17 $
# is_server feature is disabled for now....
#
# change log (starting from 2/8/93):
#
# 2/8/93   - Installpatch and backoutpatch should permit a patch
#	     to be installed which contains only postinstall scripts
#	     and no actual files delivered.
# 2/9/93   - Installpatch should resolve macros in the pkgmap.
# 2/9/93   - pkgadd should be executed in such a way that messages from
#	     postinstall script are echoed to the screen in addition to
#	     the log.
# 2/10/93  - verify that patch being installed was not obsoleted by a
#	     patch already installed on the system.
# 4/22/93 -  Permit pkgadd to return codes other than 0 (2, 10, and 20
#	     are ok).
#	     Make sure installpatch is run as root.
#	     Correct calls to backoutpatch.  Make sure backoutpatch is
#	     called from patch directory.
#	     Add -p option (to print currently-applied patches).
# 4/23/93 -  If user turned validation off, keep a list of files that
#	     failed validation.  backoutpatch won't do installf's on
#	     these files.  This ensures that files that failed validation
#	     before the patch was applied will still fail validation
#	     after the patch is applied.
# 4/23/93 -  Do the df command in /var/sadm/patch, not /var/sadm in
#	     case it's a symlink to another file system. 
PATH=/usr/sbin:/usr/bin:$PATH
export PATH

print_obsolete_msg() {
    echo "This patch is obsoleted by a patch which has already been applied"
    echo "to this system.  Application of this patch would leave the system"
    echo "in an inconsistent state.  Patch installation is aborted."
}

validate="yes"
saveold="yes"
is_server="no"

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

USAGE="usage: installpatch [-u] [-d] [-p] <patch directory>"

for i in $*
do
	case $i in
	-u)	validate="no"; shift;;
	-d)	saveold="no"; shift;;
	-p)	print_applied_patches; exit 0;;
	-*)	echo $USAGE; exit 2;;
	*)	break;;
	esac
done

echo "@(#) installpatch 3.7 93/08/11"

if [ $# -lt 1 ]; then
    echo "Patch number not specified."
    echo 'See the section on "Instructions for applying the patch" in README file.'
    exit 1
fi

if [ ! -d $1 ]; then
    echo "$1 is not a patch."
    echo 'See the section on "Instructions for applying the patch" in README file.'
    exit 1
fi

if [  `echo $1 | grep -v "^/"` ]; then
    echo "$1 is not a full path name."
    echo 'See the section on "Instructions for applying the patch" in README file.'
    exit 1
fi

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

# change directory to location of patch
patchdir=$1
cd $patchdir

thisdir=`pwd`
patchnum=`basename $thisdir`
pbase=`expr $patchnum : '\(.*\)-.*'`
prev=`expr $patchnum : '.*-\(.*\)'`

if [ -f /var/sadm/patch/$patchnum/.applied ]; then
	echo "Patch has already been applied."
	echo "See README file for instructions."
	rm -f /tmp/*.$$
	exit 1
else
	rm -fr /var/sadm/patch/$patchnum
fi

#
# determine whether this patch has been obsoleted by a prior patch
#
if [ -d /var/sadm/patch ]; then
	cd /var/sadm/patch;
	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 [ $j = X -o "$j" = "*/pkginfo" ]; then
				break;
			fi
			Patchid=`sed -n 's/^[ 	]*SUNW_PATCHID[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $j`
			if [ "$Patchid" = "" ] ; then
				continue
			fi
			thisbase=`expr $Patchid : '\(.*\)-.*'`
			thisrev=`expr $Patchid : '.*-\(.*\)'`
			if [ $thisbase = $pbase -a \
			     $prev -lt $thisrev ] ; then
				print_obsolete_msg
				exit 1
			fi
			obsoletes=`sed -n 's/^[	 ]*SUNW_OBSOLETES[ 	]*=[ 	]*\([^ 	]*\)[ 	]*$/\1/p' $j`
			while [ "$obsoletes" != "" ] ; do
				opatchid=`expr $obsoletes : '\([0-9\-]*\).*'`
				obsoletes=`expr $obsoletes : '[0-9\-]*[ ,]*\(.*\)'`
				# prevent infinite loop.  If we couldn't
				# find a valid patch id, just quit.
				if [ "$opatchid" = "" ] ; then
					break;
				fi
				
				obase=`expr $opatchid : '\(.*\)-.*'`
				if [ "$obase" = "" ] ; then
					# opatchid didn't have revision
					# field, which might be supported
					# someday (since we don't use
					# the revision field for obsoletion
					# testing anyway)
					obase=$opatchid
				fi
				if [ $obase = $pbase ] ; then
					print_obsolete_msg
					exit 1
				fi
			done
		done
		cd ..
	done
	cd $patchdir
fi

#
# determine if system is a client.  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.2.
# Later releases may require that this test be changed.
#

client=no
/usr/bin/pkginfo -q SUNWcsu
if [ $? != 0 ] ; then
	client=yes
fi

trap 'echo "Interrupt signal detected.  Patch not installed.";
rm -f /tmp/*.$$; rm -fr /var/sadm/patch/$patchnum; exit 1' 1 2 3 15
#
# generate list of packages to be installed
#

pkglist=
for i in */pkginfo X; do
	if [ "$i" = "X" ] ; then
		break
	fi
	pkg=`expr $i : '\(.*\)/pkginfo'`
	pkglist="$pkglist $pkg"
done
if [ "$pkglist" = "" ]; then
        echo "Patch directory is not of expected format."
	echo "$USAGE"
        exit 1
fi


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

#
#determine whether system being patched is a server.
#

#if grep -s "SPOOLED_ROOT" /var/sadm/softinfo/$prodver > /dev/null 2>&1
#then
#	is_server="yes"
#fi

# step through each package in pkglist to find
# those packages that are already installed on the system.

finalpkglist=
rootlist=
is_a_root_pkg=no
for i in $pkglist; do
        #
        # find package instance of originally-installed package
        #
        Pkgabbrev=`sed -n 's/^[ 	]*PKG[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $i/pkginfo`
        Pkgarch=`sed -n 's/^[ 	]*ARCH[ 	]*=[ 	]*\([^ 	]*\)[	 ]*$/\1/p' $i/pkginfo`
        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
        pkginst=
        for j in /var/sadm/pkg/$Pkgabbrev /var/sadm/pkg/$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;
                fi
        done

        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" /var/sadm/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

#
# is there any work to do?
#
if [ "$pkglist" = "" -a "$rootlist" = "" ]; then
	if [ $client = no -o $is_a_root_pkg = yes ] ; then
 		echo "The packages to be patched are not installed on this system."
		echo "Installpatch is terminating."
		exit 1
	else
		echo "This patch is not applicable to client systems."
		echo "Installpatch is terminating."
		exit 0
	fi
fi


#
# Make sure pkgadd is executable.  On client systems, it won't be
# readable unless the system administrator has exported it with
# read-only root access.
#
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 1
fi

#
# generate list of files to be installed
#

rm -f /tmp/patchfiles.$$
echo "generating list of files to be patched"
for i in $pkglist; do
	patchpkg=`expr $i : '\(.*\),.*'`
	pkginst=`expr $i : '.*,\(.*\)'`
	basedir=`grep '^BASEDIR' /var/sadm/pkg/$pkginst/pkginfo | \
	    sed -e 's@.*=\ *@@' -e 's@/$@@' -e 's@/a$@/@'`
	sed -e '/^:/d' \
	    -e '/^[^ ][^ ]* i/d' \
	    -e 's/^[^ ]* . [^ ]* \([^ ]*\).*$/\/\1/' \
	    -e 's/=.*//' \
	    -e 's, \$BASEDIR/,,' \
	    -e "s,^.,$basedir&," $patchpkg/pkgmap > /tmp/pkgfiles.$$

	if [ -s /tmp/pkgfiles.$$ ]; then
		#
		# resolve any macros in the list of files
		#
		(rm -f /tmp/pkgmacros.$$ /tmp/resolvedfiles.$$
		cat /var/sadm/pkg/$pkginst/pkginfo |
		while read i
		do
			echo `echo $i| sed -e 's/^\(.*\)=.*/\1/'`=\"`echo $i|sed -e 's/^\(.*\=\)//'`\" | grep -v '^PATH' >> /tmp/pkgmacros.$$
		done
		. /tmp/pkgmacros.$$
	        cat /tmp/pkgfiles.$$ | 
	        while read i 
	        do
			eval echo $i >> /tmp/resolvedfiles.$$
		done
		)

		if [ -s /tmp/resolvedfiles.$$ ]; then
			/usr/sbin/pkgchk -i /tmp/resolvedfiles.$$ 2>&1 | \
				grep -v '^WARNING' >> /tmp/pkgchk.out.$$ 
		fi

	 	cat /tmp/resolvedfiles.$$ >> /tmp/patchfiles.$$
	fi

done

if [ "$validate" = "yes"  -a  -s /tmp/pkgchk.out.$$ ]; then
	if grep ERROR /tmp/pkgchk.out.$$ >/dev/null ; then
		echo "The following validation error was found: \n"
		cat /tmp/pkgchk.out.$$
		echo 
		echo "See the README file for instructions regarding"
		echo "patch validation errors."
		rm -f /tmp/*.$$
		exit 1
	fi
fi

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

if [ "$validate" != "yes"  -a  -s /tmp/pkgchk.out.$$ ]; then
	grep ERROR /tmp/pkgchk.out.$$ | sed 's/ERROR: //' > \
		 /tmp/valerr.$$
fi

#
# Generate list of files to be patched that already exist.
# Calculate the bytes required to save the files as well.
#

size_total=0
if [ -s /tmp/patchfiles.$$ ] ; then
	cat /tmp/patchfiles.$$ |
	(while read j
	do
		if ls -d $j >/dev/null 2>&1
		then
		    echo $j >> /tmp/existfiles.$$
		    size=`wc -c $j`
		    size=`echo $size | sed 's/\ .*//'`
		    [ $size ] && size_total=`expr $size_total + $size`
		fi
	done;echo $size_total > /tmp/bytes_required.$$)
else
	rm -f /tmp/existfiles.$$
fi

#
# Create patch archive and save area if it does not already exist.
#

if [ ! -d /var/sadm/patch ] ; then
	echo "Create patch archive area"
	mkdir /var/sadm/patch
	chown bin /var/sadm/patch
	chgrp bin /var/sadm/patch
	chmod 555 /var/sadm/patch
fi

if [ ! -d /var/sadm/patch/$patchnum ] ; then
	mkdir /var/sadm/patch/$patchnum
fi

if [ ! -d /var/sadm/patch/$patchnum/save ] ; then
	mkdir /var/sadm/patch/$patchnum/save
fi

if [ -s /tmp/valerr.$$ ] ; then
	cp /tmp/valerr.$$ /var/sadm/patch/$patchnum/.validation.errors
fi

# if a server, save the patches in the /export/root/templates
# directory so that future clients will be patched.

trap 'echo "Interrupt signal detected.  Backing out patch:";
cd $patchdir; ./backoutpatch -s $patchnum; rm -fr /var/sadm/patch/$patchnum; 
rm -f /tmp/*.$$; exit 1' 1 2 3 15

if [ "$is_server" = "yes" ]
then
	echo "applying patch package to root templates for future clients"
	echo "(Note that existing clients will need patch applied directly)"
	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 -p /export/root/templates/$prodver/$spoolloc
		fi
		find $patchpkg -print | cpio -pmdv /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
			rm -fr /var/sadm/patch/$patchnum
			rm -f /tmp/*.$$
			exit 1
		fi
		echo SPOOLED_ROOT=$arch":"/export/root/templates/$prodver/$spoolloc >> /var/sadm/softinfo/$prodver
		echo ROOT=$arch:$abbrev,$spoolsize,$ver >> /var/sadm/softinfo/$prodver
		echo /SPOOLED_ROOT=$arch":".export.root.templates.$prodver.$spoolloc/d >> /var/sadm/patch/$patchnum/softinfo_sed
		echo /ROOT=$arch:$abbrev,$spoolsize,$ver/d >> /var/sadm/patch/$patchnum/softinfo_sed
		echo "/export/root/templates/$prodver/$spoolloc" >> /var/sadm/patch/$patchnum/spooled_dirs
	done
fi

# Save current versions of files to be patched unless user specified
# -d option.
#
if [ -s /tmp/existfiles.$$ ] ; then
	if [ "$saveold" = "yes" ]; then
		echo "Save old versions of files to be patched"
		# Is there enough space?
		bytes_avail=`df -b /var/sadm/patch | tail -1`
		bytes_avail=`echo $bytes_avail | sed 's/.*\ //'`
		bytes_avail=`expr $bytes_avail \* 1000`
		bytes_required=`cat /tmp/bytes_required.$$`
		if [ $bytes_required -gt $bytes_avail ]; then
		    echo "Insufficient space in /var/sadm to save old files."
		    echo "Space required in bytes:  $bytes_required"
		    echo "Space available in bytes:  $bytes_avail"
		    cd $patchdir
		    ./backoutpatch -s $patchnum
		    rm -f /tmp/*.$$
		    rm -fr /var/sadm/patch/$patchnum
		    exit 1 
		fi
		cd /
		cat /tmp/existfiles.$$ | cpio -pmdv /var/sadm/patch/$patchnum/save
		if [ $? != 0 ]; then
			echo "Save of old files failed."
			echo "See README file for instructions."
			cd $patchdir
		        ./backoutpatch -s $patchnum
			rm -f /tmp/*.$$
			rm -fr /var/sadm/patch/$patchnum
			exit 1
		fi
		touch /var/sadm/patch/$patchnum/.oldfilessaved
		cd $patchdir
	fi
else
	touch /var/sadm/patch/$patchnum/.nofilestosave
fi
 
#
# Build admin file for later use by pkgadd
#

cat > /tmp/admin.tmp.$$ << EOF
mail=
instance=unique
partial=nocheck
runlevel=nocheck
idepend=nocheck
rdepend=nocheck
space=quit
setuid=nocheck
conflict=nocheck
action=nocheck
EOF

#
# Install the patch packages
#
echo "Installing patch packages"

trap 'echo "Interrupt signal detected.  Backing out patch:";
cd $patchdir; ./backoutpatch $patchnum; rm -f /tmp/*.$$; exit 1' 1 2 3 15
for ij in $pkglist; do
	i=`expr $ij : '\(.*\),.*'`
	pkginst=`expr $ij : '.*,\(.*\)'`
	basedir=`grep '^BASEDIR' /var/sadm/pkg/$pkginst/pkginfo | sed -e 's@.*=\ *@@' -e 's@/a/@/@' -e 's@/a$@/@'`
	mkdir /var/sadm/patch/$patchnum/$i
	cp $i/pkgmap /var/sadm/patch/$patchnum/$i/pkgmap
	cp $i/pkginfo /var/sadm/patch/$patchnum/$i/pkginfo
 	cp /tmp/admin.tmp.$$ /tmp/admin.$$
	echo basedir=$basedir >> /tmp/admin.$$

	echo "Doing pkgadd of $i package:"
	/usr/sbin/pkgadd -S -a /tmp/admin.$$ -n -d $patchdir $i > /tmp/pkgaddlog.$$ 2>&1
	pkgadderr=$?
	cat /tmp/pkgaddlog.$$ >> /var/sadm/patch/$patchnum/log
	cat /tmp/pkgaddlog.$$
	rm /tmp/pkgaddlog.$$
	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.$patchnum for reason for failure."
		cp /var/sadm/patch/$patchnum/log /tmp/log.$patchnum
		echo "Backing out patch:"
		cd $patchdir
		./backoutpatch $patchnum
		rm -fr /tmp/*.$$
		exit 1
	fi
done

# Save ACTION file if exists, README file and backoutpatch script.
mv -f /tmp/ACTION.$patchnum /var/sadm/patch/$patchnum > /dev/null 2>&1
cp -p README.$patchnum backoutpatch /var/sadm/patch/$patchnum > /dev/null 2>&1

touch /var/sadm/patch/$patchnum/.applied
rm -f /tmp/*.$$

echo "Patch installation finished"
exit 0


