#!/bin/sh -h # @(#) backoutpatch 4.7 95/08/08 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 $SOFTINFO/INST_RELEASE file not found # # 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.$$ force=no spoolonly="no" pkginstlist= pkglist= ROOTDIR="/" PATCHDB="/var/sadm/patch" PKGDB="/var/sadm/pkg" SOFTINFO="/var/sadm/softinfo" CONTENTS="/var/sadm/install/contents" TMP_LIB_DIR="/tmp/TmpLibDir.$$" PKGDBARG="" PARAMS_FILE=/tmp/ParamsFile.$$ 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 execute_prebackout() { retcode= if [ -x $1/$2/prebackout ]; then echo "Executing prebackout script..." $1/$2/prebackout retcode=$? if [ "$retcode" != 0 ]; then echo "prebackout patch 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 execute_postbackout() { retcode= if [ -x $1/$2/postbackout ]; then echo "Executing postbackout script..." $1/$2/postbackout retcode=$? if [ "$retcode" != 0 ]; then echo "postbackout patch exited with return code $retcode." echo "Backoutpatch exiting." exit 10 fi fi } # # Description: # Make internal variables available to child processes # of installpatch. This is done by writing them to a # file and by exporting them. # Parameters: # none # Environment Variables Set: # none # make_params_available() { echo "ROOTDIR=$ROOTDIR" >> $PARAMS_FILE echo "PATCHDB=$PATCHDB" >> $PARAMS_FILE echo "PKGDB=$PKGDB" >> $PARAMS_FILE echo "SOFTINFO=$SOFTINFO" >> $PARAMS_FILE echo "PKGDBARG=$PKGDBARG" >> $PARAMS_FILE echo "patchdir=$patchdir" >> $PARAMS_FILE echo "patchnum=$patchnum" >> $PARAMS_FILE echo "patchrev=$patchrev" >> $PARAMS_FILE export patchdir patchnum patchrev export PARAMS_FILE ROOTDIR PATCHDB PKGDB SOFTINFO PKGDBARG } # # 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 Set: # none # myshowrev() { olddir=`pwd` cd $1 patches= patches=`sed -n 's/^SUNW_PATCHID=//p' ./*/pkginfo | sort -u` if [ "$patches" != "" ]; then for apatch in $patches; do outstr="Patch: $apatch Obsoletes: " patchvers=`grep -l "SUNW_PATCHID=$apatch" ./*/pkginfo | \ sed 's,^./\(.*\)/pkginfo$,\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 echo $outstr done else echo "No patches are installed" fi cd $olddir } # Description: # Print out the usage message to the screen # Parameters: # none print_usage() { cat<] [-S ] Options: -f force the backout regardless of whether the patch was superceded -V print version number only -R 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 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 # $2 - patch revision number # 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. 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() { service_specified="n" rootdir_specified="n" while [ "$1" != "" ] do case $1 in -s) spoolonly="yes"; shift;; -f) force="yes"; shift;; -V) echo "@(#) backoutpatch 4.7 95/08/08" exit 0 ;; -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 "/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" 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 rootdir 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/var/sadm/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 print_usage; exit 1 fi echo "@(#) backoutpatch 4.7 95/08/08" } # Description: # Make sure the effective UID is '0' # Parameters: # none validate_uid() { 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 _ of local Solaris installation # Parameters: # $1 - softinfo directory pathname # Globals Set: # prodver get_os_version() { if [ ! -f ${1}/INST_RELEASE ] then echo "$0 is unable to find the INST_RELEASE file. This file" echo "must be present for $0 to function correctly." exit 11 fi 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 $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 -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/*.$$ 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 -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. 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" = "" -o ! -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 [ $? = 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 # patchid # patchrev activate_patch() { if myshowrev $PKGDB | grep -s "^Patch:[ ]*$2" > /dev/null 2>&1; then patchdir=$1/$2 cd $patchdir patchid=`expr $2 : '\(.*\)-.*'` patchrev=`expr $2 : '[^-]*-\(.*\)'` else echo "Patch $2 has not been successfully 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 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" "$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 -a $2 != $oldbase ] ; then print_obsolete_msg "$2" "none" 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 -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 rm -fr ./$2/* rm -fr $2 } # 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:" 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 "\npkgrm of $i package failed with return code $pkgrmerr." echo "See $1/$2/log for reason for failure." 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 # move_libraries() { Rev=`echo $Instver | sed -e 's/[0-9]\.//'` if [ "$Rev" -ge "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 # remove_libraries() { LD_PRELOAD= export LD_PRELOAD rm -rf $TMP_LIB_DIR } ######################################################### # # # 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" echo $patchnum | grep $PatchIdFormat >/dev/null if [ "$?" != "0" ] then echo "Invalid patch id format: $patchnum" exit 8 fi activate_patch "$PATCHDB" "$patchnum" make_params_available # # Check to see if this patch was obsoleted by another patch # if [ "$force" = "no" ]; then check_if_obsolete "$PATCHDB" "$patchid" "$patchrev" fi execute_prebackout "$PATCHDB" "$patchnum" # - 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 trap 'remove_libraries' HUP INT QUIT TERM move_libraries remove_patch_pkgs "$PATCHDB" "$patchnum" "$PKGDBARG" restore_orig_files "$PATCHDB" "$patchnum" "$PKGDBARG" "$CONTENTS" remove_libraries execute_postbackout "$PATCHDB" "$patchnum" cleanup "$PATCHDB" "$patchnum" "$SOFTINFO" "$prodver" echo "Patch $patchnum has been backed out." exit 0