Copyright 2004 Sun Microsystems, Inc. ALL RIGHTS RESERVED Use of this software is authorized pursuant to the terms of the license found at http://developers.sun.com/berkeley_license.html #!/bin/sh # This script is intended to run from /etc/rc2.d/S73XXX to automatically patch # the system it is running on. It relies upon the patch sets being in a # particular location on a particular server # # This script must be installed after the network is configured and NFS is up # and before any application script have begun to start things up. # /etc/rc2.d/S73patch seems to be a good place (follows S73nfs.client) # # Written by Mike Myers 10/2001 # Updated to version 2.0 6/2002 # Version 2.0 # Added support for patching of the patch server # Added support vmsa patching # Restructured a bit to clean it up # Lowered the amount of normal output (moved it to a debug mode) # Removed "press return" at the end # Removed email off of "previous" patch log # Put a fix in for a Veritas bug (Sun alert 42797) # Removed most of the date/time outputs # Cleaned up how we look for packages so no errors are printed # Changed all "clusters" to use "install_cluster" # Updated to version 3.0 12/2002 # Version 3.0 # Added fix for 2.6 kobj_map bug (hangs after patching) # Trap of stderr to log # Integration with replacement of sun install_cluster script # This script will only install patches that aren't installed # Crunch addon/required just down to one -- Addon # Change debug mode to use patches in /patches/new # Added command post alert support # Enhanced debugging mode (shorter waits, adjusted admin email, etc.) # Checked for at least a minimum of free space (50mb) # Added support for using script stand-alone # It can be run from the command line and at boot time # Removed workaround for Veritas libraries (fixed in the patch) # Add the track.patch file at this level (removed from install_cluster) # Updated to version 3.1 06/2003 # Version 3.1 # Changed Veritas to use install_cluster scripts # Removed commented out code blocks for: # Sun ODS # kobj bug fix for Solaris 2.6 (installed on all systems now) # _Required directories (no longer used) # Updated our groups email address to one that works... # Updated to version 3.2 11/2003 # Version 3.2 # Changed PATH_HOST from static global to a conditional statement that # selects the patch host based on hostname. This accommodates # the Americas servers, which need their own local patch # repository. # Let's catch some traps trap '' 1 2 3 4 5 6 7 8 10 12 13 14 15 # Block logins echo "This system is being patched. Please try back later." > /etc/nologin echo "If this condition persists for more than 3 hours please" >> /etc/nologin echo "contact the help desk and ask to speak to Unix support" >> /etc/nologin # Setup our globals OS=`uname -r` LOG='/var/tmp/autopatch.log' HOSTNAME=`hostname` MOUNT_DIR="/mnt" echo "Hostname is ${HOSTNAME}" # Here you must select a patch server based on the name of the host # (eg. a "close" patch server) case ${HOSTNAME} in a*) PATCH_SERVER="PATCHSERVER" ;; *) # uh, oh. We're not ready to patch this server. echo "ERROR: no patch server configured for this host, ${HOSTNAME}." exit 1 ;; esac echo "PATCHHOST is ${PATCH_SERVER}" # Note: ADMIN is over-ridden in DEBUG mode ADMIN="YOUR.GROUP.EMAIL@YOUR.COMPANY.COM" # This code fragment has to be here to turn debugging on early in the process if [ -f "/debug_patches" ]; then DEBUG=1 fi # These are used to alert operations to errors and to successful completion if [ "$DEBUG" -eq 1 ]; then # In debug mode we probably just want to know these WOULD have triggered ERROR_ALERT="echo YOURALERTSOFTWARE" CLEAN_ALERT="echo YOURALERTSOFTWARE" ADMIN="YOUR.PERSONAL.EMAIL@FOR.TESTING.COM" VERBOSE="-v" else ERROR_ALERT="YOURALERTSOFTWARE" CLEAN_ALERT="YOURALERTSOFTWARE" VERBOSE="" fi # For debugging we don't want to wait a long time... if [ "$DEBUG" -eq 1 ]; then WAIT_TIME=2 else WAIT_TIME=30 fi # Check for special conditions # Are we the patch server? if [ "$HOSTNAME" = "$PATCH_SERVER" ]; then if [ "$DEBUG" -eq 1 ]; then echo "We are patching ourselves..." echo "" fi MOUNT_DIR="/patches" PATCHING_SELF=1 fi # Are we being run from the console at boot time? if [ -z "$TERM" ]; then # We're on the console during a boot mytty=/dev/console interactive=0 else # We're running interactively mytty=`tty` interactive=1 fi # Clean up any left-over logs if [ -f $LOG ]; then mv $LOG ${LOG}.prev fi # Check free space against an arbitrary number -- 50mb freespace=`df -k /usr | grep -v Filesystem | awk '{print $4}'` if [ "$freespace" -lt 50000 ]; then if [ "$interactive" -eq 0 ]; then $ERROR_ALERT %%Insufficient free space for patching%% fi echo "*****************************" echo "** ERROR: There is not enough free space to install the patch" echo "*****************************" echo "Press return to proceed with reboot" read yn < $mytty clean_exit fi ##################### ## Setup functions ## ##################### ################################## ## Gracefully clean up and exit ## ################################## clean_exit () { # Ok, the log should be closed here echo "Mailing off the log for review. Please wait a moment." # Our stupid mail gateway filters anything with "patch" in the subject # Thus the goofy subject... cat $LOG | mailx -s "Binary update log from $HOSTNAME" $ADMIN # Give the mail a few seconds to get through sleep $WAIT_TIME if [ "$interactive" -eq 0 ]; then # Move myself out of the way NEWNAME=`echo $0 | sed -e 's/\/S/\/s/'` mv $0 $NEWNAME if [ $? -ne 0 ]; then if [ "$interactive" -eq 0 ]; then $ERROR_ALERT %%Autopatch script was unable to deactivate%% fi echo "*****************************" echo "** This script was unable to deactivate itself" echo "** Please call the Unix on-call. On the next boot" echo "** do NOT select to patch the system." echo "** You may reboot while you're waiting." echo "*****************************" echo "Press return to proceed with reboot" read yn < $mytty fi fi # No reason it shouldn't exist but... if [ -f /etc/nologin ]; then rm /etc/nologin fi if [ $? -ne 0 ]; then if [ "$interactive" -eq 0 ]; then $ERROR_ALERT %%Autopatch script was unable to remove /etc/nologin%% fi echo "*****************************" echo "** This script was unable to remove /etc/nologin" echo "** Please call the Unix on-call." echo "** You can go ahead and reboot while you are waiting for" echo "** them to respond, but no one will be able to login." echo "*****************************" echo "Press return to proceed with reboot" read yn < $mytty fi echo "Done sending mail" if [ "$interactive" -eq 1 ]; then echo "Would you like to reboot?" read yn < $mytty case "$yn" in n*|N*) echo "Ok, but don't forget to reboot soon." echo "Would you like to umount the patches?" read yn < $mytty case "$yn" in y*|Y*) umount /mnt ;; esac exec /bin/true ;; esac fi echo "Rebooting..." # A reconfigure boot for good measure sync;sync;sync;reboot -q echo "Reboot initiated...sit back and relax..." exit 0 } ##################### # This will locate the "latest" patches based # on a date string of "MMDDYYYY" in the path ##################### # This is silly -- we ought to just change it to follow a # symlink that gets updated when we change clusters. Next revision find_latest () { year=`ls -d ${PATCH_BASE}_* | cut -d_ -f2 | grep ........ | cut -c 5-8 | sort -n | tail -1` month=`ls -d ${PATCH_BASE}_*$year | cut -d_ -f2 | grep ........ | cut -c 1-2 | sort -n | tail -1` day=`ls -d ${PATCH_BASE}_$month*$year | cut -d_ -f2 | grep ........ | cut -c 3-4 | sort -n | tail -1` RECENT_PATCHES="${PATCH_BASE}_${month}${day}${year}" } ############################## ## Handle terminal failures ## ############################## failure () { if [ "$interactive" -eq 0 ]; then $ERROR_ALERT %%$ERROR_MSG1%% fi echo "**************************************" echo "** ERROR: $ERROR_MSG1" echo "** $ERROR_MSG2" echo "** Contact the Unix on-call. The patching will be aborted." echo "**************************************" echo "Press return to continue with system startup" read yn < $mytty if [ "$yn" != "override" ]; then clean_exit else echo "Admin override -- are you sure?" read yn < $mytty if [ "$yn" != "yes" ]; then echo " I didn't think so..." exit 0 fi echo "Hope you're telling the truth..." # This will fall through to complete the reboot... fi } ############################################### ## Check the return code for acceptable ones ## ############################################### check_rc () { errcode=$? if [ "$DEBUG" -eq 1 ]; then echo "Checking return code $errcode" fi # error code 2 = already applied # error code 8 = package not installed # error code 5 = pkgadd failure. Apparently (according to Sun) # this means the patch is the type that replaces a whole package and # it has already been applied (confusing) if [ "$errcode" -eq 0 -o "$errcode" -eq 2 -o "$errcode" -eq 8 -o "$errcode" -eq 5 ]; then return else echo " ***********************************************" echo " ** Error code $errcode is non-standard. It **" echo " ** is not fatal, but should be investigated **" echo " ***********************************************" fi } ############ # Figure out the OS patches ############ find_os_patches () { PATCH_BASE="${MOUNT_DIR}/Sun${OS}" find_latest $PATCH_BASE OS_PATCHES=$RECENT_PATCHES if [ "$DEBUG" -eq 1 ]; then echo "Using patches in $OS_PATCHES" ls -l $OS_PATCHES fi if [ ! -r ${RECENT_PATCHES}/track.patch ]; then ERROR_MSG1="Cannot find file ${RECENT_PATCHES}/track.patch" ERROR_MSG2="" failure fi LOCAL_PATCH=`cat ${RECENT_PATCHES}/track.patch` if [ "$DEBUG" -eq 1 ]; then echo "Using LOCAL_PATCH token of $LOCAL_PATCH" fi OS_PATCH_RECOMMENDED="${OS_PATCHES}/*_Recommended" if [ ! -x $OS_PATCH_RECOMMENDED/install_cluster ]; then ERROR_MSG1="Cannot find install_cluster in" ERROR_MSG2=" $OS_PATCH_RECOMMENDED" failure fi if [ "$DEBUG" -eq 1 ]; then echo "Using OS_PATCH_RECOMMENDED token of $OS_PATCH_RECOMMENDED" fi OS_PATCH_ADDON="${OS_PATCHES}/*_Addon" if [ ! -x $OS_PATCH_ADDON/install_cluster ]; then ERROR_MSG1="Cannot find install_cluster in" ERROR_MSG2="$OS_PATCH_ADDON" failure fi if [ "$DEBUG" -eq 1 ]; then echo "Using OS_PATCH_ADDON token of $OS_PATCH_ADDON" fi } ############## # Figure out the Volume Manager patches ############## find_vxvm_patches () { VXVMPKG=`pkginfo | grep vxvm | awk '{print $2}'` PKGCNT=`echo $VXVMPKG | fmt -1 | grep vxvm | wc -l` if [ "$PKGCNT" -gt 1 ]; then ERROR_MSG1="there appears to be more than one Volume manager installed" ERROR_MSG2="" failure fi if [ "$PKGCNT" -eq 0 ]; then HAVE_VXVM=0 if [ "$DEBUG" -eq 1 ]; then echo "No volume manager installed" fi return # No more to do here... else HAVE_VXVM=1 fi VXVMVER=`pkginfo -l $VXVMPKG | grep VERSION | awk '{print $2}' | awk -F, '{print $1}'` PATCH_BASE="${MOUNT_DIR}/VxVM" find_latest $PATCH_BASE VXVM_PATCHES="${RECENT_PATCHES}/$VXVMVER/$OS" if [ ! -d "$VXVM_PATCHES" ]; then ERROR_MSG1="There are no patches for Volume Manager $VXVMVER on OS $OS" ERROR_MSG2="(missing directory $VXVM_PATCHES)" failure fi if [ "$DEBUG" -eq 1 ]; then echo "Using $VXVM_PATCHES for Volume Manager" ls -l $VXVM_PATCHES fi } ############## # Figure out the VMSA patches ############## find_vmsa_patches () { VMSAPKG=`pkginfo |grep VRTSvmsa` if [ -z "$VMSAPKG" ]; then HAVE_VMSA=0 if [ "$DEBUG" -eq 1 ]; then echo "No VMSA installed" fi return # No need to keep figuring else HAVE_VMSA=1 fi VMSAVER=`pkginfo -l VRTSvmsa | grep VERSION | awk '{print $2}' | awk -F, '{print $1}'` if [ "$DEBUG" -eq 1 ]; then echo "Found VMSA Version $VMSAVER" fi PATCH_BASE="${MOUNT_DIR}/VMSA" find_latest $PATCH_BASE VMSA_PATCHES="${RECENT_PATCHES}/$VMSAVER" if [ ! -d "$VMSA_PATCHES" ]; then ERROR_MSG1="There are no patches for VMSA $VXVMVER" ERROR_MSG2="(missing directory $VMSA_PATCHES)" failure fi if [ "$DEBUG" -eq 1 ]; then echo "Using $VMSA_PATCHES for VMSA" ls -l $VMSA_PATCHES fi } ################ # Figure out the Veritas File system version ################ find_vxfs_patches () { VXFSPKG=`pkginfo | grep VRTSvxfs` if [ -z "$VXFSPKG" ]; then HAVE_VXFS=0 if [ "$DEBUG" -eq 1 ]; then echo "Veritas File system not installed, no patches" fi return # No more to do here else HAVE_VXFS=1 fi VXFSVER=`pkginfo -l VRTSvxfs | grep VERSION | awk '{print $2}'| awk -F, '{print $1}'` PATCH_BASE="${MOUNT_DIR}/VxFS" find_latest $PATCH_BASE VXFS_PATCHES="${RECENT_PATCHES}/$VXFSVER/$OS" if [ ! -d "$VXFS_PATCHES" ]; then ERROR_MSG1="There are no patches for Veritas File System $VXVMVER on OS $OS" ERROR_MSG2="(missing directory $VXFS_PATCHES)" failure fi if [ "$DEBUG" -eq 1 ]; then echo "Using $VXFS_PATCHES for Veritas File System" ls -l $VXFS_PATCHES fi } #################### # OK, we have everything we need to get started...no? #################### ## Old patch order: OS_RECOMMENDED, OS_REQUIRED, OS_ADDON, VXVM, VXFS ## New patch order: OS_RECOMMENDED, OS_ADDON, VXVM, VXFS apply_os_patches () { ## # OS_RECOMMENDED ## echo "*****" echo "** Patching OS with RECOMMENDED patches" echo "*****" cd $OS_PATCH_RECOMMENDED $OS_PATCH_RECOMMENDED/install_cluster $VERBOSE # OS_REQUIRED #echo "*****" #echo "** Patching OS with LOCAL REQUIRED patches" #echo "*****" #cd $OS_PATCH_REQUIRED #$OS_PATCH_REQUIRED/install_cluster $VERBOSE ## # OS_ADDON ## echo "*****" echo "** Patching OS with LOCAL ADDON patches" echo "*****" cd $OS_PATCH_ADDON $OS_PATCH_ADDON/install_cluster $VERBOSE } ## # VxVM ## apply_vxvm_patches () { if [ "$HAVE_VXVM" -eq 1 ]; then echo "*****" echo "** Patching OS with VERTIAS VOLUME MANAGER patches" echo "*****" cd $VXVM_PATCHES $VXVM_PATCHES/install_cluster $VERBOSE fi } ## # VMSA ## apply_vmsa_patches () { if [ "$HAVE_VMSA" -eq 1 ]; then echo "*****" echo "** Patching OS with VMSA patches" echo "*****" cd $VMSA_PATCHES $VMSA_PATCHES/install_cluster fi } ## # VxFS ## apply_vxfs_patches () { if [ "$HAVE_VXFS" -eq 1 ]; then echo "*****" echo "** Patching OS with VERTIAS FILE SYSTEM patches" echo "*****" cd $VXFS_PATCHES $VXFS_PATCHES/install_cluster fi } ########################## ## Begin main sequence ## ########################## ( # This wrappers it all to send output to a "tee" echo "*****************************************" echo "** Automatic system patching beginning **" echo "*****************************************" date if [ "$interactive" -eq 0 ]; then $CLEAN_ALERT %%Patching needs approval%% fi # This is here for systems with their primary network on ge0 # These systems seem to need more time to get up on the net # network before patching will work correctly. ifconfig ge0 > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "Waiting for network to stabilize..." # THIS NETMASK ISN'T RIGHT FOR EVERYONE... ifconfig ge0 netmask 255.255.255.0 sleep 120 fi while :; do echo "Should the patching proceed (y/n)? \c" read yn < $mytty case "$yn" in y|Y|yes|YES|Yes|YEs) break ;; n|N|no|NO|No) echo "Are you sure you want to cancel (y/n)? \c" read yn < $mytty case "$yn" in y|Y|yes|YES|Yes|YEs) ERROR_MSG1="Patching aborting" ERROR_MSG2="Rebooting system to restart normal startup" clean_exit ;; *) echo "Ok, then let's try again..." ;; esac ;; *) echo "Invalid response!" esac done echo "****************************************" echo "** Starting patching **" echo "****************************************" if [ "$yn" = "d" ]; then DEBUG=1 fi if [ "$DEBUG" -eq 1 ]; then echo "DEBUG is on ($DEBUG)" fi # Let's mount the patches # Unless we are the patch server if [ "$PATCHING_SELF" != 1 ]; then if [ "$DEBUG" -eq 1 ]; then PATCH_DIR="/patches/new" else PATCH_DIR="/patches" fi mount $PATCH_SERVER:$PATCH_DIR $MOUNT_DIR if [ $? -ne 0 ]; then ERROR_MSG1="Cannot mount patches" ERROR_MSG2="($PATCH_SERVER:/patches)" failure fi fi find_os_patches find_vxvm_patches find_vmsa_patches find_vxfs_patches apply_os_patches apply_vxvm_patches apply_vmsa_patches apply_vxfs_patches echo $LOCAL_PATCH >> /etc/track.patch chmod 644 /etc/track.patch echo "************************************************" echo "** Patching is complete. System will now reboot" echo "************************************************" if [ "$interactive" -eq 0 ]; then $CLEAN_ALERT %%Patching complete%% fi date ) 2>&1 | tee -a $LOG clean_exit Here is the modified install_cluster script (this was originally based on the Sun install_cluster script, though most of it has been re-written - I don't want Sun grumpy about the use of this code. A diff of the two shows just a few lines that remain the same. If this is a problem, you can trim this bit out as it's not strictly required for this to function properly - just faster): #! /bin/sh # Set a minimal path to work PATH=/sbin:/usr/sbin:/bin:/usr/bin export PATH umask 077 # Grab our name for this cluster (should be a date stamp per standards) LOGFILE=/var/sadm/install_data/patching.log touch $LOGFILE chmod 644 $LOGFILE # This is a temp file for holding a list of patches on the system PLIST=/tmp/uniq.patches.$$ # Got root? id | egrep -s 'uid=0' if [ $? -eq 1 ]; then echo "You must be root to execute this script." exit 1 fi # Do they want us to be chatty? if [ "$1" = "-v" ]; then VERBOSE=1 else VERBOSE=0 fi # We use a fixed value here of the largest patch size we had in 12/2002 bytes_avail=`df -b /var/sadm/patch | tail -1 | awk '{print $2}'` if [ ${bytes_avail} -lt 224615 ]; then echo "Insufficient space in /var/sadm/patch to save old files." echo "Space required in bytes: 224615" echo "Space available in bytes: ${bytes_avail}" echo "Exiting..." exit 1 fi # This generates a list of the currently installed patches # (only the most recent version is reported) # Why doesn't Sun make this an option in showrev?! rm -f $PLIST prevpatch="" prerev="" echo "Determining the patches on the system..." for i in `showrev -p | cut -d" " -f2 | sort -n`; do patch=`echo $i | cut -d- -f1` rev=`echo $i | cut -d- -f2` if [ "$patch" -eq "$prepatch" ]; then if [ "$rev" -gt "$prerev" ]; then prerev=$rev fi else if [ -n "$prepatch" ]; then echo "$prepatch-$prerev" >> $PLIST fi prepatch=$patch prerev=$rev fi done echo "$prepatch-$prerev" >> $PLIST echo "*** Installation of patches on `date` ***" >> ${LOGFILE} echo "Starting patching..." if [ -f patch_order ]; then patchlist=`cat patch_order` echo "Using patch_order file for patch installation sequence" >> ${LOGFILE} else patchlist=1* echo "No patch_order file, creating a list of patches" >> ${LOGFILE} fi for patch in ${patchlist} ; do if [ ! -d $patch ]; then echo "NOTE: Patch $patch is in the patch_order file but" >> $LOGFILE echo "the directory does not exist. Skipping." >> $LOGFILE echo "NOTE: Patch $patch is in the patch_order file but" echo "the directory does not exist. Skipping." continue fi patch_id=`echo $patch | cut -d- -f1` has_patch=`egrep ${patch_id} $PLIST` if [ -z "$has_patch" ]; then echo "Installing new patch $patch" echo "Installing new patch $patch" >> $LOGFILE else # We have a revision of this patch -- higher, lower, same? patch_rev=`echo $patch | cut -d- -f2` has_rev=`echo $has_patch | cut -d- -f2` if [ "$patch_rev" -gt "$has_rev" ]; then echo "Upgrading patch $patch_id from $has_rev to $patch_rev" echo "Upgrading patch $patch_id from $has_rev to $patch_rev" >> $LOGFILE elif [ "$patch_rev" -eq "$has_rev" ]; then if [ "$VERBOSE" -eq 1 ]; then echo "Patch $patch_id installed at rev $has_rev already - skipping" echo "Patch $patch_id installed at rev $has_rev already - skipping" >> $LOGFILE fi continue elif [ "$patch_rev" -lt "$has_rev" ]; then echo "NOTE: Patch $patch_id with rev $patch_rev is in bundle, but higher rev $has_rev is already installed!" echo "Skipping." echo "NOTE: Patch $patch_id with rev $patch_rev is in bundle, but higher rev $has_rev is already installed!" >> $LOGFILE echo "Skipping." >> $LOGFILE continue fi fi if [ `uname -r` = "5.5.1" -o `uname -r` = "5.4" ]; then ( cd ${patch}; ./installpatch `pwd` ) >> ${LOGFILE} 2>&1 else ( /usr/sbin/patchadd ${patch} ) >> ${LOGFILE} 2>&1 fi result=$? # Seeing as we filtered out already installed, pretty much everything should # install without error...I'm sure I'll change my mind later. if [ ${result} -eq 0 ]; then continue; elif [ ${result} -eq 8 ]; then # 8 = software not installed echo " Software not installed" continue; else echo " Installation of ${patch} failed. Return code ${result}." failed_list="${failed_list} ${patch}" fi echo "##########################################################" >> $LOGFILE done exit 0