#!/bin/bash
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk is free software;  you can redistribute it and/or modify it
# under the  terms of the  GNU General Public License  as published by
# the Free Software Foundation in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# ails.  You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.

# Check_MK agent plugin for monitoring ORACLE databases
# This plugin is a result of the common work of Thorsten Bruhns
# and Mathias Kettner. Thorsten is responsible for the ORACLE
# stuff, Mathias for the shell hacking...

# Example for mk_oracle.cfg
# DBUSER=<DBUSER>:<PASSWORD>:<SYSDBA>:<HOSTNAME>:<Port>
# ASMUSER=<DBUSER>:<PASSWORD>:<SYSDBA/SYSASM>:<HOSTNAME>:<Port>
#
# SYSDBA or SYSASM is optional but needed for a mounted instance
# HOSTNAME is optional - Default is localhost
# PORT is optional - Default is 1521

while test $# -gt 0
do
    if [ "${1}" = '-d' ] ; then
        set -xv ; DEBUG=1
    elif [ "${1}" = '-t' ] ; then
        DEBUGCONNECT=1
    fi
    shift
done

if [ ! "$MK_CONFDIR" ] ; then
    echo "MK_CONFDIR not set!" >&2
    exit 1
fi

if [ ! "$MK_VARDIR" ] ; then
    export MK_VARDIR=$MK_CONFDIR
fi


#   .--Config--------------------------------------------------------------.
#   |                     ____             __ _                            |
#   |                    / ___|___  _ __  / _(_) __ _                      |
#   |                   | |   / _ \| '_ \| |_| |/ _` |                     |
#   |                   | |__| (_) | | | |  _| | (_| |                     |
#   |                    \____\___/|_| |_|_| |_|\__, |                     |
#   |                                           |___/                      |
#   +----------------------------------------------------------------------+
#   | The user can override and set variables in mk_oracle.cfg             |
#   '----------------------------------------------------------------------'

# Sections that run fast and do no caching
SYNC_SECTIONS="instance sessions logswitches undostat recovery_area processes recovery_status longactivesessions dataguard_stats performance"

# Sections that are run in the background and at a larger interval.
# Note: sections not listed in SYNC_SECTIONS or ASYNC_SECTIONS will not be
# executed at all!
ASYNC_SECTIONS="tablespaces rman jobs ts_quotas resumable locks"

# Sections that are run in the background and at a larger interval.
# Note: _ASM_ sections are only executed when SID starts with '+'
#       sections listed in SYNC_SECTIONS or ASYNC_SECTIONS are not
#       executed for ASM.
SYNC_ASM_SECTIONS="instance"
ASYNC_ASM_SECTIONS="asm_diskgroup"

# Interval for running async checks (in seconds)
CACHE_MAXAGE=600

# You can specify a list of SIDs to monitor. Those databases will
# only be handled, if they are found running, though!
#
#   ONLY_SIDS="XE ORCL FOO BAR"
#
# It is possible to filter SIDS negatively. Just add the following to
# the mk_oracle.cfg file:
#
#   EXCLUDE_<sid>="ALL"
#
# Another option is to filter single checks for SIDS. Just add
# lines as follows to the mk_oracle.cfg file. One service per
# line:
#
#   EXCLUDE_<sid>="<service>"
#
# For example skip oracle_sessions and oracle_logswitches checks
# for the instance "mysid".
#
#   EXCLUDE_mysid="sessions logswitches"
#

# Source the optional configuration file for this agent plugin
if [ -e "$MK_CONFDIR/mk_oracle.cfg" ]
then
    . $MK_CONFDIR/mk_oracle.cfg
fi

#.
#   .--SQL Queries---------------------------------------------------------.
#   |        ____   ___  _        ___                  _                   |
#   |       / ___| / _ \| |      / _ \ _   _  ___ _ __(_) ___  ___         |
#   |       \___ \| | | | |     | | | | | | |/ _ \ '__| |/ _ \/ __|        |
#   |        ___) | |_| | |___  | |_| | |_| |  __/ |  | |  __/\__ \        |
#   |       |____/ \__\_\_____|  \__\_\\__,_|\___|_|  |_|\___||___/        |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   | The following functions create SQL queries for ORACLE and output     |
#   | them to stdout. All queries output the database name or the instane  |
#   | name as first column.                                                |
#   '----------------------------------------------------------------------'

sql_performance()
{
    if [ "$AT_LEAST_ORACLE_101" = 'yes' ] ; then
        echo 'PROMPT <<<oracle_performance:sep(124)>>>'
        echo "select upper(i.INSTANCE_NAME)
                     ||'|'|| 'sys_time_model'
                     ||'|'|| S.STAT_NAME
                     ||'|'|| Round(s.value/1000000)
              from v\$instance i,
                   v\$sys_time_model s
              where s.stat_name in('DB time', 'DB CPU')
              order by s.stat_name;
              select upper(i.INSTANCE_NAME)
                     ||'|'|| 'buffer_pool_statistics'
                     ||'|'|| b.name
                     ||'|'|| b.db_block_gets
                     ||'|'|| b.db_block_change
                     ||'|'|| b.consistent_gets
                     ||'|'|| b.physical_reads
                     ||'|'|| b.physical_writes
                     ||'|'|| b.FREE_BUFFER_WAIT
                     ||'|'|| b.BUFFER_BUSY_WAIT
              from v\$instance i, V\$BUFFER_POOL_STATISTICS b;
              select upper(i.INSTANCE_NAME)
                     ||'|'|| 'librarycache'
                     ||'|'|| b.namespace
                     ||'|'|| b.gets
                     ||'|'|| b.gethits
                     ||'|'|| b.pins
                     ||'|'|| b.pinhits
                     ||'|'|| b.reloads
                     ||'|'|| b.invalidations
              from v\$instance i, V\$librarycache b;"
    fi
}

sql_tablespaces()
{
    echo 'PROMPT <<<oracle_tablespaces:sep(124)>>>'
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then

        echo "select upper(d.NAME) || '|' || file_name ||'|'|| tablespace_name ||'|'|| fstatus ||'|'|| AUTOEXTENSIBLE
                  ||'|'|| blocks ||'|'|| maxblocks ||'|'|| USER_BLOCKS ||'|'|| INCREMENT_BY
                  ||'|'|| ONLINE_STATUS ||'|'|| BLOCK_SIZE
                  ||'|'|| decode(tstatus,'READ ONLY', 'READONLY', tstatus) || '|' || free_blocks
                  ||'|'|| contents
           from v\$database d , (
                    select f.file_name, f.tablespace_name, f.status fstatus, f.AUTOEXTENSIBLE,
                    f.blocks, f.maxblocks, f.USER_BLOCKS, f.INCREMENT_BY,
                    f.ONLINE_STATUS, t.BLOCK_SIZE, t.status tstatus, nvl(sum(fs.blocks),0) free_blocks, t.contents
                    from dba_data_files f, dba_tablespaces t, dba_free_space fs
                    where f.tablespace_name = t.tablespace_name
                    and f.file_id = fs.file_id(+)
                    group by f.file_name, f.tablespace_name, f.status, f.autoextensible,
                    f.blocks, f.maxblocks, f.user_blocks, f.increment_by, f.online_status,
                    t.block_size, t.status, t.contents
                    UNION
                    select f.file_name, f.tablespace_name, f.status, f.AUTOEXTENSIBLE,
                    f.blocks, f.maxblocks, f.USER_BLOCKS, f.INCREMENT_BY, 'TEMP',
                    t.BLOCK_SIZE, t.status, sum(sh.blocks_free) free_blocks, 'TEMPORARY'
                    from v\$thread th, dba_temp_files f, dba_tablespaces t, v\$temp_space_header sh
                    WHERE f.tablespace_name = t.tablespace_name and f.file_id = sh.file_id
                    GROUP BY th.instance, f.file_name, f.tablespace_name, f.status,
                    f.autoextensible, f.blocks, f.maxblocks, f.user_blocks, f.increment_by,
                    'TEMP', t.block_size, t.status);
              "
    elif [ "$AT_LEAST_ORACLE_92" = 'yes' ] ; then

        echo "select upper(d.NAME) || '|' || file_name ||'|'|| tablespace_name ||'|'|| fstatus ||'|'|| AUTOEXTENSIBLE
                  ||'|'|| blocks ||'|'|| maxblocks ||'|'|| USER_BLOCKS ||'|'|| INCREMENT_BY
                  ||'|'|| ONLINE_STATUS ||'|'|| BLOCK_SIZE
                  ||'|'|| decode(tstatus,'READ ONLY', 'READONLY', tstatus) || '|' || free_blocks
                  ||'|'|| contents
           from v\$database d , (
                    select f.file_name, f.tablespace_name, f.status fstatus, f.AUTOEXTENSIBLE,
                    f.blocks, f.maxblocks, f.USER_BLOCKS, f.INCREMENT_BY,
                    'ONLINE' ONLINE_STATUS, t.BLOCK_SIZE, t.status tstatus, nvl(sum(fs.blocks),0) free_blocks, t.contents
                    from dba_data_files f, dba_tablespaces t, dba_free_space fs
                    where f.tablespace_name = t.tablespace_name
                    and f.file_id = fs.file_id(+)
                    group by f.file_name, f.tablespace_name, f.status, f.autoextensible,
                    f.blocks, f.maxblocks, f.user_blocks, f.increment_by, 'ONLINE',
                    t.block_size, t.status, t.contents
                    UNION
                    select f.file_name, f.tablespace_name, 'ONLINE' status, f.AUTOEXTENSIBLE,
                    f.blocks, f.maxblocks, f.USER_BLOCKS, f.INCREMENT_BY, 'TEMP',
                    t.BLOCK_SIZE, 'TEMP' status, sum(sh.blocks_free) free_blocks, 'TEMPORARY'
                    from v\$thread th, dba_temp_files f, dba_tablespaces t, v\$temp_space_header sh
                    WHERE f.tablespace_name = t.tablespace_name and f.file_id = sh.file_id
                    GROUP BY th.instance, f.file_name, f.tablespace_name, 'ONLINE',
                    f.autoextensible, f.blocks, f.maxblocks, f.user_blocks, f.increment_by,
                    'TEMP', t.block_size, t.status);
              "
    fi
}

sql_dataguard_stats()
{
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo 'PROMPT <<<oracle_dataguard_stats:sep(124)>>>'
        echo "SELECT upper(d.NAME)
                     ||'|'|| upper(d.DB_UNIQUE_NAME)
                     ||'|'|| d.DATABASE_ROLE
                     ||'|'|| ds.name
                     ||'|'|| ds.value
              FROM  v\$database d
              JOIN  v\$parameter vp on 1=1
              left outer join V\$dataguard_stats ds on 1=1
              WHERE vp.name = 'log_archive_config'
              AND   vp.value is not null
              ORDER BY 1;
             "
    fi
}

sql_recovery_status()
{
    echo 'PROMPT <<<oracle_recovery_status:sep(124)>>>'
    if [ "$AT_LEAST_ORACLE_101" = 'yes' ] ; then
        echo "SELECT upper(d.NAME)
                     ||'|'|| d.DB_UNIQUE_NAME
                     ||'|'|| d.DATABASE_ROLE
                     ||'|'|| d.open_mode
                     ||'|'|| dh.file#
                     ||'|'|| round((dh.CHECKPOINT_TIME-to_date('01.01.1970','dd.mm.yyyy'))*24*60*60)
                     ||'|'|| round((sysdate-dh.CHECKPOINT_TIME)*24*60*60)
                     ||'|'|| dh.STATUS
                     ||'|'|| dh.RECOVER
                     ||'|'|| dh.FUZZY
                     ||'|'|| dh.CHECKPOINT_CHANGE#
              FROM  V\$datafile_header dh, v\$database d, v\$instance i
              ORDER BY dh.file#;
             "
    elif [ "$AT_LEAST_ORACLE_92" = 'yes' ] ; then
        echo "SELECT upper(d.NAME)
                     ||'|'|| d.NAME
                     ||'|'|| d.DATABASE_ROLE
                     ||'|'|| d.open_mode
                     ||'|'|| dh.file#
                     ||'|'|| round((dh.CHECKPOINT_TIME-to_date('01.01.1970','dd.mm.yyyy'))*24*60*60)
                     ||'|'|| round((sysdate-dh.CHECKPOINT_TIME)*24*60*60)
                     ||'|'|| dh.STATUS
                     ||'|'|| dh.RECOVER
                     ||'|'|| dh.FUZZY
                     ||'|'|| dh.CHECKPOINT_CHANGE#
              FROM  V\$datafile_header dh, v\$database d, v\$instance i
              ORDER BY dh.file#;
             "
    fi
}

sql_rman()
{
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo 'PROMPT <<<oracle_rman:sep(124)>>>'
        echo "SELECT upper(d.NAME)
                     ||'|'|| a.STATUS
                     ||'|'|| to_char(a.START_TIME, 'YYYY-mm-dd_HH24:MI:SS')
                     ||'|'|| to_char(a.END_TIME, 'YYYY-mm-dd_HH24:MI:SS')
                     ||'|'|| replace(b.INPUT_TYPE, ' ', '_')
                     ||'|'|| round(((sysdate - END_TIME) * 24 * 60),0)
                     FROM V\$RMAN_BACKUP_JOB_DETAILS a, v\$database d,
                          (SELECT input_type, max(command_id) as command_id
                           FROM V\$RMAN_BACKUP_JOB_DETAILS
                          WHERE START_TIME > sysdate-14
                            and input_type != 'ARCHIVELOG'
                            and STATUS<>'RUNNING' GROUP BY input_type) b
                     WHERE a.COMMAND_ID = b.COMMAND_ID
              UNION ALL
              select name
                     || '|COMPLETED'
                     || '|'|| to_char(sysdate, 'YYYY-mm-dd_HH24:MI:SS')
                     || '|'|| to_char(completed, 'YYYY-mm-dd_HH24:MI:SS')
                     || '|ARCHIVELOG|'
                     || round((sysdate - completed)*24*60,0)
              from (
                    select d.name
                         , max(a.completion_time) completed
                         , case when a.backup_count > 0 then 1 else 0 end
                    from v\$archived_log a, v\$database d
                    where a.backup_count > 0
                          and a.dest_id in
                          (select b.dest_id
                           from v\$archive_dest b
                           where b.target = 'PRIMARY'
                             and b.SCHEDULE = 'ACTIVE'
                          )
                    group by d.name, case when a.backup_count > 0 then 1 else 0 end);"
    fi
}

sql_recovery_area()
{
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo 'PROMPT <<<oracle_recovery_area>>>'
        echo "select upper(d.NAME)
                     ||' '|| round((SPACE_USED-SPACE_RECLAIMABLE)/
                               (CASE NVL(SPACE_LIMIT,1) WHEN 0 THEN 1 ELSE SPACE_LIMIT END)*100)
                     ||' '|| round(SPACE_LIMIT/1024/1024)
                     ||' '|| round(SPACE_USED/1024/1024)
                     ||' '|| round(SPACE_RECLAIMABLE/1024/1024)
              from V\$RECOVERY_FILE_DEST, v\$database d;
             "
    fi
}

sql_undostat()
{
    echo 'PROMPT <<<oracle_undostat:sep(124)>>>'
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo "select upper(i.INSTANCE_NAME)
                     ||'|'|| ACTIVEBLKS
                     ||'|'|| MAXCONCURRENCY
                     ||'|'|| TUNED_UNDORETENTION
                     ||'|'|| maxquerylen
                     ||'|'|| NOSPACEERRCNT
              from v\$instance i,
                  (select * from (select *
                                  from v\$undostat order by end_time desc
                                 )
                            where rownum = 1
                              and TUNED_UNDORETENTION > 0
                  );
             "
    elif [ "$AT_LEAST_ORACLE_92" = 'yes' ] ; then
        # TUNED_UNDORETENTION and ACTIVEBLKS are not availibe in Oracle <=9.2!
        # we sent a -1 for filtering in check_undostat
        echo "select upper(i.INSTANCE_NAME)
                     ||'|-1'
                     ||'|'|| MAXCONCURRENCY
                     ||'|-1'
                     ||'|'|| maxquerylen
                     ||'|'|| NOSPACEERRCNT
                  from v\$instance i,
                  (select * from (select *
                                  from v\$undostat order by end_time desc
                                 )
                            where rownum = 1
                  );
             "
    fi
}

sql_resumable()
{
    echo 'PROMPT <<<oracle_resumable:sep(124)>>>'
    echo "select upper(i.INSTANCE_NAME)
                 ||'|'|| u.username
                 ||'|'|| a.SESSION_ID
                 ||'|'|| a.status
                 ||'|'|| a.TIMEOUT
                 ||'|'|| round((sysdate-to_date(a.SUSPEND_TIME,'mm/dd/yy hh24:mi:ss'))*24*60*60)
                 ||'|'|| a.ERROR_NUMBER
                 ||'|'|| to_char(to_date(a.SUSPEND_TIME, 'mm/dd/yy hh24:mi:ss'),'mm/dd/yy_hh24:mi:ss')
                 ||'|'|| a.RESUME_TIME
                 ||'|'|| a.ERROR_MSG
          from dba_resumable a, v\$instance i, dba_users u
          where a.INSTANCE_ID = i.INSTANCE_NUMBER
          and u.user_id = a.user_id
          and a.SUSPEND_TIME is not null
          union all
          select upper(i.INSTANCE_NAME)
                 || '|||||||||'
          from v\$instance i
;
         "
}

sql_jobs()
{
    if [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo 'PROMPT <<<oracle_jobs:sep(124)>>>'
        echo "SELECT upper(d.NAME)
                     ||'|'|| j.OWNER
                     ||'|'|| j.JOB_NAME
                     ||'|'|| j.STATE
                     ||'|'|| ROUND((TRUNC(sysdate) + j.LAST_RUN_DURATION - TRUNC(sysdate)) * 86400)
                     ||'|'|| j.RUN_COUNT
                     ||'|'|| j.ENABLED
                     ||'|'|| NVL(j.NEXT_RUN_DATE, to_date('1970-01-01', 'YYYY-mm-dd'))
                     ||'|'|| NVL(j.SCHEDULE_NAME, '-')
                     ||'|'|| d.STATUS
              FROM dba_scheduler_jobs j, dba_scheduler_job_run_details d, v\$database d
              WHERE d.owner=j.OWNER AND d.JOB_NAME=j.JOB_NAME
                AND d.LOG_ID=(SELECT max(LOG_ID) FROM dba_scheduler_job_run_details dd
                              WHERE dd.owner=j.OWNER and dd.JOB_NAME=j.JOB_NAME
                             );
             "
    fi
}

sql_ts_quotas()
{
    echo 'PROMPT <<<oracle_ts_quotas:sep(124)>>>'
    echo "select upper(d.NAME)
                 ||'|'|| Q.USERNAME
                 ||'|'|| Q.TABLESPACE_NAME
                 ||'|'|| Q.BYTES
                 ||'|'|| Q.MAX_BYTES
          from dba_ts_quotas Q, v\$database d
          where max_bytes > 0
          union all
          select upper(d.NAME)
                 ||'|||'
          from v\$database d
          order by 1;
         "
}

sql_version()
{
    echo 'PROMPT <<<oracle_version>>>'
    echo "select upper(i.INSTANCE_NAME)
	  || ' ' || banner
	  from v\$version, v\$instance i
	  where banner like 'Oracle%';"
}

sql_instance()
{
    echo 'prompt <<<oracle_instance:sep(124)>>>'
    if [ ${ORACLE_SID:0:1} = '+' ] ; then
        # ASM
        echo "select upper(i.instance_name)
                     || '|' || i.VERSION
                     || '|' || i.STATUS
                     || '|' || i.LOGINS
                     || '|' || i.ARCHIVER
                     || '|' || round((sysdate - i.startup_time) * 24*60*60)
                     || '|' || '0'
                     || '|' || 'NO'
                     || '|' || 'ASM'
                     || '|' || 'NO'
                     || '|' || i.instance_name
                from v\$instance i;
             "
    else
        # normal Instance
        echo "select upper(i.instance_name)
                     || '|' || i.VERSION
                     || '|' || i.STATUS
                     || '|' || i.LOGINS
                     || '|' || i.ARCHIVER
                     || '|' || round((sysdate - i.startup_time) * 24*60*60)
                     || '|' || DBID
                     || '|' || LOG_MODE
                     || '|' || DATABASE_ROLE
                     || '|' || FORCE_LOGGING
                     || '|' || d.name
                from v\$instance i, v\$database d;
             "
    fi
}

sql_sessions()
{
    echo 'prompt <<<oracle_sessions>>>'
    echo "select upper(i.instance_name)
                  || ' ' || CURRENT_UTILIZATION
           from v\$resource_limit, v\$instance i
           where RESOURCE_NAME = 'sessions';
         "
}

sql_processes()
{
    echo 'prompt <<<oracle_processes>>>'
    echo "select upper(i.instance_name)
                  || ' ' || CURRENT_UTILIZATION
                  || ' ' || ltrim(rtrim(LIMIT_VALUE))
           from v\$resource_limit, v\$instance i
           where RESOURCE_NAME = 'processes';
         "
}

sql_logswitches()
{
    echo 'prompt <<<oracle_logswitches>>>'
    echo "select upper(i.instance_name)
                  || ' ' || logswitches
           from v\$instance i ,
                (select count(1) logswitches
                 from v\$loghist
                 where first_time > sysdate - 1/24
                );
         "
}

sql_locks()
{
    if [ "$AT_LEAST_ORACLE_101" = 'yes' ] ; then
        echo 'prompt <<<oracle_locks:sep(124)>>>'
        echo "SET SERVEROUTPUT ON feedback off
DECLARE
    type x is table of varchar2(20000) index by pls_integer;
    xx x;
begin
    begin
        execute immediate 'select upper(i.instance_name)
           || ''|'' || a.sid
           || ''|'' || b.serial#
           || ''|'' || b.machine
           || ''|'' || b.program
           || ''|'' || b.process
           || ''|'' || b.osuser
           || ''|'' || a.ctime
           || ''|'' || decode(c.owner,NULL,''NULL'',c.owner)
           || ''|'' || decode(c.object_name,NULL,''NULL'',c.object_name)
            from V\$LOCK a, v\$session b, dba_objects c, v\$instance i
            where (a.id1, a.id2, a.type)
                       IN (SELECT id1, id2, type
                           FROM GV\$LOCK
                           WHERE request>0
                          )
            and request=0
            and a.sid = b.sid
            and a.id1 = c.object_id (+)
            union all
            select upper(i.instance_name) || ''|||||||||''
            from  v\$instance i'
        bulk collect into xx;
        if xx.count >= 1 then
            for i in 1 .. xx.count loop
                dbms_output.put_line(xx(i));
            end loop;
        end if;
    exception
        when others then
            for cur1 in (select upper(i.instance_name) instance_name from  v\$instance i) loop
                dbms_output.put_line(cur1.instance_name || '|||||||||'||sqlerrm);
            end loop;
    end;
END;
/
set serverout off
"
    fi
}

sql_longactivesessions()
{
    if [ "$AT_LEAST_ORACLE_101" = 'yes' ] ; then
        echo 'prompt <<<oracle_longactivesessions:sep(124)>>>'
        echo "select upper(i.instance_name)
                     || '|' || s.sid
                     || '|' || s.serial#
                     || '|' || s.machine
                     || '|' || s.process
                     || '|' || s.osuser
                     || '|' || s.program
                     || '|' || s.last_call_et
                     || '|' || s.sql_id
              from v\$session s, v\$instance i
              where s.status = 'ACTIVE'
              and type != 'BACKGROUND'
              and s.username is not null
              and s.username not in('PUBLIC')
              and s.last_call_et > 60*60
              union all
              select upper(i.instance_name)
                     || '||||||||'
              from v\$instance i;
             "
    fi
}

sql_asm_diskgroup()
{
    echo 'prompt <<<oracle_asm_diskgroup>>>'
    if [ "$AT_LEAST_ORACLE_112" = 'yes' ] ; then
        echo "select STATE
                     || ' ' || TYPE
                     || ' ' || 'N'
                     || ' ' || sector_size
                     || ' ' || block_size
                     || ' ' || allocation_unit_size
                     || ' ' || total_mb
                     || ' ' || free_mb
                     || ' ' || required_mirror_free_mb
                     || ' ' || usable_file_mb
                     || ' ' || offline_disks
                     || ' ' || voting_files
                     || ' ' || name || '/'
                from v\$asm_diskgroup;
             "
    elif [ "$AT_LEAST_ORACLE_102" = 'yes' ] ; then
        echo "select STATE
                     || ' ' || TYPE
                     || ' ' || 'N'
                     || ' ' || sector_size
                     || ' ' || block_size
                     || ' ' || allocation_unit_size
                     || ' ' || total_mb
                     || ' ' || free_mb
                     || ' ' || required_mirror_free_mb
                     || ' ' || usable_file_mb
                     || ' ' || offline_disks
                     || ' ' || 'N'
                     || ' ' || name || '/'
                from v\$asm_diskgroup;
             "
    fi
}

#.
#   .--oraenv--------------------------------------------------------------.
#   |                                                                      |
#   |                    ___  _ __ __ _  ___ _ ____   __                   |
#   |                   / _ \| '__/ _` |/ _ \ '_ \ \ / /                   |
#   |                  | (_) | | | (_| |  __/ | | \ V /                    |
#   |                   \___/|_|  \__,_|\___|_| |_|\_/                     |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   | Functions for getting the Oracle environment                         |
#   '----------------------------------------------------------------------'

function set_oraenv () {
    ORACLE_SID=${1}

    test -f /etc/oratab && ORATAB=/etc/oratab
    # /var/opt/oracle/oratab is needed for Oracle Solaris
    test -f /var/opt/oracle/oratab && ORATAB=/var/opt/oracle/oratab
    test -f ${ORATAB:-""} || echo "ORA-99999 oratab not found"
    test -f ${ORATAB:-""} || exit 1

    ORACLE_HOME=$(cat ${ORATAB} | grep "^"${ORACLE_SID}":" | cut -d":" -f2)
    if [ -z $ORACLE_HOME ] ; then
        # cut last number from SID for Oracle RAC to find entry in oratab
        ORACLE_SID_SHORT=$(echo $ORACLE_SID | sed "s/[0-9]$//")
        ORACLE_HOME=$(cat ${ORATAB} | grep "^"${ORACLE_SID_SHORT}":" | cut -d":" -f2)
    fi

    if [ ! -d ${ORACLE_HOME:-"not_found"} ] ; then
        echo "ORA-99999 ORACLE_HOME for ORACLE_SID="$ORACLE_SID" not found or not existing!"
        exit 1
    fi

    TNS_ADMIN=${TNS_ADMIN:-$MK_CONFDIR}

    test -f ${TNS_ADMIN}/sqlnet.ora || ( echo "ORA-99998 Couldn't find "${TNS_ADMIN}/sqlnet.ora ; exit 1)

    export ORACLE_HOME TNS_ADMIN ORACLE_SID
}

function get_oraversion () {
    set_oraenv ${1}
    ORACLE_VERSION=$($ORACLE_HOME/bin/sqlplus -V | grep ^SQL | cut -d" " -f3 | cut -d"." -f-2)

    # remove possible existing variables
    unset AT_LEAST_ORACLE_121 AT_LEAST_ORACLE_112 AT_LEAST_ORACLE_111 AT_LEAST_ORACLE_102 AT_LEAST_ORACLE_101 AT_LEAST_ORACLE_92

    if [ "$ORACLE_VERSION" = '12.1' ] ; then
        AT_LEAST_ORACLE_121=yes
    fi

    if [ "$ORACLE_VERSION" = '12.1' -o "$ORACLE_VERSION" = '11.2' ] ; then
        AT_LEAST_ORACLE_112=yes
    fi

    if [ "$ORACLE_VERSION" = '12.1' -o "$ORACLE_VERSION" = '11.2' \
         -o "$ORACLE_VERSION" = '11.1' ] ; then
        AT_LEAST_ORACLE_111=yes
    fi

    if [ "$ORACLE_VERSION" = '12.1' -o "$ORACLE_VERSION" = '11.2' \
         -o "$ORACLE_VERSION" = '11.1' -o "$ORACLE_VERSION" = '10.2' ] ; then
        AT_LEAST_ORACLE_102=yes
    fi

    if [ "$ORACLE_VERSION" = '12.1' -o "$ORACLE_VERSION" = '11.2' \
         -o "$ORACLE_VERSION" = '11.1' -o "$ORACLE_VERSION" = '10.2' \
         -o "$ORACLE_VERSION" = '10.1' ] ; then
        AT_LEAST_ORACLE_101=yes
    fi

    if [ "$ORACLE_VERSION" = '12.1' -o "$ORACLE_VERSION" = '11.2' \
         -o "$ORACLE_VERSION" = '11.1' -o "$ORACLE_VERSION" = '10.2' \
         -o "$ORACLE_VERSION" = '10.1' -o "$ORACLE_VERSION" = '9.2' ] ; then
        AT_LEAST_ORACLE_92=yes
    fi
}

#.
#   .--Functions-----------------------------------------------------------.
#   |             _____                 _   _                              |
#   |            |  ___|   _ _ __   ___| |_(_) ___  _ __  ___              |
#   |            | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __|             |
#   |            |  _|| |_| | | | | (__| |_| | (_) | | | \__ \             |
#   |            |_|   \__,_|_| |_|\___|\__|_|\___/|_| |_|___/             |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   | Helper functions                                                     |
#   '----------------------------------------------------------------------'

function sqlplus_internal() {
    loc_stdin=$(cat)
    set_oraenv $SID

    # reload mk_oracle.cfg for run_cached. Otherwise some variables are missing
    if [ -e "$MK_CONFDIR/mk_oracle.cfg" ]
    then
        . $MK_CONFDIR/mk_oracle.cfg
    fi

    # mk_oracle_dbusers.conf is for compatibility. Do not use it anymore
    ORACLE_USERCONF=${MK_CONFDIR}/mk_oracle_dbuser.conf

    TNSPINGOK=no
    if [ -f ${TNS_ADMIN}/tnsnames.ora ] ; then
        if "${ORACLE_HOME}"/bin/tnsping "${ORACLE_SID}" >/dev/null 2>&1  ; then
            TNSALIAS=$ORACLE_SID
            TNSPINGOK=yes
        fi
    fi

    ORADBUSER=""
    DBPASSWORD=""

    # ASM use '+' as 1st character in SID!
    if [ ${ORACLE_SID:0:1} = '+' ] ; then
        ORACFGLINE=${ASMUSER}
    else
        # use an individuel user or the default DBUSER from mk_oracle.cfg
        dummy="DBUSER_"${ORACLE_SID}
        ORACFGLINE=${!dummy}
        if [ "$ORACFGLINE" = '' ] ; then
            ORACFGLINE=${DBUSER}
        fi
    fi

    if [ -f ${ORACLE_USERCONF} -a "${ORACFGLINE}" = '' ] ; then
        # mk_oracle_dbuser.conf
        ORACFGLINE=$(cat ${ORACLE_USERCONF} | grep "^"${ORACLE_SID}":")
        # mk_oracle_dbuser has ORACLE_SID as 1. parameter. we need an offset for all values
        offset=1
    else
        # mk_oracle.cfg
        offset=0
    fi

    ORADBUSER=$(echo ${ORACFGLINE} | cut -d":" -f$[1+offset])
    DBPASSWORD=$(echo ${ORACFGLINE} | cut -d":" -f$[2+offset])
    DBSYSCONNECT=$(echo ${ORACFGLINE} | cut -d":" -f$[3+offset])
    DBHOST=$(echo ${ORACFGLINE} | cut -d":" -f$[4+offset])
    DBPORT=$(echo ${ORACFGLINE} | cut -d":" -f$[5+offset])

    if [ ! "${ORACFGLINE}" ] ; then
        # no configuration found
        # => use the wallet with tnsnames.ora or EZCONNECT
        TNSALIAS=${TNSALIAS:-"localhost:1521/${ORACLE_SID}"}
    else
        if [ ${DBSYSCONNECT} ] ; then
            assysdbaconnect=" as "${DBSYSCONNECT}
        fi
        TNSALIAS=${TNSALIAS:-"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=${DBHOST:-"localhost"})(PORT=${DBPORT:-1521}))(CONNECT_DATA=(SID=${ORACLE_SID})(SERVER=DEDICATED)(UR=A)))"}

        # ORADBUSER = '/'? => ignore DBPASSWORD and use the wallet
        if [ "${ORADBUSER}" = '/' ] ; then
            # connect with / and wallet
            ORADBUSER=""
            DBPASSWORD=""
            if [ "$TNSPINGOK" = 'no' ] ; then
                # create an EZCONNECT string when no tnsnames.ora is usable
                # defaults to localhost:1521/<ORACLE_SID>
                TNSALIAS="${DBHOST:-"localhost"}:${DBPORT:-1521}/${ORACLE_SID}"
            fi
        fi
    fi

    DBCONNECT="${ORADBUSER}/${DBPASSWORD}@${TNSALIAS}${assysdbaconnect}"

    SQLPLUS=${ORACLE_HOME}/bin/sqlplus
    if [ ! -x ${SQLPLUS} ] ; then
        echo "sqlplus not found or ORACLE_HOME wrong! "
        echo "SQLPLUS="${SQLPLUS}
        return 1
    fi

    echo "$loc_stdin" | ${SQLPLUS} -L -s ${DBCONNECT}
    if [ $? -ne 0 ] ; then
        if [ "$DEBUGCONNECT" ] ; then
            echo "Logindetails: ${DBCONNECT}" >&2
        fi
        return 1
    fi
}

# Helper function that calls an SQL statement with a clean output
# Usage: echo "..." | sqlplus SID
function sqlplus ()
{
    local SID=$1
    loc_stdin=$(cat)

    # use sqlplus_internal when no sqlplus.sh is found
    SQLPLUS="$MK_CONFDIR"/sqlplus.sh
    test -f "$SQLPLUS" || SQLPLUS=sqlplus_internal

    if OUTPUT=$({ echo 'set pages 0 trimspool on feedback off lines 8000' ; echo 'whenever sqlerror exit 1'; echo "$loc_stdin" ; } | "$SQLPLUS" $SID)
    then
        echo "$OUTPUT"
    else
        echo '<<<oracle_instance:sep(124)>>>'
        local SID_UPPER=$(echo "$SID" | tr '[:lower:]' '[:upper:]')
        echo "$OUTPUT" | grep -v "^ERROR at line" | tr '\n' ' ' | sed "s/^/$SID_UPPER|FAILURE|/" ; echo
        return 1
    fi
}

function remove_excluded_sections ()
{
    local sections="$1"
    local excluded="$2"
    local result=""
    for section in $sections
    do
        local skip=
        for exclude in $excluded
        do
            if [ "$exclude" = "$section" ] ; then
                local skip=yes
                break
            fi
        done
        if [ "$skip" != yes ] ; then
            result="$result $section"
        fi
    done
    echo "$result"
}


# Create one SQL statements for several sections and run
# these with sqlplus. The exitcode is preserved.
function do_sync_checks ()
{
    local SID=$1
    local SECTIONS="$2"
    for section in $SECTIONS
    do
        eval "sql_$section"
    done | sqlplus $SID
}

function do_async_checks ()
{
    local SID=$1
    echo "$ASYNC_SQL" | sqlplus $SID
}

# Make sure that the new shell that is being run by run_cached inherits
# our functions
export -f sqlplus
export -f sqlplus_internal
export -f do_async_checks
export -f set_oraenv

function run_cached_local () {
    local section=
    if [ "$1" = -s ] ; then local section="echo '<<<$2>>>' ; " ; shift ; fi
    local NAME=$1
    local MAXAGE=$2
    shift 2
    local CMDLINE="$section$@"

    if [ ! -d $MK_VARDIR/cache ]; then mkdir -p $MK_VARDIR/cache ; fi
    CACHEFILE="$MK_VARDIR/cache/$NAME.cache"

    # Check if the creation of the cache takes suspiciously long and return
    # nothing if the age (access time) of $CACHEFILE.new is twice the MAXAGE
    local NOW=$(date +%s)
    if [ -e "$CACHEFILE.new" ] ; then
        local CF_ATIME=$(stat -c %X "$CACHEFILE.new")
        if [ $((NOW - CF_ATIME)) -ge $((MAXAGE * 2)) ] ; then
            return
        fi
    fi

    # Check if cache file exists and is recent enough
    if [ -s "$CACHEFILE" ] ; then
        local MTIME=$(stat -c %Y "$CACHEFILE")
        if [ $((NOW - MTIME)) -le $MAXAGE ] ; then local USE_CACHEFILE=1 ; fi
        # Output the file in any case, even if it is
        # outdated. The new file will not yet be available
        cat "$CACHEFILE"
    fi

    # Cache file outdated and new job not yet running? Start it
    if [ -z "$USE_CACHEFILE" -a ! -e "$CACHEFILE.new" ] ; then
        if [ "$DEBUG" ] ; then
            echo "set -o noclobber ; exec > \"$CACHEFILE.new\" || exit 1 ; $CMDLINE && mv \"$CACHEFILE.new\" \"$CACHEFILE\" || rm -f \"$CACHEFILE\" \"$CACHEFILE.new\"" | bash
        else
            echo "set -o noclobber ; exec > \"$CACHEFILE.new\" || exit 1 ; $CMDLINE && mv \"$CACHEFILE.new\" \"$CACHEFILE\" || rm -f \"$CACHEFILE\" \"$CACHEFILE.new\"" | nohup bash 2>/dev/null &
        fi
    fi
}

#.
#   .--Main----------------------------------------------------------------.
#   |                        __  __       _                                |
#   |                       |  \/  | __ _(_)_ __                           |
#   |                       | |\/| |/ _` | | '_ \                          |
#   |                       | |  | | (_| | | | | |                         |
#   |                       |_|  |_|\__,_|_|_| |_|                         |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |  Iterate over all instances and execute sync and async sections.     |
#   '----------------------------------------------------------------------'

# Get list of all running databases
# Do not work on ASM in this plugin. => Ignore a running ASM-Instance!
SIDS=$(UNIX95=true ps -ef | awk '{print $NF}' | grep -E '^asm_pmon_|^ora_pmon_|^xe_pmon_XE' | cut -d"_" -f3-)

# If we do not have found any running database instance, then either
# no ORACLE is present on this system or it's just currently not running.
# In the later case we ouput empty agent sections so that Check_MK will be
# happy and execute the actual check functions.
if [ -z "$SIDS" -a ! -e "$MK_VARDIR/mk_oracle.found" ] ; then
    exit
fi

# From now on we expect databases on this system (for ever)
touch $MK_VARDIR/mk_oracle.found

# Make sure that always all sections are present, even
# in case of an error. Note: the section <<<oracle_instance>>>
# section shows the general state of a database instance. If
# that section fails for an instance then all other sections
# do not contain valid data anyway.
for section in $SYNC_SECTIONS $ASYNC_SECTIONS $SYNC_ASM_SECTIONS $ASYNC_ASM_SECTIONS
do
    echo "<<<oracle_"$section">>>"
done

for SID in $SIDS
do
    # We need the SID in uppercase at later time
    SID_UPPER=$(echo $SID | tr '[:lower:]' '[:upper:]')

    # Check if SID is listed in ONLY_SIDS if this is used
    if [ "$ONLY_SIDS" ] ; then
        SKIP=yes
        for S in $ONLY_SIDS ; do
	    if  [ "$S" = "$SID" ] ; then
                SKIP=
                break
            fi
        done
        if [ "$SKIP" ] ; then continue ; fi
    fi

    # Handle explicit exclusion of instances
    EXCLUDE=EXCLUDE_$SID
    EXCLUDE=${!EXCLUDE}
    # SID filtered totally?
    if [ "$EXCLUDE" = "ALL" ]; then
        continue
    fi

    if [ ${SID:0:1} = '+' ] ; then
        DO_ASYNC_SECTIONS=${ASYNC_ASM_SECTIONS}
        DO_SYNC_SECTIONS=${SYNC_ASM_SECTIONS}
    else
        # switch sections to ASM
        DO_SYNC_SECTIONS=${SYNC_SECTIONS}
        DO_ASYNC_SECTIONS=${ASYNC_SECTIONS}
    fi

    get_oraversion $SID

    # Do sync checks
    EXCLUDED=$(eval 'echo $EXCLUDE'"_$SID")
    SECTIONS=$(remove_excluded_sections "$DO_SYNC_SECTIONS" "$EXCLUDED")

    # Do async checks
    ASECTIONS=$(remove_excluded_sections "$DO_ASYNC_SECTIONS" "$EXCLUDED")
    ASYNC_SQL=$(for section in $ASECTIONS ; do eval "sql_$section" ; done)
    export ASYNC_SQL

    if [ "$DEBUGCONNECT" ] ; then
        echo "-----------------------------------------------"
        echo "Logincheck to Instance: "$SID"  Version: "$ORACLE_VERSION
        echo "select 'Login ok User: ' || user || ' on ' || host_name
              from v\$instance;" | sqlplus $SID
        echo "SYNC_SECTIONS=$SECTIONS"
        echo "ASYNC_SECTIONS=$ASECTIONS"
        # do not execute any check
        continue
    fi

    do_sync_checks $SID "$SECTIONS"

    run_cached_local oracle_$SID $CACHE_MAXAGE do_async_checks $SID

done
