#!/bin/bash
#< BCP data out of specified Sybase tables
# Description:
#   Script to bcp data out of specified tables, appending
#   to already existing dump files
#
# Usage:
#   See usage() function below
#
# Requirements:
#   Needs configuration file (specified by ${CONFIG_FILE})
#   Format of configuration file
#      dbname.owner.tablename
#      Inline, whole-line comments, and whitespace, allowed
#
# Exit codes:
#   0  -  Success
#   1  -  Invalid synatax
#   2  -  Not user "sybase"
#   3  -  Configuration file does not exist
#   4  -  No configuration options to process
#   5  -  Gzip operation failed
#   6  -  Filesystem capacity check failed
#   7  -  DB filesystem not mounted
#
# History:
#   26/04/06 - KWALDRON - Initial Version - 0.1
#   27/04/06 - KWALDRON - Added GZIP code - 0.2
#                         Added Disk Space Check
#   01/05/06 - KWALDRON - Check that      - 0.3
#                         filesystem is mounted
#   10/05/06 - KWALDRON - Added action to - 0.4
#                         unix2dos files

# Command initialisation
AWK="/bin/awk"
BASENAME="/bin/basename"
BCP="/opt/sybase/OCS-12_5/bin/bcp"
CAT="/bin/cat"
DATE="/bin/date"
DF="/bin/df"
ECHO="/bin/echo"
GREP="/bin/grep"
GZIP="/bin/gzip"
ID="/usr/bin/id"
MOUNT="/bin/mount"
RM="/bin/rm"
SED="/bin/sed"
SEQ="/usr/bin/seq"
TR="/usr/bin/tr"
UNIX2DOS="/usr/bin/unix2dos"

# Variable initialisation
AUTHOR="Kevin Waldron"
CONFIG_FILE="/usr/local/etc/dump_tables.conf"
MAJOR_VER="0"
MAX_THRESHOLD="40"              # If ${OUTPUT_DIR} FS greater than ${MAX_THRESHOLD} % used, do not proceed.
MINOR_VER="4"
MOUNTPOINT="/foo"
OUTPUT_DELIM="|"
OUTPUT_DIR="/backup"
OUTPUT_RS="\\n"
SYBASE_DB="FOODB"
SYBASE_USER="sa"
SYBASE_PASS="passwd"
TEMP_DIR="/var/tmp"
TEMP_FILENAME="${TEMP_DIR}/bcp.$$"
THIS_PROG=$( ${BASENAME} $0 )
VERBOSE=0
VERSION="${MAJOR_VER}.${MINOR_VER}"
YEAR_WRITTEN="2006"

# Function definitions

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

# Function:  usage
# Arguments: none
# Returns:   nothing
# Purpose:   Display brief usage message
usage() {
  echo_stderr "${THIS_PROG} [-v|-h]
  -v  Verbose
  -h  Print this help message"
}

# Function:  check_config_file_exists
# Arguments: none - expects CONFIG_FILE variable to be set
# Returns:   0 - if file exists
#            1 - if file does not exist or cannot be read
# Purpose:   Check that configuration file exists and is readable
check_config_file_exists() {
  if [ ! -e "${CONFIG_FILE}" ]; then
     return 1
  else
     return 0
  fi
}

# Function:  check_uid
# Arguments: none
# Returns:   0 - if we are user "sybase"
#            1 - if we're not
# Purpose:   Check that we are running the script as user "sybase"
check_uid() {
  MY_USERNAME=$( ${GREP} "^[^:]*:[^:]*:$( ${ID} -u ):.*$" /etc/passwd | ${AWK} -vFS=':' '{print $1}' )
  if [ "${MY_USERNAME}" != "sybase" ]; then
     return 1
  else
     return 0
  fi
}

# 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:  check_disk_space
# Arguments: 1 - the filesystem to check
# Returns:   1 on failure (i.e. usage greater than ${MAX_THRESHOLD}
#            0 on success
# Purpose:   Check that disk usage does not exceed specified threshold on dump filesystem
check_disk_space() {
  FILESYSTEM="$1"
  CAPACITY_USED=$( ${DF} ${FILESYSTEM} | ${SED} -n '$p' | ${AWK} '{print $5}' | ${TR} -d '%' )
  (( VERBOSE )) && echo_stderr "<-- Capacity on ${FILESYSTEM} [${CAPACITY_USED}%]"
  if [ "${CAPACITY_USED}" -gt "${MAX_THRESHOLD}" ]; then
     return 1
  else
     return 0
  fi
}

# Function:  print_timestamp
# Arguments: none
# Returns:   nothing
# Purpose:   Print simple timestamp to STDERR
print_timestamp() {
  echo_stderr "<-- $( ${DATE} )"
}

# Function:  parse_input_file
# Arguments: none
# Returns:   nothing
# Purpose:   Parses input file. Removes comments and blank lines. Checks for correct
#            number of properly delimited fields. Assigns verified lines to array.
parse_input_file() {
   COUNTER=0
   oldIFS="${IFS}"
   IFS='
'
   for line in $(< ${CONFIG_FILE} ); do
      # ignore comments
      [[ $( ${ECHO} "${line}" | ${GREP} "^#" ) ]] && continue
      # ignore blank lines
      [[ $( ${ECHO} "${line}" | ${GREP} "^[    ]*$" ) ]] && continue
      # remove trailing comments
      line=$( ${ECHO} "${line}" | ${SED} 's/^\([^#]*\)#.*$/\1/g' )
      # remove whitespace
      line=$( ${ECHO} "${line}" | ${SED} 's/[  ]//g' )
      # check for correct syntax of remaining lines
      if [ $( ${ECHO} "${line}" | ${AWK} -vFS='.' '{print NF}' ) -ne "3" ]; then
         (( VERBOSE )) && {
            echo_stderr "--> Incorrect number of fields in line:"
            echo_stderr "<-- ${line}"
            echo_stderr "--> Ignoring line above!"
         }
         continue
      fi
      CONFIG_ENTRIES[${COUNTER}]="${line}"
      (( COUNTER = COUNTER + 1 ))
   done
   IFS="${oldIFS}"
}

# Function:  parse_input_file
# Arguments: none
# Returns:   nothing
# Purpose:   Process the CONFIG_ENTRIES array, performing the actual bcp operation
#            where appropriate. Append onto monthly archive file
# ToDo:      Perform archiving/gzipping - need to run a few times first to estimate
#            capacity requirements, etc
process_entries() {
  (( VERBOSE )) && {
     echo_stderr "--> Temporary output file is"
     echo_stderr "<-- ${TEMP_FILENAME}"
  }
  if [ "$(( ${#CONFIG_ENTRIES[@]} ))" -eq "0" ]; then
     echo_stderr "--> Error: No configuration entries to process!"
     echo_stderr "--> ${TEMP_FILENAME} not created. No action taken. Exiting..."
     print_timestamp
     exit 4
  fi
  for i in $( seq 0 $(( ${#CONFIG_ENTRIES[@]} - 1 )) ); do
     (( VERBOSE )) && {
        echo_stderr "--> Processing entry:"
        echo_stderr "<-- CONFIG_ENTRIES[${i}] is ${CONFIG_ENTRIES[${i}]}"
     }
     RESULT=$( ${BCP} ${CONFIG_ENTRIES[${i}]} out ${TEMP_FILENAME} -U${SYBASE_USER} -P${SYBASE_PASS} -S${SYBASE_DB} -c -t"${OUTPUT_DELIM}" -r ${OUTPUT_RS} )
     (( VERBOSE )) && {
        echo_stderr "--> BCP RESULT:"
        echo_stderr "${RESULT}"
     }
     ${ECHO} "${RESULT}" | ${GREP} -i "unexpected" >/dev/null 2>&1
     if [ "$?" -eq "0" ]; then
        echo_stderr "--> Warning: Unexpected output from BCP command. Check configuration file for entry"
        echo_stderr "<-- ${CONFIG_ENTRIES[${i}]}"
        (( VERBOSE )) && {
           echo_stderr "--> Are database, owner, and tablename specified correctly?"
        }
     fi
     ${ECHO} "${RESULT}" | ${GREP} -i "rows copied" >/dev/null 2>&1
     if [ "$?" -eq "0" ]; then
        echo_stderr "--> BCP successful for entry"
        echo_stderr "<-- ${CONFIG_ENTRIES[${i}]}"
        oldIFS="${IFS}"
        IFS="."
        set -- ${CONFIG_ENTRIES[${i}]}
        DBNAME=$1
        TABLENAME=$3
        IFS="${oldIFS}"
        DAY=$( ${DATE} +%d )
        MONTH=$( ${DATE} +%b )
        YEAR=$( ${DATE} +%Y )
        APPEND_FILENAME="${OUTPUT_DIR}/${DBNAME}-${TABLENAME}-${DAY}-${MONTH}-${YEAR}.bcp"
        if [ -e "${APPEND_FILENAME}.gz" ]; then
           ${GZIP} -d ${APPEND_FILENAME}.gz
        fi
        (( VERBOSE )) && echo_stderr "--> Appending to ${APPEND_FILENAME}"
        ${CAT} ${TEMP_FILENAME} >> ${APPEND_FILENAME}
        (( VERBOSE )) && echo_stderr "--> Removing ${TEMP_FILENAME}"
        ${RM} ${TEMP_FILENAME}
        (( VERBOSE )) && echo_stderr "--> Converting ${APPEND_FILENAME}"
        ${UNIX2DOS} ${APPEND_FILENAME}
        (( VERBOSE )) && echo_stderr "--> Gzipping ${APPEND_FILENAME}"
        ${GZIP} ${APPEND_FILENAME} >/dev/null 2>&1
        if [ "$?" -eq "0" ]; then
           (( VERBOSE )) && echo_stderr "--> Gzipped to ${APPEND_FILENAME}.gz"
        else
           echo_stderr "--> Error: Gzip operation failed!"
           exit 5
        fi
     fi
  done
}

#
# First, check arguments
#
if [ "$#" -gt "1" ]; then
  usage
  exit 1
elif [ "$#" -eq "1" ]; then
  case $1 in
     -v)  VERBOSE=1
          ;;
     -h)  usage
          exit 1
          ;;
     *)   usage
          exit 1
          ;;
  esac
fi
(( VERBOSE )) && {
 echo_stderr "${THIS_PROG} - Version: ${VERSION}"
 echo_stderr "Copyright ${YEAR_WRITTEN} ${AUTHOR}"
}

check_uid
if [ "$?" -ne "0" ]; then
  echo_stderr "<-- You must be sybase to run this script"
  exit 2
fi

check_config_file_exists
if [ "$?" -ne "0" ]; then
  echo_stderr "<-- Configuration file ${CONFIG_FILE} does not exist or is not readable"
 exit 3
fi

echo_stderr "--> Checking mountpoint ${MOUNTPOINT}"
print_timestamp
check_mountpoint "${MOUNTPOINT}"
[[ "$?" -eq "0" ]] && {
 echo_stderr "--> Mountpoint exists and is mounted"
 print_timestamp
} || {
 echo_stderr "--> Error: No filesystem mounted on ${MOUNTPOINT}!"
 print_timestamp
 exit 7
}

echo_stderr "--> Checking capacity on ${OUTPUT_DIR}"
print_timestamp
check_disk_space "${OUTPUT_DIR}"
if [ "$?" -ne "0" ]; then
  echo_stderr "<-- Capacity check failed on filesystem housing ${OUTPUT_DIR}"
  exit 6
fi
echo_stderr "--> Capacity check complete"
print_timestamp

echo_stderr "--> Input file parsing commenced at:"
print_timestamp
parse_input_file
echo_stderr "--> Input file parsing finished at:"
print_timestamp

echo_stderr "--> Entry processing commenced at:"
print_timestamp
process_entries
echo_stderr "--> Entry proecessing finished at:"
print_timestamp

# All good
exit 0