#!/bin/sh -h # @(#) backoutpatch 1.2.1.6 94/01/24 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 # umask 007 # Global Files TMPSOFT=/tmp/soft.$$ ADMINFILE=/tmp/admin.$$ force=no spoolonly="no" pkginstlist= pkglist= ROOTDIR="/" PATCHDB="/var/sadm/patch" PKGDB="/var/sadm/pkg" SOFTINFO="/var/sadm/softinfo" CONTENTS=/var/sadm/install/contents PKGDBARG="" # Description: # Print out the usage message to the screen # Parameters: # none print_usage() { cat<] Options: -f force the backout regardless of whether the patch was superceded -V print version number only -S Specify an alternate service (e.g. Solaris_2.3) for patch package processing references. EOF } # Description: # Remove the patch files from the /export/root/templates directory. # NOT SUPPORTED AT THIS TIME # Parameters: # $1 - patch database directory # $2 - patch number # $3 - 'only spool' flag [ "yes" or "no" ] # $4 - softinfo directory # $5 - product version # Globals Used: # TMPSOFT # #remove_spooled_files() { # cd $1/$2 # if [ "$3" = "yes" ]; then # if [ -f softinfo_sed ]; then # sed -f softinfo_sed $4/$5 >$TMPSOFT # mv $4/$5 $4/sav.$5 # cp $TMPSOFT $4/$5 # fi # fi # if [ -f spooled_dirs ]; then # cat spooled_dirs | xargs rm -fr # fi # if [ "$3" = "yes" ]; then # exit 0 # fi #} # Description: # Patch obsolecense message, printed if the patch being backed # out was superceded by other patches # Parameters: # $1 - patch ID print_obsolete_msg() { echo "This patch was obsoleted by patch $1." echo "Patches must be backed out in the order in" echo "which they were installed. Patch backout aborted." } # Description: # Parse the arguments and set all affected global variables # Parameters: # Arguments to backoutpatch # Globals Set: # force # patchnum # ROOTDIR # PATCHDB # PKGDB # SOFTINFO # PKGDBARG # CONTENTS parse_args() { while [ "$1" != "" ] do case $1 in -s) spoolonly="yes"; shift;; -f) force="yes"; shift;; -V) echo "Backoutpatch Version 3.7 1/24/94"; exit 0;; -S) shift get_os_version "/var/sadm/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/var/sadm/softinfo PKGDBARG="-R $ROOTDIR" else echo "The $1 service cannot be found on this system." fi fi shift;; -*) print_usage; exit 1;; *) break;; esac done patchnum=$1 echo "Backoutpatch Version 3.7 1/24/94" } # Description: # Make sure the effective UID is '0' # Parameters: # none validate_uid() { 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: # Get the product version _ of local Solaris installation # Parameters: # $1 - softinfo directory pathname # 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: # Build the admin script for pkgadd # Parameters: # none # Globals Used: # ADMINFILE build_admin() { cat >$ADMINFILE </dev/null` cpio -iumv -I archive.cpio else if [ -f archive.cpio.Z ]; then filelist=`zcat archive.cpio.Z | cpio -it 2>/dev/null` zcat archive.cpio.Z | cpio -iumv else filelist=`find . -print | sed "s/^.//"` find . -print | cpio -pdumv / fi fi if [ $? != 0 ]; then echo "Restore of old files failed." echo "See README file for instructions." rm -f /tmp/*.$$ exit 7 fi echo "Making the package database consistent with restored files:" for file in $filelist do if [ ! -f $file -o -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. srch="^$file\$" if [ -f ../.validation.errors ] && \ grep "$srch" ../.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="^$file[ =]" cfpath=`grep "$srch" $CONTENTS | sed 's/ .*//'` if [ "$cfpath" = "" -o ! -f "$cfpath" ] ; then continue fi installfdone=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. /usr/sbin/pkgchk $3 -lp $file | grep ' ' | \ while read instlist ; do for i in $instlist ; do /usr/bin/pkginfo $3 $i >/dev/null 2>&1 if [ $? = 0 ] ; then installf $i $file installf -f $i installfdone=yes break fi done if [ $installfdone = "yes" ] ; then break fi done done cd .. fi } # # Description: # Change directory to location of patch # Parameters: # $1 - patch database directory # $2 - patch number # Globals Set: # patchdir # patchid # patchrev activate_patch() { if [ ! -d $1/$2 -o ! -f $1/$2/.applied ]; then echo "Patch $2 has not been successfully applied to this system." exit 2 else patchdir=$1/$2 cd $patchdir patchid=`expr $2 : '\(.*\)-.*'` patchrev=`expr $2 : '[^-]*-\(.*\)'` fi } # Description: # Find the package instances for this patch # Parameters: # $1 - package database directory # $2 - patch number # Globals Set: # pkginstlist 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 check_if_obsolete() { Patchid= oldbase= oldrev= opatchid= obase= obsoletes= i= j= if [ -d $1 ] ; then cd $1 for i in * X ; do if [ $i = X -o "$i" = "*" ] ; then break elif [ ! -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 oldbase=`expr $Patchid : '\(.*\)-.*'` oldrev=`expr $Patchid : '.*-\(.*\)'` if [ $oldbase = $2 -a $3 -lt $oldrev ]; then print_obsolete_msg "$2" 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 ] ; then print_obsolete_msg "$2" exit 6 fi done done cd .. done 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 check_if_saved() { if [ ! -f $1/$2/.oldfilessaved -a \ ! -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 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 cleanup() { cd $1 # if [ -f $2/spooled_dirs ]; then # cat $2/spooled_dirs | xargs rm -fr # fi rm -fr $2/* rm -fr $2 rm -f /tmp/*.$$ if [ -f softinfo_sed ]; then sed -f softinfo_sed $3/$4 > $TMPSOFT mv $3/$4 $3/sav.$4 cp $TMPSOFT $3/$4 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 remove_patch_pkgs() { logfile=/tmp/pkgrmlog.$$ pkgrmerr= i= for i in $pkginstlist; do echo "\nRemoving patch package for $i:" /usr/sbin/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 -a $pkgrmerr != 2 -a \ $pkgrmerr != 10 -a $pkgrmerr != 20 ] ; then echo "\n/usr/sbin/pkgrm of $i package failed with return code $pkgrmerr." echo "See $1/$2/log for reason for failure." rm -fr /tmp/*.$$ exit 5 fi done } ######################################################### # # # Main routine # # # ######################################################### # - Parse the argument list and set globals accordingly # - Make sure the user is running as 'root' # - Get the product version _ of the local # Solaris installation # - activate the patch echo parse_args $* validate_uid get_os_version "$SOFTINFO" activate_patch "$PATCHDB" "$patchnum" # # Check to see if this patch was obsoleted by another patch # if [ "$force" = "no" ]; then check_if_obsolete "$PATCHDB" "$patchid" "$patchrev" fi # - Remove spooled files under /export/root/templates (NOT SUPPORTED) # remove_spooled_files "$PATCHDB" "$patchnum" "$spoolonly" "$SOFTINFO" "$prodver" # - 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 check_if_saved "$PATCHDB" "$patchnum" get_package_list "$PATCHDB" "$patchnum" get_pkg_instances "$PKGDB" "$patchnum" build_admin remove_patch_pkgs "$PATCHDB" "$patchnum" "$PKGDBARG" restore_orig_files "$PATCHDB" "$patchnum" "$PKGDBARG" "$CONTENTS" cleanup "$PATCHDB" "$patchnum" "$SOFTINFO" "$prodver" echo "Patch $patchnum has been backed out." exit 0