#!/bin/ksh
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
# bos72X src/bos/usr/lib/nim/methods/c_mksavevg.sh 1.15.3.6 
#  
# Licensed Materials - Property of IBM 
#  
# Restricted Materials of IBM 
#  
# COPYRIGHT International Business Machines Corp. 2002,2021 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
# @(#)10 1.15.3.6 src/bos/usr/lib/nim/methods/c_mksavevg.sh, cmdnim, bos72X, x2021_05A0 1/28/21 11:33:45
#
#   COMPONENT_NAME: CMDNIM
#
#   FUNCTIONS: ./usr/lib/nim/methods/c_mksavevg.sh
#
#   ORIGINS: 27
#
#
#   (C) COPYRIGHT International Business Machines Corp. 2002
#   All Rights Reserved
#   Licensed Materials - Property of IBM
#   US Government Users Restricted Rights - Use, duplication or
#   disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
#

# include common NIM shell defines/functions
NIMPATH=${0%/*}
NIMPATH=${NIMPATH%/*}
[[ ${NIMPATH} = ${0} ]] && NIMPATH=/usr/lpp/bos.sysmgt/nim
NIM_METHODS="${NIMPATH}/methods"
. ${NIM_METHODS}/c_sh_lib

#---------------------------- local defines     --------------------------------
EXCLUDE_VG=""
EXCLUDE_PACKVG=""
SAVEVG=/usr/bin/savevg
savevg_mnt_dir=""

#---------------------------- module globals    --------------------------------
REQUIRED_ATTRS="location"
OPTIONAL_ATTRS="server comments exclude_files group mk_image savevg_flags size_preview source verbose volume_group force" 
location=""
server=""
comments=""
exclude_files=""
group=""
mk_image=""
savevg_flags=""
size_preview=""
source=""
verbose=""
volume_group=""

#------------------------------- check_space -----------------------------------
#
# NAME: check_space
#
# FUNCTION:  Calculate the maximum amount of space required for 
#            the savevg image along with the free space available
#            in the target location.  If the maximum required is 
#            greater than the space available, give an error and 
#            exit.  Otherwise, show the user the space 
#            requirements and continue.
#
# EXECUTION ENVIRONMENT:
#
# NOTES:
#       calls error on failure
#       NOTE: The savevg image is packed, so the maximum amount
#             of space required will probably never be completely
#             used.  The compression rate varies widely depending
#             on the type of data that is being backed up. 
#
# RECOVERY OPERATION:
#
# DATA STRUCTURES:
#       parameters:
#       global:
#
# RETURNS: (int)
#       0 = success
#
# OUTPUT:
#-------------------------------------------------------------------------------
function check_space {

    typeset MAX_SPACE_REQ=0
    typeset FREE_SPACE=0
    typeset USE_EXCLUDE_FILE=0  #default does not use exclude files

    # get the free space in the target filesystem 
    # (in 1024-byte blocks)
    FREE_SPACE=`${DF} -k \`${DIRNAME} ${location_access}\` | ${AWK} 'NR==2{print $3}'`
    [[ -z "${FREE_SPACE}" ]] && err_from_cmd ${DF}

    # if "e" in flags, use exclude files    
    USE_EXCLUDE_FILE=$(expr "${savevg_flags}" : ".*e") # counts occurences of "e" => exclude
    MAX_SPACE_REQ=$(calculate_vg_space ${volume_group:-rootvg} ${USE_EXCLUDE_FILE})
    (( REQ_MEG = ${MAX_SPACE_REQ} / 1024 + 1 ))
    (( FREE_MEG = ${FREE_SPACE} / 1024 + 1 ))

    # print the space information 
    ${C_ERRMSG} ${MSG_SPACE_MKSYSB} ${C_ERRMSG_MSG} $MAX_SPACE_REQ \
       $REQ_MEG $FREE_SPACE $FREE_MEG > ${TMPDIR}/msg 2>&1
    if [[ `expr "${savevg_flags}" : ".*p"` -eq 0 ]]
    then
       cat ${TMPDIR}/msg
    else
       head -8 ${TMPDIR}/msg
    fi


    # if just previewing space then exit 
    if [ "${size_preview}" = "yes" ]
    then
        exit 0
    fi

    # is there enough space for the savevg? 
    if (( MAX_SPACE_REQ > FREE_SPACE ))
    then
        # if force ignore space requirements 
        if [ "${force}" != "yes" ]
        then
            error ${ERR_SPACE_MKSYSB}
        fi
    fi 

} # end check_space

#--------------------------- calculate_vg_space --------------------------------
#
# NAME: calculate_vg_space
#
# FUNCTION:  Calculate space ${VG} takes up excluding files if asked
#
# EXECUTION ENVIRONMENT:
#
# NOTES:
#       VG = $1      name of volume group
#       EF = $2      0  => do not exclude files (default) 
#                    >0 => exclude files
#
# RECOVERY OPERATION:
#
# DATA STRUCTURES:
#       parameters:
#       global:
#
# RETURNS: (int)
#       0                           = success
#
# OUTPUT:
#-------------------------------------------------------------------------------
function calculate_vg_space {
    typeset VG=$1 EF=${2:-0}
    typeset lv fs LVLIST
    typeset BUF                   # command output buffer
    typeset TEF=$TMPDIR/ef.nim.$$ # temporary exclude file
    typeset LSVG=/usr/sbin/lsvg
    typeset LSVG=/usr/sbin/lsvg

    cd / 
    BUF=$(${LSVG} -l ${VG} | ${AWK} '$6 ~ "^open" && ($2 == "jfs2" || $2 == "jfs") {print $1,$7}') 

    if [[ ${EF} == 0 ]] ; then
        echo "${BUF}" | while read lv fs; do
            typeset LVLIST="/dev/${lv} ${LVLIST}"
        done
        ${DF} -kIM ${LVLIST} | ${AWK} 'NR > 1 {tot = tot + $4} END {printf "%d",tot+1}'
    else
        echo "${BUF}" | while read lv fs; do
            # this seems ugly but we really need to match how mksysb/savevg works, so...
            # the long pipeline follows the logic:
            # - find command to get the files and sizes per FS
            # - awk to reorder in a manner egrep and we can use (eg, $11=filename first, $2=size)
            # - egrep out based on the exclude file
            # - awk to calculate total and buffer by 512K for the header

            # setup a temporary exclude_file
            > ${TEF}

            # process the same as savevg
            if [[ -f ${EXCLUDE_VG} ]]; then
                ${CAT} ${EXCLUDE_VG} | ${SED} 's/       //g;s/^  *//g;s/  *$//g;/^$/d' > ${TEF}
            fi

            ${FIND} .$fs ! -type s \( -fstype jfs -o -fstype jfs2 \) -xdev -ls |
            ${AWK} '{print $11,$2}' | ${EGREP} -v -f ${TEF} |
            ${AWK} -v fs=".$fs" '{tot=tot+$2} END {printf ("%s\t%d\n",fs,tot)}'
        done  | ${AWK} '{tot=tot+$2} END {printf ("%.0d",tot+512)}' 

        # cleanup the temporary exclude file
        [[ -r ${TEF} ]] && rm -f ${TEF}
    fi

    return 0
}

#---------------------------- process_exclude_file ----------------------------
#
# NAME: process_exclude_file 
#
# FUNCTION:  Sets up access to the exclude file that we are 
#            using and copies it to /etc/exclude.${volume_group:-rootvg} so
#            that the savevg command will use it when backing
#            up the system.  If a previous exclude file exists
#            it will be saved and restored after the backup. 
#
#            Updated to process packing exclude file.
#
# EXECUTION ENVIRONMENT:
#
# NOTES:
#       calls error on failure
#
# RECOVERY OPERATION:
#
# DATA STRUCTURES:
#       parameters:
#       global:
#
# RETURNS: (int)
#       0 = success
#
# OUTPUT:
#-------------------------------------------------------------------------------
function process_exclude_file {

     # save the old exclude file
     if [[ -f ${EXCLUDE_VG} ]]; then
          ${CP} ${EXCLUDE_VG} ${EXCLUDE_VG}.nim_save.$$ ||
               err_from_cmd "${CP} ${EXCLUDE_VG} ${EXCLUDE_VG}.nim_save.$$"
     fi
     # save the old exclude file
     if [[ -f ${EXCLUDE_PACKVG} ]]; then
          ${CP} ${EXCLUDE_PACKVG} ${EXCLUDE_PACKVG}.nim_save.$$ ||
               err_from_cmd "${CP} ${EXCLUDE_PACKVG} ${EXCLUDE_PACKVG}.nim_save.$$"
     fi


     # set up access to the exclude file
     if [[ ${exclude_files} = ?*:?* ]]; then
          # mount remote exclude file
          nim_mount ${exclude_files}
          exclude_access=${access_pnt}
     else
          # exclude file is local
          exclude_access=${exclude_files}
     fi

    # just to be extra safe, in the case that the exclude_files
    # resource is local AND already meets the name/location that
    # savevg would use
    if [[ "${exclude_access}" != "${EXCLUDE_VG}" ]]
    then
        echo ${exclude_files} > /tmp/.nim_pack
        LANG=C ${EGREP} exclude_packing /tmp/.nim_pack
        RC=$?
        if [ "$RC" -ne 0 ]
        then
        # put the exclude file in /etc/exclude.${VG}
        # so that the savevg command will read it
        ${CP} ${exclude_access} ${EXCLUDE_VG} ||
                err_from_cmd "${CP} ${exclude_access} ${EXCLUDE_ROOTVG}"
        else
        ${CP} ${exclude_access} ${EXCLUDE_PACKVG} ||
                err_from_cmd "${CP} ${exclude_access} ${EXCLUDE_PACKVG}"
        fi
        ${RM} /tmp/.nim_pack
    fi

     nim_unmount ${access_pnt}

    # add the -e flag since we have successfully copied the exclude_files resource
    savevg_flags=e${savevg_flags}

} # end process_exclude_file

#---------------------------- format_savevg_flags ----------------------------
#
# NAME: format_savevg_flags 
#
# FUNCTION:  The options to savevg_flags are "AVXab:ePimpvZ".  This function
#            makes sure that the flags are in the correct format to 
#            pass to savevg no matter what order they are entered 
#			 by the user.
#
# EXECUTION ENVIRONMENT:
#
# NOTES:
# 		The -b option to savevg takes a numeric argument.
# 		Make sure that there is a space after the -b flag
# 		and it's argument.  If not, savevg will take everything
# 		after the -b flag as the block_size argument.
#
# RECOVERY OPERATION:
#
# DATA STRUCTURES:
#       parameters:
#       global:
#
# RETURNS: 
#
# OUTPUT:
#-------------------------------------------------------------------------------
function format_savevg_flags {

     in_flags=${1}
     fmt_flags=""
     i=1

     # get number of characters in flags 
     num_flags=`${ECHO} ${in_flags} | ${WC} -c | ${AWK} '{print $1}'`

     # Loop through the flags and make sure that there is
     # a space after the -b flag and it's argument.
     while [[ $i -lt $num_flags ]]; do

          flag=`${ECHO} ${in_flags} | ${CUT} -c $i`

          # if digit print and set digit flag 
          if [[ "${flag}" = +([0-9]) ]]; then
               fmt_flags=${fmt_flags}`${ECHO} "${flag}\c"`
               isdigit=true

          # if not digit print savevg flag
          elif [ ! "$isdigit" ]; then
               fmt_flags=${fmt_flags}`${ECHO} "${flag}\c"`
          else
               # last thing printed was a digit so
               # print space before next savevg flag
               isdigit=

               # if space just print it
               if [[ "${flag}" = " " ]]; then
       	            fmt_flags=${fmt_flags}`${ECHO} " \c"`

               # if flag print with hyphen
               else
      	            fmt_flags=${fmt_flags}`${ECHO} " -${flag}\c"`
               fi

          fi

          let i=i+1

     done

     ${ECHO} ${fmt_flags}

} # end format_savevg_flags

#---------------------------- c_mksavevg_cleanup ----------------------------
#
# NAME: c_mksavevg_cleanup 
#
# FUNCTION:  Replace the exclude file as it was before we ran
#            and call the generic cleanup function to make sure
#            everything gets unmounted.
#
# EXECUTION ENVIRONMENT:
#
# NOTES:
#       calls error on failure
#
# RECOVERY OPERATION:
#
# DATA STRUCTURES:
#       parameters:
#       global:
#
# RETURNS: 
#
# OUTPUT:
#-------------------------------------------------------------------------------
function c_mksavevg_cleanup {

     # copy the old exclude file back
     if [[ -f ${EXCLUDE_VG}.nim_save.$$ ]]; then
          ${MV} ${EXCLUDE_VG}.nim_save.$$ ${EXCLUDE_VG} > /dev/null 2>&1 || 
               err_from_cmd "${MV} ${EXCLUDE_VG}.nim_save.$$ ${EXCLUDE_VG}"
     else
          # remove the exclude file we installed
          if [ -n "${exclude_files}" ] && [ -f "${EXCLUDE_VG}" ]; then
               ${RM} ${EXCLUDE_VG} || warning_from_cmd "${RM} ${EXCLUDE_VG}"
          fi
     fi

    # copy the old exclude packing file back
    if [ -f ${EXCLUDE_PACKVG}.nim_save.$$ ]
    then
        ${MV} ${EXCLUDE_PACKVG}.nim_save.$$ ${EXCLUDE_PACKVG} > /dev/null 2>&1 ||
            err_from_cmd "${MV} ${EXCLUDE_PACKVG}.nim_save.$$ ${EXCLUDE_PACKVG}"
    else
        # remove the exclude file we installed
        if [ -n "${exclude_files}" ] && [ -f "${EXCLUDE_PACKVG}" ]
        then
            ${RM} ${EXCLUDE_PACKVG} ||
            warning_from_cmd "${RM} ${EXCLUDE_PACKVG}"
        fi
    fi

     # clean up the mount point if we mounted the savevg write directory over.
     # we need to do this here since if the unmount fails the entire directory 
     # might get erased.  
     if [[ -n "${savevg_mnt_dir}" ]]; then
          nim_unmount ${savevg_mnt_dir}

          # Check to see if the unmount failed.  If it failed then
          # leave the temporary directory there.  We don't want to 
          # inadvertently erase extra files.
          ${MOUNT} | ${EGREP} -q ${savevg_mnt_dir}
          if [[ $? -ne 0 ]]; then
                ${RM} -r ${savevg_mnt_dir}
          fi
     fi

     cleanup

} # end c_mksavevg_cleanup

#-------------------------------  MAIN  ---------------------------------------

# signal processing
trap c_mksavevg_cleanup 0
trap err_signal 1 2 11 15

# NIM initialization
nim_init

# set parameters from command line
while getopts :a:qv c
do
     case ${c} in

          a)  # validate the attr ass
              parse_attr_ass "${OPTARG}"

              # include the assignment for use in this environment
              eval ${variable}=\"${value}\"
              ;;

          q)  # show attr info
              cmd_what
              exit 0
              ;;

          v)  # verbose mode (for debugging)
              set -x
              for i in $(typeset +f)
              do
                   typeset -ft $i
              done
              ;;

          \?) # unknown option
              error ${ERR_BAD_OPT} ${OPTARG}
              ;;
     esac
done

# check for missing attrs
ck_attrs

# check that bos.sysmgt.sysbr (System Backup and BOS Install Utilities) is installed 
STATE=`${LSLPP} -qcIL bos.sysmgt.sysbr 2>/dev/null | ${CUT} -d":" -f6`  

if [[ "${STATE}" != "C" ]]; then
     nim_name=`${GREP} NIM_NAME ${NIMINFO} | ${CUT} -d"=" -f2`
     error ${ERR_NO_SYSBR} ${nim_name}   
fi

# handle exclude file resource 
EXCLUDE_VG=/etc/exclude.${volume_group:-rootvg}
EXCLUDE_PACKVG=/etc/exclude_packing.${volume_group:-rootvg}
[ -n "${exclude_files}" ] && process_exclude_file

# is this machine the image server?
if [[ -n "${server}" ]]; then
     # mount the remote location 
 
     # going to create a tmp directory at "/tmp/$$.mnt"
     # look for lowest, unused seqno
     typeset -i idx=0
     while (( ${idx} < ${MAX_MNT} ))
     do
          savevg_mnt_dir="/tmp/$$.mnt${idx}"
          [[ ! -d ${savevg_mnt_dir} ]] && break
          let idx=idx+1
     done
     (( ${idx} >= ${MAX_MNT} )) && error ${ERR_GEN_SEQNO} /tmp/$$.mnt

     ${MKDIR} ${savevg_mnt_dir} 2>${ERR} || err_from_cmd ${MKDIR}

     nim_mount ${server}:`${DIRNAME} ${location}` ${savevg_mnt_dir}
     location_access=${access_pnt}/`${BASENAME} ${location}`
else
     # location is local to this machine
     location_access=${location}
fi

# check savevg_flags format and content
if [[ -n "${savevg_flags}" ]]; then
     # put hyphens in front of all flags
     savevg_flags=$( ck_installp_flags "${savevg_flags}" )

     # make sure all flags are valid
     /usr/bin/getopt "AVXab:ePimpvZTr" ${savevg_flags} > /dev/null 2>&1 || 
          error ${ERR_BAD_SAVEVG_FLAG}

	  # verify that flags are in correct format
	  savevg_flags=$( format_savevg_flags "${savevg_flags}" )
fi

# do space checking only for savevg image
if [[ `expr "${savevg_flags}" : ".*r"` -eq 0 ]]; then
     check_space
else
    # if just previewing space for -r then exit 
    if [ "${size_preview}" = "yes" ]
    then
        exit 0
    fi
fi

# call savevg to create the image
${SAVEVG} -f ${location_access} ${savevg_flags} ${volume_group:-rootvg} 

# exit with return code from savevg
RC=$?

# all done
exit $RC
