#!/bin/ksh # # @(#)analyze_patches.sh 1.9 99/06/06 SMI # # Copyright (c) 1995-1996 by Sun Microsystems, Inc. All Rights Reserved. # This script compares an installed system with a CD/Net image to # determine the effect on installed patches if the system is upgraded # using the CD/Net image. # # Usage: analyze_patches [-v] [-h] [-R root_dir] [-N net_dir] [-D database_dir] # # Return codes: # 2 Usage error # 3 Unable to create temporary file # 4 Unable to find pkginfo files # The Directory on the image containing Product and Misc # Normally set via sed from the Makefile OS_RELEASE_DIR=Solaris_10 # Defaults root_dir=/ db_dir= debug=0 terse=0 # A SPARC OS CD in a SPARC machine puts the product tree in cdrom0/s0, # whereas an Intel OS CD in an Intel machine puts the product tree # in cdrom0/s2. The heterogeneous cases are different too, but we # don't handle those by default here, because it really doesn't make # sense to. if [ "X`uname -p`" = "Xsparc" ] ; then net_dir=/cdrom/cdrom0/s0 else net_dir=/cdrom/cdrom0/s2 fi dprintf() { if [ "$debug" -ge "1" ]; then echo $1 >&2 fi } # Print a translated version of a dynamic string that has one variable # # This function was created in this way (using variable names in the # strings, resulting in a requirement that the names of those variables # be passed in addition to their values) both to increase readability # of the messages in the callers, and to avoid message retranslation. # # This routine is special-cased in the Makefile so the invocations will # make it into the message catalog # # Usage: i18n_string_1_arg string_to_translate varname varvalue # Returns: none # Function variable name prefix: is1_ i18n_string_1_arg() { typeset is1_varname=$2 typeset is1_varval=$(echo $3 |sed 's:/:\\/:'g) gettext "$1" |sed -e "s/\$$is1_varname/$is1_varval/g" } # Print a translated version of a dynamic string that has two variables # # This function was created in this way (using variable names in the # strings, resulting in a requirement that the names of those variables # be passed in addition to their values) both to increase readability # of the messages in the callers, and to avoid message retranslation. # # This routine is special-cased in the Makefile so the invocations will # make it into the message catalog # # Usage: i18n_string_2_arg string_to_translate var1name var1value # var2name var2value # Returns: none # Function variable name prefix: is2_ i18n_string_2_arg() { typeset is2_var1name=$2 typeset is2_var1val=$(echo $3 |sed 's:/:\\/:'g) typeset is2_var2name=$4 typeset is2_var2val=$(echo $5 |sed 's:/:\\/:'g) gettext "$1" |sed -e "s/\$$is2_var1name/$is2_var1val/g" \ -e "s/\$$is2_var2name/$is2_var2val/g" } # Print a translated version of a dynamic string that has three variables # # This function was created in this way (using variable names in the # strings, resulting in a requirement that the names of those variables # be passed in addition to their values) both to increase readability # of the messages in the callers, and to avoid message retranslation. # # This routine is special-cased in the Makefile so the invocations will # make it into the message catalog # # Usage: i18n_string_3_arg string_to_translate var1name var1value # var2name var2value var3name var3value # Returns: none # Function variable name prefix: is3_ i18n_string_3_arg() { typeset is3_var1name=$2 typeset is3_var1val=$(echo $3 |sed 's:/:\\/:'g) typeset is3_var2name=$4 typeset is3_var2val=$(echo $5 |sed 's:/:\\/:'g) typeset is3_var3name=$6 typeset is3_var3val=$(echo $7 |sed 's:/:\\/:'g) gettext "$1" |sed -e "s/\$$is3_var1name/$is3_var1val/g" \ -e "s/\$$is3_var2name/$is3_var2val/g" \ -e "s/\$$is3_var3name/$is3_var3val/g" } # Make a zero-length unique temp file # # Usage: get_tmp_file filename_prefix [initial_suffix] # Returns: $tmpfile - the name of the new temp file # Function variable name prefix: gtf_ get_tmp_file() { gtf_tmpprefix=$1 gtf_tmpnum=${2:-$$} while [ -r $gtf_tmpprefix$gtf_tmpnum ] ; do gtf_tmpnum=`echo $gtf_tmpnum + 1 |bc` done tmpfile=$gtf_tmpprefix$gtf_tmpnum cp /dev/null $tmpfile if [ $? != "0" ]; then i18n_string_1_arg 'Unable to create temp file $tmpfile. Exitting.\n' \ tmpfile "$tmpfile" exit 3 fi dprintf "Got temp file $tmpfile" } # Adds patches found in packages to a temp file. Patches are not sorted, and # any given patch may be added more than once # # Usage: patches_on_packages package_dir packages tmpfile # Returns: none # Function variable name prefix: pop_ patches_on_packages() { pop_packagedir=$1 pop_packages=$2 pop_tmpfile=$3 if [ "X$pop_packages" = "X" ] ; then /usr/bin/gettext "Cannot find pkginfo files.\n" # Read $pop_patchdir in below string as $pop_packagedir # Change to $pop_packagedir when messages open up again i18n_string_1_arg '(was looking in $pop_patchdir/*)\n' \ pop_patchdir "$pop_packagedir" exit 4 fi # Get the patches that have been applied to packages in the # provided list, if those packages exist. The list of packages # provided to this function may be a superset of the packages # that exist - no warning will be given for packages that # don't exist. for pop_pkg in $pop_packages ; do if [ -f "$pop_packagedir/$pop_pkg/pkginfo" ] ; then # Get applied patches pop_pkg_patches="`pkgparam -f $pop_packagedir/$pop_pkg/pkginfo PATCHLIST`" for pop_patch in "$pop_pkg_patches"; do echo $pop_patch >>$pop_tmpfile done fi done } # Returns a list of patches from the net image in netpatches # # Usage: patches_in_netimage # Returns: $netpatches - List of patches # $netaccumpatches - List of patches that accumulate others # Function variable name prefix: pin_ patches_in_netimage() { dprintf "Looking for patches in net image" get_tmp_file "/tmp/shwp_patch." ; pin_patchesfile=$tmpfile pin_dir="$net_dir/$OS_RELEASE_DIR/Product" packages=`cd "$pin_dir" 2>/dev/null && ls -d */pkginfo |sed -e 's:/pkginfo$::g' 2>/dev/null` # Look for patches in each package in the Product directory patches_on_packages "$pin_dir" "$packages" "$pin_patchesfile" # Look for patches in the Patches directory if [ -d "$net_dir/$OS_RELEASE_DIR/Patches" ] ; then ( cd $net_dir/$OS_RELEASE_DIR/Patches; ls ) >>$pin_patchesfile fi netpatches=`sort <$pin_patchesfile |uniq` rm $pin_patchesfile dprintf "Found the following patches in the net image:" dprintf "$netpatches" } # Returns a list of patches applied to the installed system # # Usage: patches_on_system # Returns: $syspatches - List of patches # $sysaccumpatches - List of patches that accumulate others # Function variable name prefix: pos_ patches_on_system() { dprintf "Looking for patches on the installed system" get_tmp_file "/tmp/shwp_patch." ; pos_patchesfile=$tmpfile # Look for patches on the installed system patches_on_packages "$root_dir/var/sadm/pkg" "$packages" "$pos_patchesfile" syspatches=`sort <$pos_patchesfile |uniq` rm $pos_patchesfile dprintf "Found the following patches on the installed system:" dprintf "$syspatches" } # Add obsoleted patches to a passed patch list. (If patch C obsoletes # patch B which obsoletes patch A, and if patch C is in the list, this # routine adds B and A) # # Algorithm: # # if patch in db # if patch rev ge db rev # foreach patch obsoleted # if NOT in either nochange or new # add to new # add patch to nochange # else # add to nochange list # if some in new list, repeat with new list # # Usage: augment_patches list_of_patches # Returns: $augmented - Augmented list of patches # $augmentors - List of patches that were expanded # Function variable name prefix: ap_ augment_patches() { get_tmp_file "/tmp/shwp_patch." ; ap_nochange_file=$tmpfile get_tmp_file "/tmp/shwp_patch." ; ap_new_file=$tmpfile ap_current="$1" ap_accumulators="" while [ -n "$ap_current" ] ; do for ap_patch in $ap_current ; do #dprintf "Looking at $ap_patch" ap_rev=`echo $ap_patch |sed 's/^[0-9]*-//g'` ap_num=`echo $ap_patch |sed 's/-[0-9]*$//g'` if [ -n "`ls $db_dir/$ap_num* 2>/dev/null`" ] ; then for ap_candidate in `ls $db_dir/$ap_num*`; do #dprintf "Looking at candidate $ap_candidate" ap_cand_rev=`echo $ap_candidate |sed 's/^.*[0-9]*-//g'` #dprintf "Comparing rev $ap_rev with cand rev $ap_cand_rev" if [ $ap_rev -ge $ap_cand_rev ] ; then ap_accumulators="$ap_accumulators $ap_num-$ap_cand_rev" for ap_newpatch in `cat $db_dir/$ap_num-$ap_cand_rev` ; do ap_newnum=`echo $ap_newpatch |sed 's/-[0-9]*$//g'` if [ -z "`grep $ap_newpatch $ap_nochange_file`" -a \ -z "`grep $ap_newpatch $ap_new_file`" ] ; then dprintf "Expanding $ap_candidate to $ap_newpatch" echo $ap_newpatch >>$ap_new_file ap_accumulators="$ap_accumulators,$ap_newpatch" fi done fi done fi #dprintf "Saving $ap_patch as nochange" echo $ap_patch >>$ap_nochange_file done ap_current="`cat $ap_new_file`" cp /dev/null $ap_new_file done augmented="`cat $ap_nochange_file |sort |uniq`" augmentors="$ap_accumulators" rm $ap_nochange_file rm $ap_new_file } # Set $srchlocale to the locale to be used according to I18N environment # variable precedence. See environ(5) for more details. # # Usage: set_srchlocale # Returns: $srchlocale - the locale for messages # Function variable name prefix: ss_ set_srchlocale() { [ -n "$LC_CTYPE" ] && srchlocale=$LC_CTYPE [ -n "$LANG" ] && srchlocale=$LANG [ -n "$LC_MESSAGES" ] && srchlocale=$LC_MESSAGES [ -n "$LC_ALL" ] && srchlocale=$LC_ALL } # # Main script body # TEXTDOMAIN=SUNW_PATCH_ANALYZE export TEXTDOMAIN # Find the locale directory, if we're not C. This code is needed # because this script can be run from a system that won't have the # catalogs - they'll be on the CD/Net image. # set_srchlocale # Point gettext towards a catalog if the derived locale is not C if [ "X$srchlocale" != "XC" -a "X$srchlocale" != "X" ]; then if [ ! -f "/usr/lib/locale/$srchlocale/LC_MESSAGES/$TEXTDOMAIN.mo" ] ; then relpath=`echo "$0" |sed 's:/[^/]*$::g'` if [ -f "$relpath/../Tools/Boot/usr/lib/locale/$srchlocale/LC_MESSAGES/$TEXTDOMAIN.mo" ] ; then TEXTDOMAINDIR="$relpath/../Tools/Boot/usr/lib/locale/" export TEXTDOMAINDIR #else # echo "Unable to locate message catalog for locale $srchlocale." >&2 fi fi fi # Localize the usage string USAGE=`/usr/bin/gettext 'Usage: $0 [-v] [-h] [-R root_dir] [-N net_dir] [-D database_dir]'` USAGE=`eval echo $USAGE` while getopts D:N:R:vht opt; do case $opt in R) root_dir=$OPTARG ;; N) net_dir=$OPTARG ;; D) db_dir=$OPTARG ;; v) debug=`echo $debug + 1|bc` ;; t) # PRIVATE FLAG - DON'T USE terse=1 ;; h) echo $USAGE exit ;; \?) echo $USAGE exit 2 ;; esac done # If database directory hasn't been specified, use # $net_dir/$OS_RELEASE_DIR/Misc/database if [ "X$db_dir" = "X" ] ; then db_dir=$net_dir/$OS_RELEASE_DIR/Misc/database fi # Verify chosen directories if [ ! -d $net_dir ] ; then i18n_string_1_arg '$net_dir is an invalid CD/Net image directory\n' \ net_dir "$net_dir" exit 2 fi if [ ! -d $root_dir ] ; then i18n_string_1_arg '$root_dir is an invalid root directory\n' \ root_dir "$root_dir" exit 2 fi if [ ! -d $db_dir ] ; then i18n_string_1_arg '$db_dir is an invalid database directory\n' \ db_dir "$db_dir" exit 2 fi dprintf "Root dir is $root_dir" dprintf "Net dir is $net_dir" dprintf "Database dir is $db_dir" dprintf "Debug level $debug" if [ $terse != 1 ] ; then /usr/bin/gettext "Making list of patches in CD/net image...\n" fi patches_in_netimage augment_patches "$netpatches" netpatches="$augmented" netaccumpatches="$augmentors" dprintf "Net image expanded patches:" dprintf "$netpatches" dprintf "Net image augmentors:" dprintf "$netaccumpatches" if [ $terse != 1 ] ; then /usr/bin/gettext "Making list of patches applied to installed system...\n" fi patches_on_system augment_patches "$syspatches" syspatches="$augmented" sysaccumpatches="$augmentors" dprintf "Installed system expanded patches:" dprintf "$syspatches" dprintf "Installed system augmentors:" dprintf "$sysaccumpatches" # We've got the two lists - the patches on the CD are in $netpatches, # and the patches from the installed CD are in $syspatches. Now we do # the merge. remupdown=`echo $syspatches xxx $sysaccumpatches xxx \ $netpatches xxx $netaccumpatches |nawk ' BEGIN { mode="syspatch"; netmax=0; sysmax=0; RS=" "; } /^xxx/ { if( mode == "syspatch" ) mode = "sysaccumpatch"; else if( mode == "sysaccumpatch" ) mode = "netpatch"; else if( mode == "netpatch" ) mode = "netaccumpatch"; } /^[0-9]/ { if( mode == "syspatch" ) { sysarr[++sysmax]=$1 ; } else if( mode == "sysaccumpatch" || mode == "netaccumpatch" ) { numparts=split( $1, parts, "," ) ; for( i = 2; i <= numparts; i++ ) { sub( /-.*$/, "", parts[i] ) ; accumcandarr[ parts[i] ]=parts[1]; } } else if( mode == "netpatch" ) { netarr[++netmax]=$1 ; } } END { upgradenum=0; downgradenum=0; removenum=0; accumnum=0; running=1; netidx=1; sysidx=1; while( running ) { if( netidx > netmax && sysidx > sysmax ) { running=0; } else if( netidx > netmax ) { for( ; sysidx <= sysmax ; sysidx++ ) { removearr[removenum++] = sysarr[sysidx]; }; } else if( sysidx > sysmax ) { running=0; } else { split( netarr[ netidx ], patch, "-" ); netnum=patch[1]; netrev=patch[2]; split( sysarr[ sysidx ], patch, "-" ); sysnum=patch[1]; sysrev=patch[2]; if( netnum == sysnum ) { if( netrev > sysrev ) { upgradepnumarr[upgradenum]=netnum; upgradefrevarr[upgradenum]=sysrev; upgradetrevarr[upgradenum]=netrev; upgradenum++; checkforaccumulation( sysnum, sysrev ); } else if( netrev < sysrev ) { downgradepnumarr[downgradenum]=netnum; downgradefrevarr[downgradenum]=sysrev; downgradetrevarr[downgradenum]=netrev; downgradenum++; } else { checkforaccumulation( sysnum, sysrev ); } sysidx++; netidx++; } else if( netnum < sysnum ) { netidx++; } else { removearr[removenum++] = sysarr[sysidx++]; } } } for( i = 0; i < removenum; i++ ) print removearr[i]; print "xxx"; for( i = 0; i < upgradenum; i++ ) printf( "%s-%s-%s\n", upgradepnumarr[i], upgradefrevarr[i], upgradetrevarr[i] ); print "xxx"; for( i = 0; i < downgradenum; i++ ) printf( "%s-%s-%s\n", downgradepnumarr[i], downgradefrevarr[i], downgradetrevarr[i] ); print "xxx"; for( i = 0; i < accumnum; i++ ) print accumarr[i]; } function checkforaccumulation( sysnum, sysrev ) { found=0 changed=1 accumulatornum=sysnum while( changed ) { changed=0 if( accumulatornum in accumcandarr ) { split( accumcandarr[accumulatornum], a, "-" ); accumulatornum=a[1]; accumulatorrev=a[2]; changed=1; found=1; } } if( found ) accumarr[accumnum++]=sysnum "-" sysrev "," accumulatornum "-" accumulatorrev; }'` dprintf "Results of analysis:" dprintf "$remupdown" loopmode=remove for patch in $remupdown ; do if [ "$patch" = "xxx" ] ; then case $loopmode in remove) loopmode=upgrade ;; upgrade) loopmode=downgrade ;; downgrade) loopmode=accumulation ;; esac else case $loopmode in remove) if [ $terse = 1 ] ; then echo "R $patch" else i18n_string_1_arg 'Patch $patch will be removed\n' \ patch "$patch" fi ;; upgrade) # Patch upgrades are supposed to be a good thing, so we # don't report them. #num=`echo $patch |sed 's/-.*$//g'` #from=`echo $patch |sed -e 's/^[^-]*-//g' -e 's/-[^-]*$//g'` #to=`echo $patch |sed 's/^.*-//g'` #if [ $terse = 1 ] ; then # echo "U $patch $from $to" #else # i18n_string_3_arg 'Patch $num will be upgraded from -$from to -$to\n' \ # num "$num" from "$from" to "$to" #fi ;; downgrade) num=`echo $patch |sed 's/-.*$//g'` from=`echo $patch |sed -e 's/^[^-]*-//g' -e 's/-[^-]*$//g'` to=`echo $patch |sed 's/^.*-//g'` if [ $terse = 1 ] ; then echo "D $num $from $to" else i18n_string_3_arg 'Patch $num will be downgraded from -$from to -$to\n' \ num "$num" from "$from" to "$to" fi ;; accumulation) accumulated=`echo $patch |sed 's/,.*$//g'` accumulator=`echo $patch |sed 's/^.*,//g'` if [ $terse = 1 ] ; then echo "A $accumulated $accumulator" else i18n_string_2_arg 'Patch $accumulated will be accumulated/obsoleted by patch $accumulator\n' \ accumulated "$accumulated" accumulator "$accumulator" fi ;; esac fi done