#!/bin/sh
#
# $Id: do-patch,v 1.25 1996/06/04 08:11:40 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] [-- 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 rm-patchdata
PATH=/usr/sbin:/usr/bin:/etc:/sbin:/usr/local/bin
export PATH
installdir=`cd \`dirname $0\`; pwd`
#installdir=/opt/install
pdir=$installdir/patches/`uname -r`
if [ -d "$pdir.`uname -p`" ]
then
    pdir=$pdir.`uname -p`
fi

while getopts dkncs 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;;
    \?) echo "Usage: `basename $0` [-csknd] [-- 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$' /etc/INSTALL_CLASS 2>/dev/null`" = SERVER ] ||
	[ "`basename $0`" = do-patch.server ] || 
	[ "`awk '$3 == "/opt" { print $4 }' /etc/vfstab`" = ufs ]
   }
then
    type=server
else
    : ${type:=client}
fi

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

xplist=
if [ -f /etc/INSTALL_CLASS ]
then
    for class in `cat /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 -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
	echo "No patches?!? You must be kidding."
	exit 0
fi
tmpf=/tmp/patches.$$

karch=`uname -m`

karchp=patchlist.$karch
if [ ! -f $karchp ] ; then karchp= ; fi
(
    cd /var/sadm/pkg;
    # In 5.5 the patches changed to "progressive"; each pkginfo filke
    # 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" ] && return
    loshare=`awk '/usr.share.*lofs/ { print $1 }' /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 /var/sadm/patch ]
    then
	(
	cd /var/sadm/patch
	rev=`expr "$p" : '\(.*\)-'`
	if [ "${rev}*" != "`echo ${rev}*`" ]
	then
	    old=
	    rm -f /var/sadm/patchlog.${rev}*
	    for oldp in `ls -td ${rev}*`
	    do
		if [ -x "$oldp"/backoutpatch ]
		then
		    echo "trying to back out $oldp ..."
		    if [ -f $oldp/save.tar.gz ]
		    then
			(
			 cd $oldp;
			 /usr/local/gnu/bin/gunzip < save.tar.gz |
			 tar xfBp -
			 rm -f save.tar.gz
			 )
		    fi
		    "$oldp"/backoutpatch "$oldp"
		    if [ $? != 4 -a $? != 0 ]
		    then
			echo "Backoutpatch 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 -o -D $dir
    fi
    echo " installing ...\c"
    if $dir/installpatch $flags "$@" $dir > /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 /var/sadm/patchlog.$p
    else
	echo " failed."
	mailx -s "Application of $p failed" root < /var/sadm/patchlog.$p 
    fi
    if [ "$rm" != "" ]
    then
	rm -rf $rm
    fi
done

# Fwi specific change file modes and update script.
if [ -z "$not" -a -x $installdir/fix-modes ]
then
    $installdir/fix-modes 
fi
if [ -z "$not" -a $type = client -a -x $installdir/rm-patchdata ]
then
    $installdir/rm-patchdata -r > /dev/null 2>&1
    # $installdir/fastpatch -obr > /dev/null 2>&1
fi
if [ "$reboot" = true ]
then
    echo Rebooting
    telinit 6
fi
