#!/usr/bin/ksh # # (c)Copyright 1996 Hewlett-Packard Co., All Rights Reserved. # $Header: /ignite/cold_fusion/src/tools/make_sys_image 10.127 1998/08/21 21:42:27 jesse Exp $ # @(#) make_sys_image $Revision: 10.127 $ # ## This tool will create system images suitable for use with the Ignite-UX ## product for installing systems. It can also be used to create system ## images for archival purposes. ## The tool can be called as a post config script to the Ignite-UX process ## or run on an already installed system. ## ## WARNING: With CLEAN_LEVEL set to 2 this command is DESTRUCTIVE! ## Files are removed or modified that are necessary for production use. ## An effort has been made to put the system back to the preimaging state when ## the command has completed, but caution should be used. ## # # flow # # # 1) archive_setup # how_to_compress # how_to_archive # pax_test # 2) clean # swremove # rm core files # tlremove # make_generic_sysfile # copy_newconfig # 3) archive # archive_size_check # message # eval of archive command # find_files ############################# LEVEL 2 CONTROL ################################# # The LEVEL2_RESET variable lists objects that will be reset to their # "newconfig" states in the archive. Since this requires that they # be temporarily changed on the system being archived, the current # versions will be saved before the archive is made and restored after # the archive is finished. **This implies that the system should be in # a "quiet" state while it's being archived** # # Posix shell pattern matching notation can be used in the list. Comments # (beginning with #) are ok. If the object is a directory, then everything # possible under that directory will be "newconfig"'ed. LEVEL2_RESET=' /.profile /etc/rc.config.d/hpetherconf /etc/rc.config.d/hpfcgsc_lanconf /etc/rc.config.d/hpfcmsconf /etc/rc.config.d/netconf /etc/rc.config.d/namesvrs /etc/rc.config.d/mailservs /etc/rc.config.d/xfs /etc/inittab /etc/hosts /etc/passwd /etc/group /etc/netgroup /etc/ntp.conf /etc/vue/config /var/adm/sw/security ' # Also in level 2: /stand/system is always archived minus any # hardware-specific drivers. # The LEVEL2_NO_ARCHIVE variable lists things that should NOT go into # the archive. They are removed from the list of files sent to the pax # command. If the object listed is a directory, then the entire subtree # (including that directory) will be ignored. Use /* to just # ignore the contents of a directory (remember * does NOT include "dot # files"). Posix shell pattern matching notation can be used. Comments # (beginning with #) are ok. # Note: 1) /stand/system cannot be ignored. It's handled specially. # 2) /stand/dlkm and /stand/system.d need to be preserved to enable # dynamic kernal modules to survive being archived and rebuilt with # mk_kernel on install. LEVEL2_NO_ARCHIVE=' /var/opt/ignite/local /.sh* /.rhosts /configure3 /d_cfg_mnt_sb61 /dev/* /.sw/* /etc/lvmtab /etc/lvmconf /etc/auto_parms.log /etc/auto_master /etc/auto.direct /etc/auto.home /etc/auto.update /etc/dhcp* /etc/exports /etc/fstab /etc/xtab /etc/mnttab /etc/ioconfig /etc/ioctl.syscon /etc/shutdownlog /etc/ptydaemonlog /etc/rc.log /etc/resolv.conf /etc/vtdaemonlog /etc/utmp /etc/utmpx /stand/!(system|kernrel|system.d|dlkm) /tmp_mnt/* /tmp /var/adm/btmp /var/adm/crash/* /var/adm/inetd.sec /var/adm/ptydaemonlog /var/adm/rc.log /var/adm/shutdownlog /var/adm/vtdaemonlog /var/adm/wtmp /var/adm/cron/log /var/adm/lp/*log /var/adm/sessions/* /var/adm/sw/*log /var/adm/syslog/* /var/dt/* /var/opt/dce/config/*log /var/sam/log/samlog /var/spool/lp/log /var/tmp/* ' ###################################################################### # usage() # # Usage message then exit. # usage(){ print " usage: ${1##*/} -[cdfilmnpsuvxh] -c [z|g|n] Compression method 'z' for comress, 'g' for gzip, or 'n' for none. gzip is the default. -d [directory|device] Destination directory eg. '/var/tmp/foo/', or device file name eg. '/dev/rmt/0m'. The default is '/var/tmp/'. -f [file] User defined list of files to be reset to newconfig state or ignored altogether. This list can be in addition to the default level 2 behavior or it can replace the default level 2 list. This option will override the -l option and sets the level to 2. -i Remove the bundle definition for the English language. This is only useful for creating archives for use in multi-language or non-English installs. -l [1|2] Level of clean-up: 1-none (overridden to 2, if -f option used) 2-Remove most net/host info, log files, device files, and remove hardware-specific drivers from /stand/system. Actions are configurable; see -f option. The default level is 2. -m [c|t] Archive method 'c' for cpio, 't' for tar. tar is the default. -n [file] Name of the archive file. If the -d option is a device file this option is ignored. The default is the hostname followed by either '.Z' for a compress'd file or '.gz' for a gzip'd file, eg. myhost.gz . -p Preview mode does all of the checks and creates the command line, but doesn't modify any files except for ${log_file}. -s [IP|local] IP address of server to send image to, or 'local' if local (client side) file system, disk or tape. The default is the install server. -u Check the archive destination for sufficient storage capacity. The capacity of raw devices isn't easily checked so this option has no effect in the case where a raw device is the archive destination. The default is to _not_ check capacity. -v Verbose mode for file clean operations, and output from stderr and stdout also put in ${log_file}. -x Print to stdout and to ${filelist_log} list of files that will be set to newconfig state and files that will be explicitly excluded at level 2. This doesn't include files excluded because they are sockets, named pipes, the local archive destination or on remote filesystems. If the -f option is used the effects will be shown here. With this option selected no archiving action is taken. -?|h This screen. NOTE: If all of the defaults are used, a gzip'd tar image of the system with all of the host and network information removed will be placed on the install server in /var/tmp/hostname.gz" usage_exit=1 exit 1 } # End usage() ###################################################################### # parse_extern_file_list() # # Parse file containing list of files to reset or exclude from archive. # If either section of the list contains the word 'ONLY' in the type description # then the default list corresponding to that section is discarded and only the # list from the file is used otherwise the list is concatenated to the default # list. # Non-recovery mode only. # example file (ignore '#' in first column) # ---------------- # # Files to reset to newconfig # # # + RESET # /etc/passwd # this is a comment after a file to be reset # + ONLY NO_ARCHIVE # /var/tmp/* # discard default NO_ARCHIVE list and only ignore /var/tmp files # parse_extern_file_list() { list_file="$1" typeset list # # print out the appropriate portions of the list # list_print(){ awk -v type="$1" 'BEGIN {found="0"} { # if we have identified the section we are interested in if (found == "1") # line not comment,keyword, or blank if ( $0 !~ /^\#|^\+|^$/) print $1 # line is next keyword, exit we are done else if ($0 ~ /^\+/) exit 0 else next else if ($0 ~ type && $0 ~ /^\+/) found="1" }' $list_file } # determine 'ONLY' list(s) that override the internal defaults # for list in $(echo \ $(awk '$0 ~ /^\+/ && $0 ~ /ONLY/ {gsub("^\+|ONLY","") print }' ${list_file})) do # empty the list eval LEVEL2_${list}="" done # load the file contents into the appropriate list # LEVEL2_RESET=${LEVEL2_RESET}$(list_print RESET) LEVEL2_NO_ARCHIVE=${LEVEL2_NO_ARCHIVE}$(list_print NO_ARCHIVE) LIST_INCLUDE_EXCLUDE=${LEVEL2_NO_ARCHIVE} } # End parse_extern_file_list() ###################################################################### # parse_recovery_file_list() # # Parse file containing list of files to include in archive for recovery. # example file (ignore '#' in first column) # Recovery mode only. # The format is the same as the make_recovery file list format # The keyword "dir" is optional # ---------------- # ### Template Include List ### # dir /opt/OMNI # dir /opt/ANSI # dir /opt/SHAREDX # parse_recovery_file_list() { list_file="$1" typeset list # # print out the appropriate portions of the list # list_print(){ awk 'BEGIN {} { # line not comment,keyword, or blank if ( $0 !~ /^\#|^\+|^$/) { if ( $1 == "dir" || $1 == "file" || $1 == "DIR" || $1 == "FILE" ) print $2 else print $1 } next }' $list_file } # load the file contents into the appropriate list # LIST_INCLUDE_EXCLUDE=$(list_print) LIST_INCLUDE_EXCLUDE=$(find ${LIST_INCLUDE_EXCLUDE} -type f 2>/dev/null) } ###################################################################### # verbose() # # Print messages to standard out if command line -v option used # or progress indicator dot if not verbose. # Print to stderr and log file if -e flag (error). # Print to stderr,log file, then exit if -fe (fatal error). # # CALLS: time_stamp_log # verbose() { ((VERBOSE)) && ERROROUT=" 2>&1 | tee -a ${log_file}" case $1 in -p) # Just printing file list shift 1 print "$*" print "$*" >> ${filelist_log} return ;; -e) # Error message to screen and log. shift 1 print -u2 "$*" print "$*" >> ${log_file} return ;; -fe) # Error message to screen and log, then exit (fatal). shift 1 print -u2 "$*" print "$*" >> ${log_file} state="END" time_stamp_log exit 1 ;; esac if ((VERBOSE)) then print "$*" | tee -a ${log_file} else printf . fi } # End verbose() ###################################################################### # message() # # Print message as to what is being used to packup the system , where is it # going, and how big it is. # # CALLS: verbose # message(){ typeset msg (( PREVIEW )) && ( print "\n\t\t+===================================+ \t\t|\t\t\t\t |\n\t\t| PREVIEW MODE, NO ARCHIVE CREATED. | \t\t|\t\t\t\t |\n\t\t+===================================+" ) if [[ $WHERE = "device" ]];then if [[ $COMPRESS = "cat" ]];then METHOD="${METHOD} image to ${SERVER} device" else METHOD="dd ${METHOD} image to ${SERVER} device" fi DESTINATION=${DEST_DIR} else METHOD="${METHOD} to ${SERVER} " DESTINATION="in location ${DEST_DIR}/${ARCHIVE_NAME}" fi msg="\nArchiving contents of ${INST_HOSTNAME} via ${METHOD} ${DESTINATION}. ${ARCHIVE_SIZE_MSG}" if ((VERBOSE)); then # print to log and to screen verbose "${msg}" else # print only to screen print "${msg}" fi verbose "Clean level ${CLEAN_LEVEL} selected: ${CLEAN_MSG[${CLEAN_LEVEL}]}" } # End message() ###################################################################### # time_stamp_log() # # Mark beginning and end of log with a date/time stamp and a label depending # on the variable 'state' # time_stamp_log(){ typeset msg msg=$(date +"======= %x %X %z ${state} ${myname##*/}") [[ -n ${log_file} ]] && eval print ${msg} ${ERROROUT} } # End time_stamp_log() ###################################################################### # how_to_archive() # # Setup the command line to archive the system, including files to exclude from # the archive, the method string, and the block size in case dd to tape is used. # Use pax to create cpio or tar archive of the system, default is tar. # how_to_archive(){ typeset input=$1 # Archive method specific variables. # case ${input} in c) METHOD="cpio" ARCH_COMMAND="find_files | /sbin/pax -tdwx cpio -f - " BLOCK="5120" ;; *) METHOD="tar" if (($bad_pax));then ARCH_COMMAND="find_files | filter_pax_bug_files | /sbin/pax -tdwx ustar -f - " else ARCH_COMMAND="find_files | /sbin/pax -tdwx ustar -f - " fi BLOCK="10k" ;; esac } # End how_to_archive() ###################################################################### # how_to_compress() # # Setup the compression method for the system image and the filename suffix # to be used. Default is gzip (gz). # how_to_compress(){ case $1 in z) COMPRESS="/usr/bin/compress" SUFFIX="Z" RATIO=1.55 ;; n) COMPRESS="cat" SUFFIX="none" RATIO=1.005 # seems odd, but pax has a small compression side effect ;; *) COMPRESS="/usr/contrib/bin/gzip" SUFFIX="gz" RATIO=2 ;; esac } # End how_to_compress() ###################################################################### # make_generic_sysfile() # # Remove the drivers that are in the hardware specific system file from the # general /stand/system file if they also exist in the list of drivers for the # INSTALL kernel. The modified /stand/system file will remain in the archive. # # Note for 11.x: # The the system files in /stand/dlkm and /stand/system.d are not modified. # # CALLS: /usr/lbin/sysadm/create_sysfile # make_generic_sysfile(){ # Create hardware specific system file. # print # readability eval /usr/lbin/sysadm/create_sysfile /tmp/hwsysfile ${ERROROUT} # list of all drivers in the INSTALL kernel # piped to awk # print " CentIf CharDrv GSCtoPCI asio0 audio beep btlan0 btlan1 btlan3 btlan4 btlan6 c700 ccio cdfs clone cn consp1 core dev_config disc1 disc3 disc4 dlpi eisa epic fcT1_cntl fcT1_fcp fc_arp fcgsc_lan fcpmux fddi fddi0 fddi2 fddi3 fddi4 framebuf graph3 hil hpfl1 hpib1 hpstreams inet install ite lan2 lan3 lanmux0 ldterm lpr2 lv lvm mux2 mux4 nfs_client nfs_core nms pa pckt pflop ps2 ptem ptm pts pty0 pty1 schgr scsi1 scsi3 sctl sdisk sflop sio sppcore stape sy tape1 tape2 tape2_included target telm tels timod token token2 tun uipc vglan0 vxbase wsio"| awk 'BEGIN{SYSTEM="/stand/system" holdfile="/tmp/keepsysfile" hwsysfile="/tmp/hwsysfile" kernlist=""} ############################## # remove driver from system file # function remove_it(SYSTEM,holdfile,driver) { system("sed /"driver"/d "SYSTEM">"holdfile";mv "holdfile" "SYSTEM) } # End remove_it() ############################## # make a list of drivers separated by space to be turned into an array # function make_list(file, list,element) { list="" while (getline element < file) {if (element !~ /^\*/) { list=list" "element}} close file return list } # End make_list() {kernlist=kernlist" "$0 } END{ #create an array from each driver list, and capture the number of elements # kerncnt=split(kernlist,kernary) hwcnt=split(make_list(hwsysfile),hwdary) syscnt=split(make_list(SYSTEM),sysary) ############################## # for each driver in the file created with create_sysfile check if it is # in the install kernel, if it is then remove it from the system file. # for(i=1;i<=hwcnt;i++) { # for each create_sysfile driver for(j=1;j<=kerncnt;j++) # check the install driver list if(hwdary[i] == kernary[j]) { for(k=1;k<=syscnt;k++) { # remove create_sysfile driver from system if(hwdary[i] == sysary[k]) { remove_it(SYSTEM,holdfile,hwdary[i]) break } #endif } #endfor break } #endif } #endfor }' eval rm /tmp/hwsysfile ${ERROROUT} } # End make_generic_sysfile() ###################################################################### # append_keepsafe() # # Add files to the keepsafe archive that aren't to be saved in the # system image, but need to be put back in place when the image has been # transferred. # append_keepsafe() { if [[ ( -f $1 || -d $1 ) && ${PREVIEW} = 0 ]];then eval /sbin/pax -w -autf /tmp/ign_configure/keepsafe $1 ${ERROROUT} fi } # End append_keepsafe() ###################################################################### # copy_newconfig() # # Move existing file to keepsafe archive then move newconfig version into # place. Expects only files; ignores directories. # # CALLS: append_keepsafe # verbose # copy_newconfig(){ typeset cpfile # Take off newconfig path if it was passed in. # cpfile=${1#/usr/newconfig} append_keepsafe ${cpfile} if [[ -f /usr/newconfig${cpfile} ]]; then verbose "cp -p /usr/newconfig${cpfile} ${cpfile}" ((! PREVIEW)) && eval cp -p /usr/newconfig${cpfile} ${cpfile} ${ERROROUT} fi } # End copy_newconfig() ####################### # archive_size_check() # # Get approximate size of archive to be produced, # assumption is 2:1 gzip, 1.55:1 compress . # Check destination to make sure there is enough room. # # CALLS: restore_keepsafe (if disk space check fails) # archive_size_check(){ typeset -i ARCHIVE_SIZE DEST_SIZE PERCENT_DIFF DEST_REMAIN # Add up "kbytes used" column from local file systems using "bdf" command. # subtract contribution from files and dirs in ignore list # For recovery mode, just add the _include_ files to 0. ARCHIVE_SIZE=$(bdf -l | awk -v ratio=${RATIO} -v file=${include_exclude_list_file} \ -v recovery=${recovery_mode} 'BEGIN { holdfile=file"_duks" file_red=file"_red" usedsum=0 include_exclude_size=0 } ###################################################### # reduce_list takes the ignore list and removes duplicate counts # of the same directory to avoid over compensating for ignored # dirs/files function reduce_list(file, oldpat,pat) { oldpat="zzzzzzz_zzzzzz"; pat="" while (getline < file) { pat=$0 if(pat !~ oldpat && ! system("test -s "pat)) { print pat >>"/tmp/ign_configure/t" oldpat=pat} } close file; close "/tmp/ign_configure/t" system("mv /tmp/ign_configure/t "file"_red 2> /dev/null") } ##end reduce_list() function # Begin total system size calculation # { if( (recovery != "TRUE") && ($0 !~ /^F/)) usedsum+=$3 } # Begin compensating for files in the ignore list # END { # check if ignore file exists if(! system("test -s "file)){ reduce_list(file) while (getline < file_red) system("du -xs "$0" >> "holdfile) while (getline < holdfile) include_exclude_size+=$1 if( recovery != "TRUE" ) usedsum-=((include_exclude_size*512)/1000) else usedsum=((include_exclude_size*512)/1000) } close(file);close(holdfile);close(file_red) printf("%d",(usedsum/ratio)) }') ARCHIVE_SIZE_MSG="Approximate archive size is ${ARCHIVE_SIZE} kbytes.\n" if ((ARCHIVE_SIZE >= 2000000));then stars="**********************************************************************" print -u2 "$stars" print -u2 "$stars\n" print -u2 "WARNING: Archive files larger than 2GB are not supported." print -u2 "The approximate size of the archive (${ARCHIVE_SIZE} kbytes) is at or beyond this limit." print -u2 "\n$stars" print -u2 "$stars\n" read ans?"Do you wish to continue (y/n)? " [[ $ans = [nN]||$ans = [qQ]||$ans = [eE] ]] && exit 1 fi # Make sure there is enough room in the destination directory. # If the destination is a device we can't easily check capacity so skip, # or if the command line specifies "-u". # if [[ ${NOCHECK} = "0" ]];then if [[ ${SERVER} != "local" ]];then DEST_SIZE=$(remsh ${SERVER} -n bdf ${DEST_DIR} | awk '$0 !~ /^F/ { if(length($4) && $4 !~ /%/) print $4 else if (length($3)) print $3}') else DEST_SIZE=$(bdf ${DEST_DIR} | awk '$0 !~ /^F/ { if(length($4) && $4 !~ /%/) print $4 else if (length($3)) print $3}') fi # If the DEST_DIR doesn't exist then plug in a size so DEST_SIZE has value. : ${DEST_SIZE:=1} # If bdf reports 0 or less available, plug in 1 to prevent div by zero. ((DEST_SIZE<=0)) && DEST_SIZE=1 PERCENT_DIFF=$(( 100*${ARCHIVE_SIZE}/${DEST_SIZE} )) if (( PERCENT_DIFF > 100 )) then verbose -e "ERROR: Not enough space for archive.\n ${DEST_DIR} on ${SERVER} needs ${ARCHIVE_SIZE} kbytes free." restore_keepsafe else (( DEST_REMAIN=${DEST_SIZE}-${ARCHIVE_SIZE} )) (( DEST_REMAIN < 0 )) && DEST_REMAIN=0 # Print reasonable message. ARCHIVE_SIZE_MSG="Approximate archive size is ${ARCHIVE_SIZE} kbytes.\n Free space on ${SERVER}:${DEST_DIR} after archive is ${DEST_REMAIN} kbytes." fi fi } # End archive_size_check() ###################################################################### # check_server_access() # # Check for rpc access to server. # # CALLS: verbose # check_server_access(){ typeset check_server=$1 failed failed=$(ping ${check_server} 8 2 | awk '$0 ~ /\% packet/ {sub("%","") if($7<100) print "0" else print "1"}') : ${failed:=1} # make sure failed has a value if ((failed)) # Couldn't ping. then verbose -e "ERROR: Can't contact server ${check_server}." else # If could ping check for rpc access. ((failed=$((remsh ${check_server} -n bdf ) >/dev/null 2>&1;echo $?))) && ( verbose -e "WARNING: Can't remsh server ${check_server}. Check server .rhosts file" ) fi if ((failed)) then return 1 else return 0 fi } # End check_server_access() ###################################################################### # log_to_server() # # Move the logfile to the install server if available, or the destination # remote server. The 'server' variable is obtained from either the # /tmp/install.vars or /var/opt/ignite/local/host.info files. # # CALLS: check_server_access # log_to_server(){ state="END" time_stamp_log # if the archive destination is a device then we're done if (( usage_exit )); then exit 0 fi # if the archive destination is a device then we're done if [[ ${WHERE} = "device" ]]; then exit 0 fi # Check if we know the install server and can get to it. # If not, put log on server that is getting the archive in the DEST_DIR. # If no server can be determined leave the log in /tmp/ign_configure # if [[ -n ${SERVER} || -n ${server} ]]; then # server if if [[ ${SERVER} != "local" && ${server} != "local" ]];then #local if # try destination server if ((SERVER_OK));then if [[ ${SERVER} = ${server} ]];then remote_log="/var/opt/ignite/logs/${myname##*/}" else remote_log="${DEST_DIR}/${myname##*/}.log" fi log_server=${SERVER} # try Ignite server next elif [[ -n ${server} && $(check_server_access ${server};echo $?) = 0 ]] then remote_log="/var/opt/ignite/logs/${myname##*/}" log_server=${server} else # couldn't contact the remote, and this wasn't a local archive exit 0 fi else # if either server var is "local" remote_log="${DEST_DIR}/${myname##*/}.log" log_server="local" fi # end local if if (( ! usage_exit )) ;then # usage if print "Moving ${log_file} to ${log_server} in location ${remote_log}." if [[ ${log_server} != "local" ]];then cat ${log_file} | remsh ${log_server} "cat >> ${remote_log}" else mv ${log_file} ${remote_log} fi rm -f ${log_file} fi # end usage if fi # end server if } # End log_to_server() ###################################################################### # source_it() # # source in a file filtering out 'init' and other lines the shell # doesn't like. Account for unbalanced quotes. # source_it(){ eval $(awk '$0 !~ /\[|^#|init |INIT / && $0 ~ /=/ \ {if ( ((gsub("\"","\"")%2)) == 0) print $0}' ${1}) } ###################################################################### # init_vars() # # Initialize internal variables and source in anything in /tmp/install.vars # or /var/opt/ignite/local/host.info . # # CALLS: time_stamp_log # source_it # init_vars() { ignore_fixed="" ignore_fixed_file="/tmp/ign_configure/ignore_fixed$$" include_exclude_list_file="/tmp/ign_configure/include_exclude$$" recovery_mode="FALSE" myname="$1" log_file="/tmp/ign_configure/${myname##*/}.log" method_set=0 compress_set=0 cur_hostname=$(hostname) filelist_log="/tmp/excluded_files" state="BEGIN" usage_exit=0 ERROROUT=" 1>>${log_file} 2>&1" LIST_INCLUDE_EXCLUDE="" NOCHECK=1 PREVIEW=0 PRINT_LIST=0 SERVER_OK=0 SWREMOVE_LANG=0 USE_EXTERNAL=0 VERBOSE=0 WHERE="null" # next three lines used by the routine that tests for pax bugs # err_file=/tmp/pax_bug_files pax_cmd=/sbin/pax bad_pax=0 export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/contrib/bin # Clean level messages. # CLEAN_MSG[1]="Leave system as is, no files removed." CLEAN_MSG[2]="Remove host identity, and most network information." # Make sure there is an ign_configure dir. # [[ ! -d /tmp/ign_configure ]] && mkdir -p /tmp/ign_configure time_stamp_log # Propagate install.vars and host.info. # Some values may get overridden by command line options. # typeset instvars="/tmp/install.vars" [[ -s ${instvars} ]] && source_it ${instvars} typeset hostinfofile="/var/opt/ignite/local/host.info" [[ -s ${hostinfofile} ]] && source_it ${hostinfofile} cp /dev/null ${include_exclude_list_file} } # End init_vars() ###################################################################### # archive_setup() # # Setup the vars that are needed to archive the system. Exit if not enough # information is available. # CALLS: how_to_archive # how_to_compress # check_server_access # verbose # archive_setup(){ if [[ ${recovery_mode} = "TRUE" && \ ${USE_EXTERNAL} = "0" ]] ;then verbose -fe "ERROR: A filename must be specified (-f option) for recovery mode." fi # parse the file listing files to # reset/exclude/recover # if [[ -n ${EXTERNAL_FILE} ]]; then if [[ ${recovery_mode} != "TRUE" ]]; then parse_extern_file_list ${EXTERNAL_FILE} else parse_recovery_file_list ${EXTERNAL_FILE} fi fi # save current stty settings, then disable "quit" and "susp" to prevent # forced coredump or suspending which keeps transition links from being put # back in place stty -g > /tmp/stty_save stty quit ^- susp ^- swtch ^- verbose "Archive setup." # Check for environment variables. # if [[ -z ${SERVER} ]];then # If the server variable isn't set make sure the install source isn't a # tape or cd-rom device. if [[ ${server} != /dev/* ]];then SERVER=${server} else verbose -fe "ERROR: $server not a valid server destination. SERVER variable must be 'local' or a valid IP address." fi fi # Set some reasonable defaults if values not supplied. # : ${DEST_DIR:='/var/tmp'} # Destination dir default /var/tmp. : ${INST_HOSTNAME:=$(hostname)} # If not in install.vars set to hostname. : ${CLEAN_LEVEL:=2} # Default to clean all. # if the current hostname isn't the same as the install time hostname use # the current one. # [[ -n ${cur_hostname} && ${INST_HOSTNAME} != ${cur_hostname} ]] && INST_HOSTNAME=${cur_hostname} # Determine if the destination is a device. # Set 'NOCHECK' if it is a device. # WHERE=$(awk -v dest_dir=${DEST_DIR} 'BEGIN {if(dest_dir ~ /\/dev\//) print "device"}' /dev/null) [[ $WHERE = "device" ]] && NOCHECK=1 # Set compression and archive methods. # how_to_compress ${COMPRESS:="g"} # Default use gzip. how_to_archive ${METHOD:="t"} # Default use tar. : ${ARCHIVE_NAME:=${INST_HOSTNAME}"."${SUFFIX}} # If the archive is being kept locally or on an nfs mount, exclude # its path. if [[ ${SERVER} = "local" && ${WHERE} != "device" ]];then ignore_fixed="${DEST_DIR}/${ARCHIVE_NAME}/" fi if [[ -n ${DEST_DIR} && -n ${ARCHIVE_NAME} ]];then : # okie dokie else verbose -fe "ERROR: Internal error. Destination directory or archive name not set. Contact HP support." fi if [[ -n ${SERVER} ]];then : # okie dokie else verbose -fe "ERROR: Could not determine the destination server (local or remote). The information is expected in either the files /tmp/install.vars or /var/opt/ignite/local/host.info, or on the command line via the -s option." fi # If clean level is >= 2, make sure the /tmp dir is large enough to hold # the keepsafe archive (3Mb). This boundary allows 400Kb headroom on an # LVM system. # if ((CLEAN_LEVEL >= 2)) then if (( $(bdf /tmp|awk '$0 !~ /^F/ {print $4}') < 3000 )) then verbose -fe "ERROR: /tmp needs 3Mb available to save files necessary to restore the system to a running state after archiving at clean levels greater than 1." fi fi # If remote server destination, make sure we can get there. # if [[ ${SERVER} != "local" ]];then (( $(check_server_access ${SERVER};echo $?) )) && exit 0 SERVER_OK=1 #we could get there, don't check again in the logging function fi pax_test_func if [ ${bad_pax} = 1 ];then verbose -e "WARNING: The version of pax on your system needs patches: PATCH OS ----- -- PHCO_11975 10.01 PHCO_11976 10.10 PHCO_11977 10.20 PHCO_11978 10.30 You can extract a patched version from /opt/ignite/data/Rel_B./SYSCMDS on your Ignite-UX server: /usr/contrib/bin/gzcat /opt/ignite/data/Rel_B./SYSCMDS | /sbin/pax -r -pe -s/pax/patched_pax/p -f - ./sbin/pax Then replace /sbin/pax on the system to be archived with the extracted version patched_pax. Files effected by the bug can be found in ${err_file}, if the file is empty then you will not be effected." fi } # End archive_setup() ########################################################################## # # This function is used to detect files piped from stdin # that fit the characteristics that expose a couple bugs # in the "pax" command when making tar archives # # Bad files will be listed in $err_file, if this file does # not exist then there were no bad files. # # Otherwise the file names piped to this routine are printed to stdout # filter_pax_bug_files() { rm -f $err_file awk ' { if (length > 100) { past_100=substr($0, 101); if (index(past_100, "/") != 0) print "File: " $0 " exposes pax bug, directory longer than 100 chars" >>"'$err_file'" } else print $0 }' } ########################################################################## # pax_test_func() # # returns 0 if $pax_cmd has been patched such that it does not have. # the bugs concerning 100 char files/dirs. # # It does a couple tests on the command to detect it. # pax_test_func() { pax_archive=/tmp/pax.out.$$ first_file="/tmp/pax_test/foo" file_100="/tmp/pax_test/567890/234567890/234567890/234567890/234567890/234567890/234567890/234567890/23456789f" dir_100="/tmp/pax_test/567890/234567890/234567890/234567890/234567890/234567890/234567890/234567890/234567890/f" last_file="/tmp/pax_test/bar" rm -rf /tmp/pax_test mkdir /tmp/pax_test echo first > $first_file mkdir -p $(dirname $file_100) echo "path is 100 chars" > $file_100 mkdir -p $(dirname $dir_100) echo "dir at 100" > $dir_100 echo last > $last_file $pax_cmd -wx ustar -f $pax_archive /tmp/pax_test if [ $? != 0 ] ; then echo "ERROR: $pax_cmd failed, cannot use it" bad_pax=1 return 1 fi rm -rf /tmp/pax_test # # Now try to extract the archive using "tar". If it is the bad # version of pax then tar will end when it hits $dir_100, so # neither $dir_100 or $last_file will exist. # /sbin/tar -xf $pax_archive if [ $? != 0 ] ; then echo "ERROR: tar failed, cannot test pax cmd" rm -rf /tmp/pax_test $pax_archive bad_pax=1 return 1 fi if [ ! -f $first_file ] ; then echo "ERROR: unexpected result of test of pax cmd" rm -rf /tmp/pax_test $pax_archive bad_pax=1 return 1 fi if [ ! -f $dir_100 -o ! -f $last_file ] ; then rm -rf /tmp/pax_test $pax_archive bad_pax=1 return 1 # bad pax else rm -rf /tmp/pax_test $pax_archive return 0 # good patched pax. fi } ###################################################################### # clean() # # In general, don't actually remove any files, just add them to an ignore list. # There are a few cases in which irrelevant files ARE removed. Other files # may be nulled. Keep original versions of modified files so they can be # restored later. Actions are dependent on the CLEAN_LEVEL selected via the # command line or default value. # # CALLS: make_generic_sysfile (called for CLEAN_LEVEL >= 2) # append_keepsafe # copy_newconfig # verbose # clean(){ typeset cpdir i ignore line byefile # remove "bundle." for HPUXEng if it exists and this is and ignition archive if [[ ${SWREMOVE_LANG} = "1" ]];then # handle the SD keyword difference between pre 10.30 and 10.30 systems # typeset category_key="category" typeset swrm_opts category_key=$(uname -r|awk -F. '{rel=$2$3 if(rel >= 1030) print "category_tag" else print "category"}') ((! PREVIEW)) && verbose "Removing language bundle definition(s)." (( PREVIEW )) && (swrm_opts="-p" verbose "Preview of removing language bundle definition(s).") for i in $(swlist -a ${category_key} | grep HPUXEnvironments | grep -E -e HPUX.*[CRTGS78]00 -e HPUX.*[6432].* | awk '{print $1"."}') do swremove ${swrm_opts} ${i} done fi # Remove any sysdump core directories. # Remove any program core files or SD "marked for remove" files. # This class of files gets removed regardless of the clean level. # verbose "Removing any core files from the system" #if [[ -d /var/adm/crash && ${PREVIEW} = 0 ]];then # find /var/adm/crash \( -name core.[0-9] -o -name bounds \) \ # -exec rm -rf {} \; & #fi ((! PREVIEW)) && find / -fsonly hfs -fsonly vxfs ! -type s ! -type p \ \( -name core -o -name '#*' \) -exec rm -rf {} \; & # Setup keepsafe archive. # ((! PREVIEW)) && pax -w -tf /tmp/ign_configure/keepsafe /dev/null if [[ ${recovery_mode} != "TRUE" ]]; then CLEAN_LEVEL=1 verbose "Recovery archive being created." else verbose "clean level ${CLEAN_LEVEL} selected: ${CLEAN_MSG[${CLEAN_LEVEL}]}" fi if [[ ${CLEAN_LEVEL} -ge 2 ]]; then # Remove transition links so SD won't complain. # print # readability if [[ -x /opt/upgrade/bin/tlremove && ${PREVIEW} = 0 ]];then eval /opt/upgrade/bin/tlremove ${ERROROUT} fi # Make the /stand/system file hardware generic. # if [[ -f /stand/system ]];then append_keepsafe /stand/system ((! PREVIEW)) && make_generic_sysfile fi fi if [[ ${CLEAN_LEVEL} -eq 2 ]]; then # Remove host local identity. # Copy each specified newconfig file down to its corresponding # non-newconfig (configurable) version IF the configurable # version exists. That is, do not create files, just reset # existing configurable ones. # Remove comments. LEVEL2_RESET=$(print "$LEVEL2_RESET" | awk '$1 && $1 !~ "^#" { print $1 }') if (( PRINT_LIST )) ;then print "\n=============================================" print "A copy of these lists is in ${filelist_log}" print "=============================================" verbose -p "\n\nFiles that will be set to newconfig state in the archive." verbose -p "=========================================================\n" eval print ${LEVEL2_RESET} ${FILELIST_OUT} | xargs ls -d else for file in $(find $LEVEL2_RESET -type f 2>/dev/null) do copy_newconfig ${file} done fi if ((VERBOSE)); then verbose "\nFiles and directories excluded from archive.\n" for i in ${LIST_INCLUDE_EXCLUDE} ${ignore_fixed} do verbose "$i" done fi if (( PRINT_LIST )) ;then verbose -p "\n\nFiles that will be excluded from the archive." verbose -p "=============================================\n" eval print ${LIST_INCLUDE_EXCLUDE} ${FILELIST_OUT} | xargs ls -d exit 0 fi fi # End clean level 2 (( CLEAN_LEVEL == 1 )) && : # As is, no cleaning. } # End clean() ###################### # file_list_processing() # # Process the ignore lists into external file for use by archive_size_check # and find_files. # file_list_processing(){ ### Set up include/exclude list, based on all available ignore info. if [[ -n $LIST_INCLUDE_EXCLUDE ]] ;then # make sure we are at root # cd / # Combine lists, remove comments. LIST_INCLUDE_EXCLUDE=$(print "$LIST_INCLUDE_EXCLUDE" | awk '$1 && $1 !~ "^#" { print $1 }' ) # Ensure leading "./", add trailing / for grep pattern control. # The idea is to terminate all paths with a trailing /, so that # exact path names are matched (avoiding "/etc/foobar" matching the # pattern "/etc/foo", for example) and so that entire directory # trees can be included/excluded without listing each individual file in # the include/exclude list. eval print $LIST_INCLUDE_EXCLUDE | xargs ls -d 2>/dev/null | sed 's,^,., ; s,/*$,/,' > ${include_exclude_list_file} fi [[ "${ignore_fixed}" != "" ]] && print "${ignore_fixed}" > ${ignore_fixed_file} } # End file_list_processing() ###################### # find_files() # # Do a find on the entire file system, filtering out the undesired files # as indicated by the ignore lists. # # find_files() { if [[ ${recovery_mode} = "TRUE" ]]; then typeset EXCLUDE_FLAG="" else typeset EXCLUDE_FLAG="v" fi # Find relative to /. cd / # grep_if_needed # only greps if file exists and is not empty # (grepping an empty file breaks the pipe) function grep_if_needed { if [[ -s $2 ]]; then grep $1 -f $2 else cat - fi } if [[ -n "$LIST_INCLUDE_EXCLUDE$ignore_fixed" ]]; then # Run the find, pulling out the to-be-ignored files. Temporarily # add trailing slashes so pattern matching works as desired. find . -fsonly hfs -fsonly vxfs ! -type s ! -type p | sed 's,$,/,' | grep_if_needed -${EXCLUDE_FLAG}F ${include_exclude_list_file} | grep_if_needed -vF ${ignore_fixed_file} | sed 's,/$,,' else if [[ ${recovery_mode} != "TRUE" ]]; then find . -fsonly hfs -fsonly vxfs ! -type s ! -type p fi fi } # End find_files() ####################### # archive() # # Capture a system image. # # CALLS: file_list_processing # archive_size_check # message # verbose # archive(){ # Check for 'find' processes put in background, wait until they are done. # verbose "Waiting for background processes to finish." wait # Expand the ignore lists and check for proper layout for find_files # file_list_processing # Get approximate size of compressed archive. # Make sure the destination has enough space. # archive_size_check # Message telling the method of archiving, where it is going, and how big. # message # Pack it up. # if ((! PREVIEW)) then cd / if [[ ${WHERE} = "device" ]];then if [[ ${SERVER} = "local" ]];then # # Writing to local device. # NB. to extract individual files from the compressed archive: # dd ibs=(tar-10k,cpio-5120) if=/dev/rmt/0m |gzcat|pax -r -pe # to extract the archive as a file: # dd ibs=(tar-10k,cpio-5120) if=/dev/rmt/0m of=archive.gz # if no compression is set then # pax -rvf /dev/rmt/0m # if [[ $COMPRESS = "cat" ]];then # # with no compression the archive command is modified to put the # pax command at the end of the pipe so that it's multi-volume # capabilities can be used # DEV_FILE=$(echo ${DEST_DIR} | sed 's/\//\\\//g') ARCH_COMMAND=$(echo "${ARCH_COMMAND}" | sed "s/\-f \-/\-f ${DEV_FILE}/") verbose "${ARCH_COMMAND}" eval ${ARCH_COMMAND} else verbose "${ARCH_COMMAND} | ${COMPRESS} | dd of=${DEST_DIR} obs=${BLOCK}" eval ${ARCH_COMMAND} | ${COMPRESS} | dd of=${DEST_DIR} obs=${BLOCK} fi else # # Writing to device file on remote. # NB. to restore: # dd ibs=(tar-10k,cpio-5120) if=/dev/rmt/0m |gzcat|pax -r -pe # to extract the archive as a file: # dd ibs=(tar-10k,cpio-5120) if=/dev/rmt/0m of=archive.gz # verbose "${ARCH_COMMAND} | ${COMPRESS} | /usr/bin/remsh ${SERVER} \"dd of=${DEST_DIR} obs=${BLOCK}\" " eval ${ARCH_COMMAND} | ${COMPRESS} | /usr/bin/remsh ${SERVER} "dd of=${DEST_DIR} obs=${BLOCK}" fi else #destination is a file system if [[ ${SERVER} = "local" ]];then # # Writing to local file system. # NB. to restore: # gzcat (archive name)|pax -r -pe # verbose "${ARCH_COMMAND} | ${COMPRESS} > ${DEST_DIR}/${ARCHIVE_NAME}" eval ${ARCH_COMMAND} | ${COMPRESS} > "${DEST_DIR}/${ARCHIVE_NAME}" else # # Writing to file system on remote. # NB. to restore: # gzcat (archive name)|pax -r -pe # verbose "${ARCH_COMMAND} | ${COMPRESS} | /usr/bin/remsh ${SERVER} \"cat >${DEST_DIR}/${ARCHIVE_NAME}\" " eval ${ARCH_COMMAND} | ${COMPRESS} | /usr/bin/remsh ${SERVER} "cat >${DEST_DIR}/${ARCHIVE_NAME}" fi fi fi ######################################################################## # Check for any files in the pax_bug_files file # [[ -s ${err_file} ]] && verbose -e "ERROR: There were files that were not successfully archived because of a pax bug. Check ${err_file} for the list." } # End archive() ###################################################################### # restore_keepsafe() # # Put original files back in place from the keepsafe archive. # Create device special files. # Build kernel based on original system file. # Install transition links. # # CALLS: verbose # time_stamp_log # log_to_server # restore_keepsafe() { typeset exit_msg="Cleanup: Do Not interrupt, restoring files, kernel, and transition links." # save stty settings, then disable keyboard signals that could stop tlinstall # from completing. [[ ! -f /tmp/stty_save ]] && stty -g > /tmp/stty_save stty intr ^- quit ^- eof ^- susp ^- kill ^- stop ^- # If didn't exit from usage and not preview. if ((! usage_exit && ! PREVIEW)) then if [[ -f /tmp/ign_configure/keepsafe ]]; then if ((VERBOSE));then verbose "${exit_msg}" else print -u2 "${exit_msg}" fi [[ -f /tmp/__tl.lock ]] && rm -f /tmp/__tl.lock [[ -f /tmp/__tl.filesets ]] && rm -f /tmp/__tl.filesets cd / eval /sbin/pax -r -pe -f /tmp/ign_configure/keepsafe ${ERROROUT} eval rm -rf /tmp/ign_configure/keepsafe ${ERROROUT} eval rm -rf /tmp/ign_configure/ignore* ${ERROROUT} eval rm -rf /tmp/ign_configure/include* ${ERROROUT} eval /opt/upgrade/bin/tlinstall ${ERROROUT} fi fi # put original stty settings back stty $(cat /tmp/stty_save) log_to_server exit 0 } # End restore_keepsafe() ###################################################################### # MAIN ###################################################################### # Run restore_keepsafe() on exit. # trap restore_keepsafe EXIT 1 2 3 15 # Initialize some internal variables and source /tmp/install.vars # and /var/opt/ignite/local/host.info. # init_vars $0 # Gather up any command line info. # while getopts :c:d:f:s:l:m:n:ipuvxh? opt do case $opt in c) # compress (z), (g)zip, or (n)one. COMPRESS=$OPTARG ;; d) # Archive destination directory or device. DEST_DIR=$OPTARG if [[ $DEST_DIR != /* ]]; then verbose -fe "The destination directory or device must start with '/'." fi ;; f) # use external file to specify additional files to reset/ignore/recover # default the clean level to 2 USE_EXTERNAL="1" if [[ ! -f $OPTARG ]]; then verbose -fe "ERROR: File $OPTARG does not exist." fi [[ ${USE_EXTERNAL} = "1" && ${CLEAN_LEVEL} = "1" ]] && \ verbose -e "Clean level set to 2 because the -f option was specified." CLEAN_LEVEL="2" EXTERNAL_FILE=$OPTARG ;; i) # for ignition purposes remove the language "bundle." SWREMOVE_LANG=1 ;; l) # Clean-up level 1,2. default to 2 if -f used [[ ${OPTARG} != [12] ]] && \ verbose -fe "ERROR: ${OPTARG} not valid for the -l option, only 1 or 2 allowed." CLEAN_LEVEL=$OPTARG [[ ${USE_EXTERNAL} = "1" && ${CLEAN_LEVEL} = "1" ]] && \ verbose -e "Clean level set to 2 because the -f option was specified." [[ ${USE_EXTERNAL} = "1" ]] && CLEAN_LEVEL="2" ;; m) # Archive method (t)ar or (c)pio. METHOD=$OPTARG ;; n) # Archive name instead of hostname.* ARCHIVE_NAME=$OPTARG ;; p) # preview mode. PREVIEW=1 ;; # r) # recovery mode. # recovery_mode="TRUE" # ;; s) # Server IP to push the archive back to, or "local". SERVER=$OPTARG ;; u) # do destination capacity test NOCHECK=0 ;; v) # Verbose mode. VERBOSE=1 ;; x) # print files to be reset or excluded PREVIEW=1 PRINT_LIST=1 > ${filelist_log} FILELIST_OUT=" 2>/dev/null | tee -a ${filelist_log}" ;; \?|h) usage $0 ;; esac done # Setup environment for archiving, and do some variable and server # sanity checks. # archive_setup # Scrub the system. # clean # Now that we know all that stuff start packing up and moving. # archive # Put back files that were moved out of the way. # restore_keepsafe() gets run on exit. #