#!/bin/sh
#
# Copyright 02/22/02 Sun Microsystems, Inc. All Rights Reserved.
#
# pragma ident  "@(#)es-backup	1.13 02/02/22 Sun Microsystems"
#

print_usage() {
    /bin/echo ""
    eval /bin/echo `/usr/bin/gettext 'Usage: $PROGNAME [-y] [-c|-e]'`
    /usr/bin/gettext "     -e : Use export backup\n"
    /usr/bin/gettext "     -c : Use cold backup\n"
    /usr/bin/gettext "     -y : Use default answer[y]\n"
    /bin/echo ""
}

print_failure_and_exit() {
    echolog "Sun Management Center data backup failed. Exiting ..."
    echolog 'Please check $2 for details.' "$LOGFILE"

    # Just in case DB is still up
    shutdown_db

    echolog ''
    exit $1
}

verify_db_files () {
    db_block_size=`/usr/bin/grep db_block_size $ORA_INITFILE | /usr/bin/cut -f2 -d=`
    dbf_files=`/usr/bin/find $ESTBL_BASEDIR/$ORA_DATADIR -name \*.dbf`
    tmplog=/tmp/dbv_log.$$

    status=0
    for file in $dbf_files ; do
        /usr/bin/rm -f $tmplog  2>/dev/null 1>&2
        $DBVERIFY file=$file blocksize=$db_block_size >$tmplog 2>&1

        scan_dbv_log $tmplog $file
        if [ $? -ne 0 ] ; then
            echolog 'Verification of database failed.'
            status=1
            break
        fi
    done

    /usr/bin/rm -f $tmplog 2>/dev/null 1>&2
    return $status
}

create_backup_meta() {
    if [ -f $BACKUPDIR/$METAFILE ] ; then
        /usr/bin/rm $BACKUPDIR/$METAFILE
    fi

    BACKUP_DATE=`date`

    /usr/bin/cat<<EOF > $BACKUPDIR/$METAFILE
#
# Sun Management Center backup meta file
#
# THIS FILE IS CREATED AND READ AUTOMATICALLY. DO NOT EDIT MANUALLY.
#

BACKUP_FORMAT=$BACKUP_FORMAT
SUNMC_VERSION=$SUNMC_VERSION
ESCOM_BASEDIR=$ESCOM_BASEDIR
ESTBL_BASEDIR=$ESTBL_BASEDIR
HOSTNAME=`hostname`
COMPRESS_SCHEME=$COMPRESS_SCHEME
COMPRESS_CMD=$COMPRESS_CMD
COMPRESS_EXT=$COMPRESS_EXT
ZCAT_CMD=$ZCAT_CMD
PRM_VERSION=$PRM_VERSION
DB_CHUNKS=$DB_CHUNKS
BACKUP_DATE=$BACKUP_DATE

EOF

}

#
# Retrieve list of Oracle data files that need to be backed up
# Note: this only includes data files; the caller should make sure other
# important files like initfiles and controlfiles are included in the backup.
#
get_dbfile_list () {
    sqlout=/tmp/sqlout.$$
    /usr/bin/rm -f $sqlout

    $SQLPLUS /nolog <<EOF >>$LOGFILE 2>&1
	connect $SYS_LOGIN/$SYS_PASSWD
	set pagesize 0
	set heading off
	set echo off
	set feedback off
	set termout off
	spool $sqlout
	select file_name from dba_data_files;
EOF

    if [ $? -ne 0 ] ; then
        echolog 'Unable to retrieve list of SunMC database files'
        status=1
    else
        DBFILE_LIST=`/usr/bin/grep '^/' $sqlout`
        status=0
    fi

    /usr/bin/rm -f $sqlout
    return $status
}

# count_fragments $basename
# Find out how many fragments /usr/bin/split generated.
# If only one, drop the .aaa suffix.
# Initializes DB_CHUNKS to be used later when creating the metafile.
count_fragments () {
    basename=$1

    if [ ! -f ${basename}.aab ] ; then
        /usr/bin/mv ${basename}.aaa $basename
        DB_CHUNKS=1
    else
        DB_CHUNKS=`/usr/bin/ls -1 ${basename}.* | /usr/bin/wc -l | /usr/bin/awk '{print $1}' `
    fi
}

#
# Tar and compress the database files
#
#
cold_backup() {
    DB_CHUNKS=1

    start_db
    echolog 'Retrieving list of files to be backed up...'
    get_dbfile_list
    if [ $? -ne 0 ] ; then
        print_failure_and_exit 1
    fi

    shutdown_db

    #
    # Validate existing database
    #
    echolog 'Checking existing Sun Management Center database for errors'
    verify_db_files
    if [ $? -ne 0 ] ; then
        print_failure_and_exit 1
    fi
    echolog 'Database check successful.'

    echolog 'Performing cold backup. Please wait ...'
    cd $ESTBL_BASEDIR

    # Compose list of all files that need to be backed up
    filelist="$ORA_EVENTDIR $ORA_CTRLFILES $DBFILE_LIST"

    bakfile_base="$BACKUPDIR/${DBBAKFILE}.${COMPRESS_EXT}"
    /usr/bin/tar cf - $filelist | $COMPRESS_CMD | /usr/bin/split -b ${CHUNK_SIZE}m -a 3 - ${bakfile_base}.

    if [ $? -ne 0 ] ; then
        echolog 'Cannot make backup copy of database.'
        print_failure_and_exit 1
    fi

    count_fragments $bakfile_base

    return 0
}

#
# Scan the database log for errors
#
ora_scan_export_log ()
{
    file=$1

    error=`/usr/bin/egrep -n "ORA-|PLS-|SP2-|EXP-" $file | /usr/bin/egrep -v "EXP-00067" | /usr/bin/head -1 | /usr/bin/cut -d: -f1`
    if [ "$error" -gt 0 ]; then
        numlines=`/usr/bin/wc -l $file | /usr/bin/awk '{print $1}'`
        offset=`/usr/bin/expr $numlines - $error`

        echolog "Oracle Error: Start..." 
        /usr/bin/tail -${offset} $file >>$LOGFILE
        echolog "Oracle Error: End"
        return 1
    fi

    return 0
}

#
# Find out how large the database is. This is computed by querying Oracle
# itself to tell us how many KBs of data we have. Based on this, we can later
# compute a rough estimate of how much space we will need for the backup.
#
# On successful exit, the total number of KBs currently being used in Oracle
# is stored in $dbsize.
#
# The caller is responsible for handling the case when we fail to compute
# this number.
#
compute_db_size() {

    start_db

    sqlout=/tmp/sqlout.$$
    /usr/bin/rm -f $sqlout

    # The following query returns the number of KB's of used space.
    # NOTE: (to check) will the numbers be different if we login as sysdba?
    # The Oracle docs say that dba_free_space reports the free space available
    # to the *current user*.

    $SQLPLUS /nolog <<EOF >>$LOGFILE 2>&1
	connect $SYS_LOGIN/$SYS_PASSWD
	set pagesize 0
	set heading off
	set echo off
	set feedback off
	spool $sqlout
	select round((totalbytes - totalfree) / 1024) from (select sum(bytes) as totalfree from dba_free_space), (select sum(bytes) as totalbytes from dba_data_files);
EOF

    if [ $? -eq 0 ] ; then
        dbsize=`/usr/bin/egrep '^[ \t]*[0-9]*[ \t]*$' $sqlout | /usr/bin/tail -1`

        if [ $? -eq 0 ] ; then
            /usr/bin/rm -f $sqlout

            if [ "$dbsize" != "" ] ; then
                /usr/bin/rm -f $sqlout
                return 0
            fi
        fi
    fi

    # Check failed.
    echolog 'Unable to compute current size of database'

    /usr/bin/rm -f $sqlout
    return 1
}

# guess_bak_size $dbsize $m $c
#	Compute a guesstimate of how much space (in KB) the backup will need.
#	The constants $m and $c are "magic" constants extrapolated from test
#	runs, and depend on various parameters such as SunMC version and
#	whether PRM is installed or not.
#
#	Guesstimate returned as $result.
guess_bak_size () {
    dbsize=$1
    m=$2
    c=$3

    result=`/bin/echo $dbsize \* $m + $c | /usr/bin/bc -l | /usr/bin/cut -f1 -d.`
}

# estimate_export_size $dbsize
#	Estimates the (compressed) database export size in KB. This is
#	basically a linear function on the given database size, adjusted
#	so that it gives a loose upper bound.
#
#	Returns result in $exportsize.
estimate_export_size() {
    dbsize=$1

    # Estimate database backup size
    if [ "$PRM_EXISTS" ] ; then
        guess_bak_size $dbsize 0.1 -640000
        if [ $? -ne 0 ] ; then
            return 1
        fi
    else
        guess_bak_size $dbsize 0.1 -19000
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    exportsize=$result
    return 0
}

# estimate_cold_size $dbsize
#	Estimates compressed cold backup (database files only) size in KB.
#
#	Returns result in $coldsize.
estimate_cold_size() {
    dbsize=$1

    if [ "$PRM_EXISTS" ] ; then
        guess_bak_size $dbsize 0.2 -1200000
        if [ $? -ne 0 ] ; then
            return 1
        fi
    else
        guess_bak_size $dbsize 0.2 18000
        if [ $? -ne 0 ] ; then
            return 1
        fi
    fi

    coldsize=$result
    return 0
}

backup_export ()
{
    start_db
    echolog 'Performing export backup. Please wait...'

    outfile=/tmp/ora_out.$$
    /usr/bin/rm -f $outfile       2>/dev/null 1>&2

    retstat=0

    #
    # Create pipe for export
    #
    pipe=/tmp/pipe.$$

    create_pipe $pipe
    if [ $? -ne 0 ]; then
        return 1
    fi

    #
    # Output from pipe will be compressed and split into 2GB fragments as
    # necessary.
    #
    bakfile_base="${BACKUPDIR}/${DUMPFILE}.${COMPRESS_EXT}"
    /usr/bin/cat $pipe | $COMPRESS_CMD | /usr/bin/split -b ${CHUNK_SIZE}m -a 3 - ${bakfile_base}. &
    pipepid=$!

    #
    # Do the export
    #
    # NOTE: potential security issue with passwords on command line
    $DBEXP ${SYS_LOGIN}/${SYS_PASSWD} full=y direct=y file=$pipe buffer=64000 2>$outfile 1>&2

    retstat=$?

    ora_scan_export_log $outfile
    if [ $? -ne 0 -o "$retstat" -ne 0 ]; then
        echolog 'Database export error' 
        retstat=2
    fi

    # If only one chunk was produced by the split, we might as well rename it
    # to a non-suffixed filename.
    count_fragments $bakfile_base

    #
    # Clean up
    #
    shutdown_db
    kill $pipepid >/dev/null 2>&1
    /usr/bin/rm -f $pipe $outfile 2>/dev/null 1>&2
    return $retstat
}

#
# Create the backup directories if necessary
#
create_backup_dir() {
    if [ ! -d $BACKUPDIR/data ] ; then
        /usr/bin/mkdir -p $BACKUPDIR/data
        if [ $? -ne 0 ] ; then
            echolog 'Failed to create backup directory $2' "$BACKUPDIR/data"
            print_failure_and_exit 1
        fi
    fi
}


backup_cfg_files() {

    filelist="$BACKUPDIR/$CFGFILE /etc/system $ESDIR/cfg $ORA_INITFILE $ORACLE_HOME/network/admin"

    if [ "$PRM_EXISTS" ] ; then
        prm_datfiles=`/usr/bin/find $ESTBL_BASEDIR/SUNWsymon/PRM -type f | /usr/bin/egrep '\.data?$'`
        filelist="$filelist $prm_datfiles"
    fi

    /usr/bin/tar cvf $filelist >> $LOGFILE 2>&1

    if [ $? -ne 0 ] ; then
        echolog 'Failed to backup cfg files. Please check $2' "$LOGFILE"
        print_failure_and_exit 1
    fi

    $COMPRESS_CMD $BACKUPDIR/$CFGFILE
    if [ $? -ne 0 ] ; then
        echolog 'Failed to compress backup file'
        print_failure_and_exit 1
    fi
}


###############################################
#
#   Main entry point
#
prog_base=`/usr/bin/basename $0`
common_cmd=`/bin/echo $0 | /usr/bin/sed s/$prog_base/es-common.sh/`
. $common_cmd

set_basedir
set_db_env
set_commondirs
NLS_LANG=AMERICAN_AMERICA.UTF8; export NLS_LANG

check_root

bak_common_cmd=`/bin/echo $0 | /usr/bin/sed s/$prog_base/es-backup-common/`
. $bak_common_cmd
set_backup_common
if [ $? -ne 0 ] ; then
    echolog 'Script initialization error'
    print_failure_and_exit 1
fi

cd "$BASEDIR"
PWD="$BASEDIR"; export PWD
PROGNAME=$0; export PROGNAME

DBEXP=$ORACLE_HOME/bin/exp

#
# Maximum export size (KB) is the maximum amount of data we shall
# export
#
# Chunk size is the maximum file size (in MB) to be created by 
# the export 
#
MAX_EXPORT_SIZE=`/usr/bin/expr 1024*2048`
CHUNK_SIZE=2048

#
# Backup formats:
#	0	Old (not actually used in metafile, but used internally for
#		pre-4.0 backups)
#	1	Cold backup.
#	2	Exported database dump file.
#
# Compression schemes:
#	multi	cfg files and DB files are compressed separately; backup
#		dir consists of meta file and data/ subdirectory with
#		compressed archive files
#	single	the entire backup archive is compressed into a single
#		compressed file. (CURRENTLY NOT IMPLEMENTED)
#
BACKUP_FORMAT=0
COMPRESS_SCHEME=multi
COMPRESS_CMD="/usr/bin/compress"
COMPRESS_EXT="Z"
ZCAT_CMD="/usr/bin/zcat"
#COMPRESS_CMD="/usr/bin/gzip"
#COMPRESS_EXT="gz"
#ZCAT_CMD="/usr/bin/gzip -dc"

#
# what should ESDIR be based on -- is there some appropriate
# package parameter?
#
ESDIR=${ESDIR-/var/opt/SUNWsymon}
BACKUPDIR=${ESDIR}/backup

#
# Parse command-line options
#
interactive=1
while [ $# -ne 0 ] ; do
    if [ $1 = "-y" ] ; then
	interactive=0
    elif [ $1 = "-e" ] ; then
        BACKUP_FORMAT=2
    elif [ $1 = "-c" ] ; then
        BACKUP_FORMAT=1
    elif [ $1 = "-d" ] ; then
        shift
	BACKUPDIR=$1
    else
        /bin/echo ""
        print_usage
        exit 1
    fi
    shift
done

umask 022

check_logfile backup
export LOGFILE

#
# Make sure that we only have one instance of es-backup/es-restore running.
#
check_process_conflicts es-backup
if [ $? -ne 0 ] ; then
    echolog 'Conflicting process found: es-backup'
    echolog 'Only one instance of es-backup should be running at a time.'
    print_failure_and_exit 1
fi

check_process_conflicts es-restore
if [ $? -ne 0 ] ; then
    echolog 'Conflicting process found: es-restore'
    echolog 'es-backup should not be run at the same time as es-restore.'
    print_failure_and_exit 1
fi


#
# Prepare backup directory
#

if [ $interactive -eq 0 ] ; then
    #
    # non-interactive mode
    #
    echolog ""
    echolog 'Shutting down Sun Management Center.'
    stop_SunMC
    if [ $? -ne 0 ]; then
        echolog 'Error stopping Sun Management Center....'
        print_failure_and_exit 1
    fi

    #
    # create backup directory if necessary
    #
    if [ ! -d $BACKUPDIR ]; then
        create_backup_dir

    #
    # ensure directory is empty
    #
    elif [ -f $BACKUPDIR/$METAFILE -o -f $BACKUPDIR/sunmc.dmp      -o \
           `/usr/bin/ls -1 $BACKUPDIR/data | /usr/bin/wc -l` -gt 0  ] ; then
        echolog 'Backup directory is not empty.'
        echolog 'Directory contents shall not be deleted in non-interactive mode. Exiting.' 
        exit 1
    fi

else 
    #
    # interactive mode
    #
    echolog ""
    echolog 'Sun Management Center must be shutdown to backup data.'
    get_input_from_user "Do you want to proceed [y/n]:"
    if [ "$answer" = "y" ] ; then
        stop_SunMC
        if [ $? -ne 0 ]; then
            echolog 'Error stopping Sun Management Center....'
            print_failure_and_exit 1
        fi
    else
        exit 0
    fi

    while [ "$backupdir_flag" = "" ]
    do
        get_input_from_user "Enter full directory path to store the backup data files[$BACKUPDIR]:"
        if [ -z "$answer" ] ; then
            #
            # use default backup directory
            #
            backupdir_flag="done"
        else
            /bin/echo "$answer" | /usr/bin/grep "^/" > /dev/null 2>&1
	    if [ $? -ne 0 ] ; then
                echolog "The backup directory path must start with /"
                continue
            fi

            /bin/echo "$answer" | /usr/bin/grep " " > /dev/null 2>&1 
            if [ $? -eq 0 ] ; then
                echolog "Invalid directory name"
                continue
            fi

            if [ "$answer" = "/" ] ; then
                echolog "Invalid directory name: /"
                continue
            fi

            backupdir_flag="done"
            BACKUPDIR="$answer"
        fi
    done

    #
    # Create backup directory, if it does not already exist
    #
    if [ ! -d "$BACKUPDIR" ] ; then
        answer=0
	echolog 'The directory $2 does not exist' "$BACKUPDIR"
	ask_user "Do you want to create it?"

        if [ $answer -eq 0 ] ; then
            echolog 'Backup directory not created. Exiting.'
	    exit 1
        fi

    #
    # Clean out any old files from backup directory
    #
    elif [ -f $BACKUPDIR/$METAFILE -o -f $BACKUPDIR/sunmc.dmp      -o \
           `/usr/bin/ls -1 $BACKUPDIR/data | /usr/bin/wc -l` -gt 0  ] ; then
        echolog 'The directory $2 contains previous backups.' "$BACKUPDIR"
        ask_user "Delete old backups?"

        if [ "$answer" -eq 1 ] ; then
            /usr/bin/rm -f $BACKUPDIR/$METAFILE
            /usr/bin/rm -f $BACKUPDIR/sunmc.dmp
            /usr/bin/rm -f $BACKUPDIR/data/*
        else
            echolog 'Exiting upon user request'
            exit 1
        fi
    fi
fi

#
# Create backup directories, if necessary
#
create_backup_dir

#
# Estimate how much space is required for backup
#
compute_db_size
if [ $? -ne 0 ] ; then
    echolog 'Unable to determine current database size.'
    echolog 'es-backup will not be able to estimate the amount of disk space required for the backup.'

    if [ "$interactive" -eq 1 ] ; then
        ask_user "Do you wish to proceed with the backup anyway?"
        if [ "$answer" -ne 1 ] ; then
            echolog 'Exiting upon user request'
            exit 1
        fi
    else
        print_failure_and_exit 1
    fi
else
    DB_SIZE=$dbsize
    echolog 'Database data size: $2 KB' "$dbsize"

    # If backup format is not determined yet, we decide by checking if the
    # database size is greater than the threshold $MAX_EXPORT_SIZE.
    if [ "$BACKUP_FORMAT" -eq 0 ] ; then
        if [ "$DB_SIZE" -gt "$MAX_EXPORT_SIZE" ] ; then
            BACKUP_FORMAT=1
        else
            BACKUP_FORMAT=2
        fi
    fi

    if   [ "$BACKUP_FORMAT" -eq 2 ] ; then
        estimate_export_size $DB_SIZE
        if [ $? -ne 0 ] ; then
            echolog 'Error while estimating database export size'
            print_failure_and_exit 1
        fi

        GUESS_BAKSIZE=$exportsize
    elif [ "$BACKUP_FORMAT" -eq 1 ] ; then
        estimate_cold_size $DB_SIZE
        if [ $? -ne 0 ] ; then
            echolog 'Error while estimating cold backup size'
            print_failure_and_exit 1
        fi

        GUESS_BAKSIZE=$coldsize
    fi

    echolog 'Estimated backup size: $2 KB' "$GUESS_BAKSIZE"

    #
    # Determine available disk space on backup partition
    # 
    availKB=`/usr/bin/df -k $BACKUPDIR | /usr/bin/grep -v "^Filesystem" | /usr/bin/awk '{print $4}'`
    if [ $? -ne 0 ]; then
        echolog "Error to computing available disk space"
        print_failure_and_exit 1 
    fi

    echolog 'Available disk space on $2: $3 KB' "$BACKUPDIR" "$availKB"

    if [ "$GUESS_BAKSIZE" -gt "$availKB" ] ; then
        echolog ''
        if [ "$interactive" -eq 1 ] ; then
            echolog 'The estimated backup size $2 KB is larger than the available space in $3.' "$GUESS_BAKSIZE" "$BACKUPDIR"
            echolog 'There may not be enough space to store the backup.'

            ask_user "Abort backup?"
            if [ "$answer" -eq 1 ] ; then
                echolog 'Exiting upon user request.'
                print_failure_and_exit 1
            fi
        else
            echolog 'Insufficient disk space in $2. Aborting.' "$BACKUPDIR"
            print_failure_and_exit 1
        fi
    fi
fi

#
# Backup Sun Management Center database
#
setup_backup_format $BACKUP_FORMAT
if [ "$BACKUP_FORMAT" -eq 1 ] ; then
    #
    # Run db_verify and perform cold backup
    #
    cold_backup

elif [ "$BACKUP_FORMAT" -eq 2 ] ; then
    #
    # Export DB data
    #
    backup_export

else
    echolog 'Unknown backup format $2' "$BACKUP_FORMAT"
    print_failure_and_exit 1
fi

#
# Backup Sun Management Center cfg files
#
backup_cfg_files

#
# Create Backup Meta File
#
create_backup_meta

echolog 'Backup is successful.'
echolog 'Please save the following Sun Management Center backup files:'
echolog ''

/usr/bin/find $BACKUPDIR -type f -print | /usr/bin/tee -a $LOGFILE

/usr/bin/sleep 5

echolog ''
echolog 'Starting Sun Management Center....'
echolog ''

start_SunMC
if [ $? -ne 0 ]; then
    echolog 'Error starting Sun Management Center....'
    exit 1
fi


exit 0
