#!/bin/sh
#
# $Id: do-patch,v 1.32 1998/11/04 15:43:01 casper Exp $
#
# Automatic patch application script for Solaris 2.x.
#
# Patch.tar.Z files should be put in <path>/patches/`uname -r`s,
# where <path> is the directory where do-patch lives.
#
# The patches you want to have applied should be in patchlist.common.
# For servers, this script should be called as ``do-patch.server'',
# in that case patches are taken from ``patchlist.server''.
#
# Server are also all machines that locally mount /opt.
# That assumption may be wrong for some sites.
# (See also the -c and -s options)
#
# If a patchlist.`uname -n` exist, that file is used instead.
# If a patchlist.`uname -n`.add exist, that file is used as in addition to
# the standard files.
#
# Patches are unpacked in /tmp, unless <patchdir>/`uname -r`/unpacked/<patch>
# exists.
#
# Kernel architecture specific patches can be listed in patchlist.`uname -m`
#
# For some patches it was necessary to unhide what is in /usr/share.
# /usr/share is unmounted before applying patches and remounted
# afterwards.  /usr/share must be unmounted, lofs mounted or in /etc/vfstab.
#
# By default, patches are only saved on servers, not on clients.
# When a previous version of a patch was installed and the files were
# saved, the patch is backed out.
#
# Usage: do-patch [-s] [-c] [-k] [-n] [-d] [-- patchadd/installpatch args]"
#
# -k - don't execute install actions (default action is reboot)
# -n - don't reboot
# -d - list patches that need installing [debug]
# -c - install patches from patchlist.common, don't save old files [client]
# -s - install patches from patchlist.server, save old files [server]
#
# Before installing a patch, the patch.pre script is executed.
# After installing a patch, the patch.post script is executed.
#
# A log of a patch installation is left in /var/sadm/patchlog.<patchid>
#
# Casper Dik (casper@fwi.uva.nl)
#

umask 022
# Path needs to include perl's directory for fastpatch
installdir=`cd \`dirname $0\`; pwd`
#installdir=/opt/install

PATH=/usr/sbin:/usr/bin:/etc:/sbin:/usr/local/bin:${installdir}:$PATH
export PATH

pdir=$installdir/patches/`uname -r`
if [ -d "$pdir.`uname -p`" ]
then
    pdir=$pdir.`uname -p`
fi

# If someone compressed old style patches w/ gunzip; gunzip should
# be in $PATH; this is for backward compatibility.
if [ -x /usr/local/gnu/bin/gunzip ]
then
    gunzip=/usr/local/gnu/bin/gunzip
else
    gunzip=gunzip
fi
root=/

while getopts dkncsR: c
do
    case "$c" in
    d)  noaction=true
	not=true;;
    k)  noaction=true
	reboot=false;;
    c)  if [ "${type-client}" != client ]
	then
	    echo "`basename $0`: options -c and -s are mutually exclusive" 1>&2
	    exit 1
	fi
	type=client;;
    s)  if [ "${type-server}" != server ]
	then
	    echo "`basename $0`: options -c and -s are mutually exclusive" 1>&2
	    exit 1
	fi
        type=server;;
    n)  reboot=false;;
    R)  root=$OPTARG;
	if [ "$root" != / ]
	then
	    flags="-R $root"; Rflag="$flags";
	fi;;
    \?) echo "Usage: `basename $0` [-csknd] [-- patchadd/installpatch args]" 1>&2
	exit 1;;
    esac
done

shift `expr $OPTIND - 1`


# Backward compatibility (if type is not set on the command line,
# check whether do-patch is called as do-patch.server or whether
# /opt is a local filesystem)
if [ -z "$type" ] && {
	[ "`grep '^SERVER$' $root/etc/INSTALL_CLASS 2>/dev/null`" = SERVER ] ||
	[ "`basename $0`" = do-patch.server ] || 
	[ "`awk '$3 == "/opt" { print $4 }' $root/etc/vfstab`" = ufs ]
   }
then
    type=server
else
    : ${type:=client}
fi

if [ $type = server ]
then
    plist=patchlist.server
else
    flags="-d $flags"
    plist=patchlist.common
fi

xplist=
if [ -f $root/etc/INSTALL_CLASS ]
then
    for class in `cat $root/etc/INSTALL_CLASS` `uname -p`
    do
	if [ -f $pdir/patchlist.$class ]
	then
	    xplist="$xplist patchlist.$class"
	fi
    done
    if [ -n "$xplist" ]
    then
	plist="$xplist"
    fi
fi

wildcard="$pdir/patchlist.[A-Z]*"
pkglist="`eval echo $wildcard`"
if [ "$wildcard" != "$pkglist" ]
then
    for f in $pkglist
    do
	pkg=`expr $f : "$pdir/patchlist.\(.*\)"`
	if pkginfo -R $root -q $pkg
	then
	    plist="$plist $f"
	fi
    done
fi

# Use bootname to get FQDN?
hname="`uname -n`"
if [ -f $pdir/patchlist.$hname ]
then
    plist=patchlist.$hname
fi
# Additional patches for a host.
if [ -f $pdir/patchlist.$hname.add ]
then
    plist="$plist patchlist.$hname.add"
fi

if [ -d "$pdir" ]
then
	cd $pdir
else
	# Well, Solaris has come a long way
	#echo "No patches?!? You must be kidding."
	echo "No patches"
	exit 0
fi
tmpf=/tmp/patches.$$

karch=`uname -m`

karchp=patchlist.$karch
if [ ! -f $karchp ] ; then karchp= ; fi
(
    cd $root/var/sadm/pkg;
    # In 5.5 the patches changed to "progressive"; each pkginfo file
    # now contains possible multiple entries on a PATCHLIST line.
    # The lines are split by replacing whitespace with newlines.
    egrep -h '^(SUNW_PATCHID|PATCHLIST)=.' */pkginfo |
	sed -e 's/.*=//' -e 's/[ 	][ 	]*/\
/g'| sort -u > $tmpf
)
patches=`awk '{print $1}' $plist $karchp | fgrep -h -v -f $tmpf`
rm -f $tmpf

trap 'usershare' 0 1 15

nousershare()
{
    [ -n "$not" -o $type = server -o -n "$beenthere" -o root != "/" ] && return
    loshare=`awk '/usr.share.*lofs/ { print $1 }' $root/etc/mnttab`
    umount /usr/share && remount=true
    beenthere=true
}

usershare()
{
    [ "$remount" != true ] && return
    if [ -z "$loshare" ]
    then
	mount /usr/share
    else
	mount -F lofs "$loshare" /usr/share
    fi
}

for p in $patches
do
    rm=""
    nousershare
    [ -z "$not" ] && echo "Installing patch #$p ...\c"
    if [ -d unpacked/$p ]
    then
	dir=$pdir/unpacked/$p
    elif [ -f $p.tar.Z -o -f alt/$p.tar.Z ]
    then
	if [ -f $p.tar.Z ]; then tarz=$p.tar.Z; else tarz=alt/$p.tar.Z; fi
	if [ -z "$not" ]
	then
	    echo " unpacking ...\c"
	    (zcat $tarz | (cd /tmp && tar xfB -))>/dev/null 2>&1
	fi
	if [ -n "$not" -o -d /tmp/$p ]
	then
	    dir=/tmp/$p
	    rm="$dir"
	else
	    echo "Can't unpack patch $p - failed." 1>&2
	    continue;
	fi
    else
	echo ""
	echo "Can't find patch $p - failed." 1>&2
	continue;
    fi
    if [ "$not" = true ]
    then
	# Generate listing on stdout, frills on stderr.
	echo "Would install patch #\\c" 1>&2
	echo "$p"
	continue;
    fi
    if [ -z "$noaction" -a -f "$p.pre" ]
    then
	echo "starting ...\c"
	. ./"$p.pre"
    fi
    if [ -d $root/var/sadm/patch ]
    then
	(
	cd $root/var/sadm/patch
	rev=`expr "$p" : '\(.*\)-'`
	if [ "${rev}*" != "`echo ${rev}*`" ]
	then
	    old=
	    rm -f $root/var/sadm/patchlog.${rev}*
	    for oldp in `ls -td ${rev}*`
	    do
		bo=$oldp/backoutpatch
		if [ ! -x $bo -a -x /usr/sbin/patchrm ]
		then
		    bo=/usr/sbin/patchrm
		fi
		if [ -x $bo ]
		then
		    echo "trying to back out $oldp ..."
		    if [ -f $oldp/save.tar.gz ]
		    then
			(
			 cd $oldp;
			 $gunzip < save.tar.gz |
			 tar xfBp -
			 rm -f save.tar.gz
			 )
		    fi
		    $bo "$oldp"
		    if [ $? != 4 -a $? != 0 ]
		    then
			echo "$bo failed - aborting" 2>&1
			exit 1
		    fi
		fi
	    done
	fi
	) || exit 1
	# Now remove patches also obsoleted by this patch or patches
	# w/o files saved.  Should also work for saved patches.
	[ -x $installdir/fastpatch ] &&
			$installdir/fastpatch $Rflag -c -o -D $dir
    fi
    echo " installing ...\c"

    # Default to installpatch, but if it isn't there, use patchadd.
    # We can't use patchadd always, as it may not cope with all
    # unbundled patches.
    ip=$dir/installpatch
    if [ ! -x $ip -a -x /usr/sbin/patchadd ]
    then
	ip=/usr/sbin/patchadd 
    fi
    if $ip $flags "$@" $dir > $root/var/sadm/patchlog.$p 2>&1
    then
	if [ -z "$noaction" ]
	then
	    if [ -f "$p.post" ]
	    then
		echo "finishing ...\c"
		. ./"$p.post"
	    else
		# default action
		: ${reboot:=true}
	    fi
	fi
	echo " done."
	# Log is kept in /var/sadm/patch/$p/log
	# This log is only interesting in case of a failure.
	rm -f $root/var/sadm/patchlog.$p
    else
	echo " failed."
	mailx -s "Application of $p failed" root < $root/var/sadm/patchlog.$p 
    fi
    if [ "$rm" != "" ]
    then
	rm -rf $rm
    fi
done

# Change file modes script.
if [ -z "$not" -a -x $installdir/fix-modes ]
then
    $installdir/fix-modes $Rflag -q
fi
if [ -z "$not" -a $type = client -a -x $installdir/fastpatch ]
then
    # $installdir/rm-patchdata -r > /dev/null 2>&1
    $installdir/fastpatch $Rflag -obrc > /dev/null 2>&1
fi
if [ "$reboot" = true ]
then
    echo Rebooting
    telinit 6
fi
