#!/bin/bash
#< Sybase Backup Script
# Description:
#   Backup Script to DUMP Sybase Devices
# Usage:
#   Read usage() function below
#
# Exit codes:
#   0 - Success
#   1 - Output file already exists
#   2 - Usage error
#   3 - Output directory does not exist
#   4 - Threshold check failed
#   5 - Errors found after DUMP
#   6 - DUMP status could not be ascertained
#   7 - Should NOT be reached!
#   8 - Database device not found
#   9 - DB filesystem not mounted
#
# History:
#   26/04/06 - KWALDRON - Initial Version - 0.1
#   26/04/06 - KWALDRON - Exit code 8 if DB_NAME.dat not found - 0.2
#   01/05/06 - KWALDRON - Added check_mountpoint() function - 0.3

# Variable Initialisation
AUTHOR="Kevin Waldron"
YEAR_WRITTEN="2006"
VERBOSE="0"
MAJOR_VER="0"
MINOR_VER="2"
MOUNTPOINT="/foo"
VERSION="${MAJOR_VER}.${MINOR_VER}"
SYBASE_DB="FOODB"
SYBASE_DEVICE_PATH="/foo/sybase/devices"
SYBASE_SA="sa"
SYBASE_SA_PASS="passwd"
SYBASE_USER="sybase"
SYBASE_WIDTH="200"
# HTTPD_CONF="/etc/httpd/conf/httpd.conf"

# Command Initialisation
AWK="/bin/awk"
BASENAME="/bin/basename"
BC="/usr/bin/bc"
CAT="/bin/cat"
CHOWN="/bin/chown"
DATE="/bin/date"
DF="/bin/df"
ECHO="/bin/echo"
GREP="/bin/grep"
HOSTNAME="/bin/hostname"
ISQL_BINARY="/opt/sybase/OCS-12_5/bin/isql"
ISQL_OPTIONS="-U${SYBASE_SA} -P${SYBASE_SA_PASS} -S${SYBASE_DB} -w${SYBASE_WIDTH}"
ISQL="${ISQL_BINARY} ${ISQL_OPTIONS}"
LS="/bin/ls"
MOUNT="/bin/mount"
SED="/bin/sed"
SU_BINARY="/bin/su"
SU_OPTIONS="- ${SYBASE_USER} -c"
SU="${SU_BINARY} ${SU_OPTIONS}"
TAIL="/usr/bin/tail"
TRUE="/bin/true"

# Global Variables Used in Functions
# DOCUMENT_ROOT=$( ${GREP} "^DocumentRoot" ${HTTPD_CONF} | ${SED} 's/^DocumentRoot[^"]*"\([^"]*\)"$/\1/' )
THIS_PROG=$( ${BASENAME} $0 )
DAY_OF_WEEK=$( date +"%a" )
OUTPUT_DIR="/backup"

# Function Definitions

# Function:  echo_v
# Arguments: Strings to output
# Returns:   nothing
# Purpose:   For use when VERBOSE flag set - outputs to STDERR - also can be used
#            to output error messages
echo_v() {
  ${ECHO} "$@" >&2
}

# Function:  check_mountpoint
# Arguments: 1 - the mountpoint to check
# Returns:   1 on failure (filesystem not mounted)
#            0 on success
# Purpose:   Check that the filesystem holding the database devices
check_mountpoint() {
  MNTPNT="${1}"
  ${MOUNT} | ${GREP} ${MNTPNT} >/dev/null 2>&1
  [[ "$?" -eq "0" ]] && {
     return 0
  } || {
     return 1
  }
}

# Function:  create_output_filename
# Arguments: none
# Returns:   nothing
# Purpose:   Create the filename to be used for the output file
create_output_filename() {
  # First, check if the output directory exists, if not, exit
  if [ ! -d "${OUTPUT_DIR}" ]; then
     echo_v "--> Error: Output directory ${OUTPUT_DIR} does not exist!"
     exit 3
  else
     # always do this - just in case
     ${CHOWN} -R sybase:sybase ${OUTPUT_DIR}
     :
  fi
  DUMP_FILE="${OUTPUT_DIR}/${DUMP_DB}-${DAY_OF_WEEK}.dmp"
  (( VERBOSE )) && {
     echo_v "--> Output Dump Filename Formulated:"
     echo_v "<-- ${DUMP_FILE}"
  }
}

# Function:  check_output_file
# Arguments: none
# Returns:   nothing
# Purpose:   Check if output file already exists or not
check_output_file() {
 if [ ! -e "${DUMP_FILE}" ]; then
   (( VERBOSE )) && {
      echo_v "--> Output file does not exist - ok"
   }
 else
    # output the error whether we're verbose or not
    echo_v "--> Error: Output file (${DUMP_FILE}) already exists!"
    if [ ! "${FORCE}" ]; then
       echo_v "--> Use -f to ignore this message"
       echo_v "--> Exiting..."
       exit 1
    fi
 fi
}

# Function:  check_disk_usage
# Arguments: none
# Returns:   nothing
# Purpose:   Check available disk space on the backup volume, and
#            handle accordingly, exiting on error unless -f specified
check_disk_usage() {
  AVAIL=$( ${DF} -k ${OUTPUT_DIR} | ${SED} -n '$p' | ${AWK} '{print $4}' )
  (( VERBOSE )) && {
     echo_v "--> Current available space on filesystem containing ${OUTPUT_DIR}"
     echo_v "<-- ${AVAIL}Kb"
  }
  DB_DEV="${SYBASE_DEVICE_PATH}/${DUMP_DB}.dat"
  ${LS} -l ${DB_DEV} >/dev/null 2>&1
  if [ "$?" -ne "0" ]; then
     echo_v "--> Error: Cannot find Database Device for ${DUMP_DB}.dat"
     exit 8
  else
     (( VERBOSE )) && {
        echo_v "--> Database Device ${DB_DEV} Found OK"
        echo_v "<-- $( ${LS} -l ${DB_DEV} )"
     }
     # The following calculates the current DB .dat file size in Kb
     DB_DEV_SIZE=$( ${ECHO} "`${LS} -l ${DB_DEV} | ${AWK} '{print $5}'` / 1024" | ${BC} )
     (( VERBOSE )) && {
        echo_v "--> Database Device Size in kilobytes"
        echo_v "<-- ${DB_DEV_SIZE}Kb"
     }
     # Check that at least the size of the device is available, although with
     # compression, and the fact that the entire device will not likely be used up
     # this should be fine
     MULTIPLIER="1"
     THRESHOLD_SIZE=$( ${ECHO} "${DB_DEV_SIZE} * ${MULTIPLIER}" | ${BC} )
     (( VERBOSE )) && {
        echo_v "--> Threshold for Available Space"
        echo_v "<-- ${THRESHOLD_SIZE}Kb"
     }
     if [ "${THRESHOLD_SIZE}" -gt "${AVAIL}" ]; then
        echo_v "--> Error: Not enough space available on Dump Device for backup!"
        if [ ! "${FORCE}" ]; then
           echo_v "--> Use -f to ignore this message"
           echo_v "--> Exiting..."
           exit 4
        fi
     fi
  fi
}

# Function:  dump_database
# Arguments: none
# Returns:   nothing
# Purpose:   DUMP DATABASE to our specified ${DUMP_FILE}
dump_database() {
  COMPRESSION_LEVEL="9"
  (( VERBOSE )) && {
  echo_v "--> Dumping database ${DUMP_DB} to ${DUMP_FILE}"
  echo_v "<-- $(date)"
  ${SU} "
     ${ISQL} <<EndOfSQL
USE master
GO
DUMP DATABASE ${DUMP_DB} TO \"compress::${COMPRESSION_LEVEL}::${DUMP_FILE}\"
GO
QUIT
EndOfSQL"
  } || {
  echo_v "--> Dumping database ${DUMP_DB} to ${DUMP_FILE}"
  echo_v "<-- $(date)"
  ${SU} "
     ${ISQL} <<EndOfSQL >/dev/null 2>&1
USE master
GO
DUMP DATABASE ${DUMP_DB} TO \"compress::${COMPRESSION_LEVEL}::${DUMP_FILE}\"
GO
QUIT
EndOfSQL"
  }
  # We'll want to output these messages whether verbose or not
  ${TAIL} -n 3 ${SYBASE_DEVICE_PATH}/backupserver.log | ${GREP} -i "error" >/dev/null 2>&1
  if [ "$?" -eq "0" ]; then
     echo_v "--> Found Errors in Backup Log - DUMP may have failed!"
     echo_v "--> Check ${SYBASE_DEVICE_PATH}/backupserver.log"
     echo_v "<-- $(date)"
     exit 5
  else
     ${TAIL} -n 3 ${SYBASE_DEVICE_PATH}/backupserver.log | ${GREP} -i "DUMP is complete" >/dev/null 2>&1
     if [ "$?" -eq "0" ]; then
        echo_v "--> Database DUMP of ${DUMP_DB} completed successfully!"
        echo_v "<-- $(date)"
        exit 0
     else
        echo_v "--> DUMP status could not be determined!"
        echo_v "--> Check ${SYBASE_DEVICE_PATH}/backupserver.log"
        echo_v "<-- $(date)"
        exit 6
     fi
  fi
}

# Function:  usage
# Arguments: none
# Returns:   nothing
# Purpose:   Display brief usage message to STDERR
usage() {
  ${ECHO} "Usage: ${THIS_PROG} [-v][-f][-h] dump_db
  -f           Force
  -v           Verbose Mode
  -h           Display This Usage Message
  dump_db      Sybase DB to Dump" >&2
}

# Argument Processing - This is not as thorough as it could be,
# but as this script is not intended to be called by anything
# other than cron, simple argument checking will suffice
if [ "$#" -lt "1" ]; then       # 1 argument, dump_db, mandatory
 usage
 exit 2
fi
while [ ${TRUE} ]; do
 case $1 in
     -f)  FORCE="1"
          shift
          ;;
     -v)  VERBOSE="1"
          shift
          ;;
     -h)  usage
          exit 0
          ;;
     *)   break
          ;;
  esac
done
# OK, now, if there is only one argument, proceed
if [ "$#" -ne "1" ]; then
 usage
 exit 2
else
 DUMP_DB="$1"
fi
(( VERBOSE )) && {
 echo_v "${THIS_PROG} - Version: ${VERSION}"
 echo_v "Copyright ${YEAR_WRITTEN} ${AUTHOR}"
}
(( VERBOSE )) && {
  echo_v "--> Verbose mode set"
}
(( FORCE && VERBOSE )) && {
  echo_v "--> Force mode set"
}
(( VERBOSE )) && {
 echo_v "--> We will be dumping database:"
 echo_v "<-- ${DUMP_DB}"
}

# Now we need to check if the output file already exists - if it does, we will
# not overwrite it - this may be changed at a later date. We will perform a 7
# day dump rotation.
create_output_filename
check_output_file
check_mountpoint "${MOUNTPOINT}"
echo_v "--> Checking mountpoint ${MOUNTPOINT}"
check_mountpoint "${MOUNTPOINT}"
[[ "$?" -eq "0" ]] && {
 echo_v "--> Mountpoint exists and is mounted"
} || {
 echo_v "--> Error: No filesystem mounted on ${MOUNTPOINT}!"
 exit 9
}

# Perform a check of the available disk capacity on /backup
check_disk_usage

# Time to dump
dump_database

# Not reached!
exit 7