#!/usr/bin/ksh

# MANUAL ENTRY BEGIN
#--------------------------------------------------------------------------- --

# NAME
#      alscan.sh - Scan the ORACLE alert-files
# 
# SYNTAX
#      alscan.sh [s] [-f filespec] [-m mailfile] [mailuser...]
#      alscan.sh -h
# 
# DESCRIPTION
#      Alscan scans the ORACLE alert-file of each ORACLE_SID and issues any
#      messages therein, beginning with "ORA-" along with the date of occurence,
#      by email to all 'mailuser's that are given as argument.
#      If no arguments are supplied, the message is printed to standard output.
#
#      Messages are gathered up to the current date. The next call to 'alscan'
#      will print all messages that occurred since that previous call. 
# 
#      Automatic initialisation for new alert-file cycle will be provided, if
#      the alert-file was switched, and the old date couldn't be found in the
#      alert-file.
#
#      If a file 'alscan.conf_$ORACLE_SID' is found, respectivly 'alscan.conf',
#      that file is supposed to contain the patterns the alert-file should be
#      scanned for (instead of "ORA-" described above) or respectivly be filtered
#      out.
#      For suppressing lines of the alert-file(s):
#        the first column in this file should contain '-', followed by a pattern
#        string in awk's 'match' syntax, separatd from '-' by a white space
#        character.
#      For printing matching lines of the alert-file(s):
#        the first column in this file should contain '+', followed by a pattern
#        string in awk's 'match' syntax, separatd from '+' by a white space
#        character.
#      All other lines in this 'pattern-file', not beginning with '-' or '+',
#      are considered as comments and are ignored.
#
#
# OPTIONS
#      -f filespec
#            Use 'filespec' to specify the alert-file. If 'filespec' contains
#            slash (/), alert-file will be 'filespec' with the entire path.
#            If 'filespec' contains no slash but period (.), the directory the
#            alert-file is supposed to be '$ORACLE_BASE/admin/$ORACLE_SID/bdump',
#            and the alert-filename is that of 'filespec'.
#            If 'filespec' contains neither slash or period, 'filespec' will
#            substitute the extension 'log' in:
#            '$ORACLE_BASE/admin/$ORACLE_SID/bdump/alert_$ORACLE_SID.log'
#            Notice:
#            Each time 'filespec' will change, 'alscan' automatically initializes
#            a new cycle.
#
#      -h    Get help (these manual pages).
#
#      -s    Status report of the file scanned and the last date of access.
#
#      -m    Filename with email addresses. Each address in a separate line.
#
# DIAGNOSTICS
#      'alscan' will return the exit stati:
#       2          on invokation syntax errors,
#       1          on abnormal termination,
#       0          on error-free operation,
#      -1 (255)    if any error occurred in scanning one of the alert-files,
#      -2 (254)    if one of the alert-files wasn't readable,
#      -3 (253)    if one of the resulting messages could't be delivered.
#
# FILES
#      $HOME/ALscan              file containing last date of alert-file read
#      $HOME/ALmail              (scratch) file containing results, sent by mail
#      $HOME/ALconf.$ORACLE_SID  scratch file containing default match pattern 
#      $HOME/alscan.conf[_$ORACLE_SID]        default pattern-file
#      /opt/oracle               for $ORACLE_BASE  defined internal,
#                                      should be replaced by environment
#      $ORACLE_BASE/admin/$ORACLE_SID/bdump
#                                directory supposed to contain alert-file
#      /var/opt/oracle/oratab
#        or                      ORATAB file containing ORACLE_SIDs definitions
#      /etc/oratab
# 
# EDITORIAL
#      Copyright by MuniQSoft GmbH
#      Author       Hans Wesnitzer

#--------------------------------------------------------------------------- --
# MANUAL ENTRY END


# ----------------------------------------------------|
#   These parameters have to be set by customer:      |
#                                                     |
ORACLE_BASE=/opt/oracle 
batch_profile=$HOME/.oraenv
def_patternfile=$HOME/alscan.conf
#                                                     |
# ----------------------------------------------------|

export ORACLE_BASE  BATCH_PROFILE

# ... Predefinitions
#
typeset -i stat=0 initialize=0 report=0 demo=0
A=`basename $0`
varg=''
host=`hostname`
tmprump=$HOME/ALscan
tmpmail=$HOME/ALmail
tmpconf=$HOME/ALconf
filespec=''
mails=''
alertfile=to_be_defined
tmpdate=to_be_defined
datestring="never_in_this_cycle"
USAGE="Usage: $A [s] [-f filespec] [-m mailfile] [mailuser ...]"
UHELP="  use: $A -h    for help"

function descr {
      BpAtTeRn='MANUAL ENTRY BEGIN'
      EpAtTeRn='MANUAL ENTRY END'
      egrep -v pAtTeRn $* | egrep "$BpAtTeRn" > /dev/null
      if [ $? -ne 0 ] ; then
        echo "No help available for: $(basename $*)"
        return 1
      fi
      egrep -v -- '----------------------------------------|pAtTeRn' $* |\
      sed -e 's/^\# //' -e 's/^\#//' |\
      nawk 'BEGIN {found=0} {
        if ( $0 ~ /'"$EpAtTeRn"'/ ) found=0
        if ( found == 1 ) print $0
        if ( $0 ~ /'"$BpAtTeRn"'/ ) found=1
        }'
      return 0
}

# ... Get the argument list
#
while getopts hsm:f: c
do
  case $c in
    f)  filespec=$OPTARG ;;
    h)  descr $0
        exit
        ;;
    m)  mails=$OPTARG ;;
    s)  report=1 ;;
    \?) echo "$USAGE"
        echo "$UHELP"
        exit 2 ;;
  esac
done
shift `expr $OPTIND - 1`
backarg="$*"
nr_arg="$#"
rm -f $tmpfile

#     Execute batch profile (since crontab's default is to not run .profile)
. $batch_profile

#... See where the oratab file is located
#
if [ -r /etc/oratab ] ; then
  ORATAB=/etc/oratab
else
  if [ -r /var/opt/oracle/oratab ] ; then
    ORATAB=/var/opt/oracle/oratab
  else
    echo "${A}: Can't find any oratab file to read."
    exit 1
  fi
fi

# ... Process each line in the oratab file
#
cat $ORATAB | while read LINE
do
  case $LINE in
    \#*) ;;  #comment-line in oratab
    '') ;;  #comment-line in oratab
    *)
         # all entries: ORACLE_SID:ORACLE_HOME:[Y|N]
         ORACLE_SID=`echo $LINE | nawk -F: '{print $1}' -`
         if [ "$ORACLE_SID" != '*' ] ; then
           
           # OFA like directory structure is needed
           cd $ORACLE_BASE/admin/$ORACLE_SID/bdump || continue

# ... Determine the alert-filename (according to -f option)
#
           case $filespec in
             '')
                   alertfile=`pwd`/alert_$ORACLE_SID.log ;;
             */*) 
                   alertfile=$filespec ;;
             *.*)
                   alertfile=`pwd`/$filespec ;;
             *)
                   alertfile=`pwd`/alert_$ORACLE_SID.$filespec ;;
           esac
           # echo "DBG: $ORACLE_SID $alertfile"
                   

           if [ ! -r $alertfile ] ; then
             echo "${A}: Alert-file $alertfile not readable." 
             stat=-2
             continue

           elif [ -r $alertfile ] ; then
             tmpdate="$tmprump.$ORACLE_SID"
             tmpmail=${tmpmail}_$ORACLE_SID
             ## echo "DBG: $ORACLE_SID $alertfile $tmpdate"

# ... Check if the ALscan-file exists and if it is writable
#
             if [ ! -e $tmpdate ] ; then
               cp /dev/null $tmpdate || exit 1
               chmod 644 $tmpdate
             else
               if [ ! -w $tmpdate ] ; then
                 echo "${A}: $tmpdate must be writable to operate."
                 echo "${A}: You may use option '-p' to run a personal edition."
                 exit 1
               fi
               datestring="$(cat $tmpdate)"
               if [ "$datestring" = "" ] ; then
                 datestring="never_in_this_cycle"
               fi

# ... Automatic initialisation for new alert-file cycle: if the date of ALscan-file
#     isn't found in the alert-file, begin a new cycle with an empty ALscan-file.
#     The mode of the ALscan-file will be set to writable by only the owner, to
#     minimize conflicts of several running 'alscans'.
#
               if [ "$datestring" != "never_in_this_cycle" ] ; then
                 egrep "$datestring" $alertfile > /dev/null
                 if [ $? -ne 0 ] ; then
                   echo "${A}: Automatic initialisation of alscan cycle at $(date)"

                   cp /dev/null $tmpdate || exit 1
                   chmod 644 $tmpdate
                   datestring="never_in_this_cycle"
                 fi
               fi
             fi

# ... Determine the patternfile name
#
             if [ -r ${def_patternfile}_$ORACLE_SID ] ; then
               patternfile=${def_patternfile}_$ORACLE_SID
             elif [ -r $def_patternfile ] ; then
               patternfile=$def_patternfile
             else
               echo '+ ORA-' > $tmpconf
               if [ $? -ne 0 ] ; then
                 exit 1
               fi
               patternfile=$tmpconf
             fi

# ... Just report the status from the ALscan-file and the patternfile
#
             if [ $report -eq 1 ] ; then
               echo "${A}: Messages from $alertfile issued till $datestring"
               echo "${A}: ... using $patternfile for filtering messages."
               continue
             fi

# ... Now the work (extracting messages from alert-file)
#
             nawk '
               function ispattern ( string, patternfile ) {
                 while ( (getline reference < patternfile) > 0 ) {
                   pattern=substr(reference,3)
                   action=substr(reference,1,2)
### print "NAWK PTRN ac="action" pt:"pattern" string:"string
                   ##if ( action ~! /^x/ && action ~! /^g/ ) {
                   ##if ( /^#/ !~ action ) {
                   if ( action ~ /^#/ ) {
### print "NAWK comment"
                     continue }
                   else if ( action ~! /^-/ && action ~! /^+/ ) {
### print "NAWK error"
                     close( patternfile )
                     return 0 }
                   else if ( action ~ /^-/ && match(string,pattern) ) {
### print "NAWK 1"
                     close( patternfile )
                     return 0 }
                   else if ( action ~ /^+/ && match(string,pattern) ) {
### print "NAWK 2"
                     close( patternfile )
                     return 1 }
                 }
                 close( patternfile )
                 return 0
               }

               BEGIN { 
                 datestring="'"$datestring"'" ; found=0
                 if ( datestring == "never_in_this_cycle" ) found=1
### printf "NAWK BEGIN: %s %d\n",datestring,found
               }
               { 
                 if ( NF == 0 ) continue
                 if ( found == 0 ) {
                   if (match($0,datestring)) {
                     found=1
### print "FOUND"
                     continue
                   }
                 }
                 if ( found >= 1 ) {
                   if ( $1 ~ /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/ && !match($0,datestring) ) {
                     found = 2
                     dprt = 0
                     datestring=$0
### printf "NAWK (2): %s %d\n",datestring,found
### print NR" "NF" "$0
                     continue
                   }
                 }
                 if ( found == 2 && ispattern($0,"'"$patternfile"'") == 1 ) {
### printf "NAWK (3): %s %d %d\n",datestring,found,dprt
### print NR" "NF" "$0
                   if ( dprt == 0 ) {
                     print datestring "  for host: '"$host"', sid: '"$ORACLE_SID"'" >> "'$tmpmail'"
                     dprt = 1
                   }
                   print $0  >> "'$tmpmail'"
                 }
               }
               END { print datestring > "'$tmpdate'" }'  $alertfile

             if [ $? -ne 0 ] ; then
               stat=-1
             fi
           fi
         fi
  esac

  # ... Display the results
  #
  if [ -s $tmpmail ] ; then
    if [ -s $mails ] ; then
      for usr in `cat $mails`
      do
        mailx -s "Message from alscan.sh ($host)" $usr < $tmpmail
        if [ $? -ne 0 ] ; then
          stat=-3
        fi
      done
    fi

    if [ $nr_arg -ne 0 ] ; then
      # mail to all explicitly defined users
      for usr in $backarg
      do
        mailx -s "Message from alscan.sh ($host)" $usr < $tmpmail
        if [ $? -ne 0 ] ; then
          stat=-3
        fi
      done
    fi

  # cat $tmpmail

  # ... Remove the tmpfiles and exit with the appropriate status
  #
    rm -f $tmpmail $tmpconf
  fi

done

exit $stat

