# $Header: empexecuteSQL.pl 20-aug-2007.08:14:29 shnavane Exp $
#
# Copyright (c) 2002, 2007, Oracle. All rights reserved.  
#
#    DESCRIPTION
#      ECM script to execute a SQL script
#
#      For each database
#       -   Execute the SQL specified
#
#    USAGE
#      perl empexecuteSQL.pl <options>
#
#    options
#          -oh <ORACLE_HOME>
#          -os <List of SIDs(separated by ampersand(&)) running out 
#               the ORACLE_HOME>
#          -sql Absolute path to the SQL script
#
#    NOTES
#      <other useful comments,qualifications,etc>
#
#    MODIFIED     (MM/DD/YY)
#       shnavane   09/02/07 - Backport shnavane_bug-6024894 from main
#       vsriram    07/25/07 - Bug 5501067 - Handle the return values
#       shnavane   05/18/07 - Fix bug #6024894
#       bguhatha   06/07/07 - bug-5971223 
#       bguhatha   06/22/07 - Backport bguhatha_bug-5971223 from main
#       rsah       03/09/06 - Fix for bug 5064490.\. 
#       milshah    11/11/05 - change statusf to always right shift return code 
#       shgangul   09/21/05 - Remove warnings 
#       shgangul   07/14/05 - support extra unknown parameters, -asm <asm 
#                             instances> 
#       shgangul   06/08/05 - Execute the sql script directly
#       shgangul   04/29/05 - Spool sql errors and parse them 
#       mningomb   04/13/05 - mningomb_dbpatch
#       shgangul   04/08/05 - Creation
#

# --- Set up necessary variables for proper running of this environment ---
use strict;
use FileHandle;
use File::Basename();
use File::Path();
use File::Find();
use File::Spec();
use File::stat;
use User::pwent;
use XML::Parser;

my $scriptName   = File::Basename::basename($0);
my $osmScriptDir = File::Basename::dirname($0);
my $ecmCommon    = File::Spec->catfile($osmScriptDir, 'ecmCommon.pl');
#my $db_instance = File::Spec->catfile($ENV{EMDROOT}, "sysman", "admin", "scripts", "db", "db_instance.pl");
my $db_state = File::Spec->catfile($ENV{EMDROOT}, "sysman", "admin", "scripts", "db", "dbstate.pl");

my %elements;
my $seen = 0;
my $root;


require "$ecmCommon";
require "$db_state";
#require "$db_instance";

# ------ Initialize global variables -------------------------------------
use constant COPYRIGHT => "Copyright \251 2002, 2006, Oracle. All rights reserved.";
use constant VERSION   => '10.2.0.0.0';
use constant PTYPE_PATCH        => 'patch';
use constant PTYPE_PATCHSET     => 'patchset';
use constant TTYPE_HOST         => 'host';
use constant TTYPE_DB           => 'oracle_database';
use constant TTYPE_EMD          => 'oracle_emd';
use constant TTYPE_IAS          => 'oracle_ias';
use constant DEPOTROOT          => 'EMStagedPatches';
use constant S_DELAY     => '-delay';
use constant S_FORCE     => '-force';
use constant S_HELP      => '-help';
use constant S_JRE       => '-jre';
use constant S_JDK       => '-jdk';
use constant S_LOCAL     => '-local';
use constant S_MINIMIZEDOWNTIME => '-minimize_downtime';
use constant S_NOBUGSUPERSET    => '-no_bug_superset';
use constant S_NOINVENTORY      => '-no_inventory';
use constant S_OH               => '-oh';
use constant S_PATCHID          => '-patchid';
use constant S_RETRY            => '-retry';
use constant S_SILENT           => '-silent';
use constant S_VERSION          => '-version';
use constant S_VERBOSE          => '-verbose';
use constant S_INVPTRLOC        => '-invPtrLoc';
use constant S_ASM              => '-asm';
use constant S_EMPTY            => '';
use constant S_SPACE            => ' ';
use constant S_QUOTE            => '"';
use constant S_APOSTROPHE       => "'";
use constant KB                 => 1024;
use constant B_TRUE             => 1;
use constant B_FALSE            => 0;
use constant E_SUCCESS          => 0;
use constant E_FAIL             => 1 * (1 << 8);
use constant E_NO_ENV           => 2 * (1 << 8);
use constant E_NO_OPATCHPL      => 3 * (1 << 8);
use constant E_NO_INVENTORY     => 4 * (1 << 8);
use constant E_NO_COMMAND       => 5 * (1 << 8);
use constant E_INV_COMMAND      => 6 * (1 << 8);
use constant E_INV_ARG          => 7 * (1 << 8);
use constant E_TOO_MANY         => 8 * (1 << 8);
use constant E_MISSING_ARG      => 9 * (1 << 8);
use constant E_NOT_ENOUGH_SPACE => 10 * (1 << 8);
my $action          = S_EMPTY;     # ARGV[0]
my $ORACLE_HOME     = S_EMPTY;     # -oh /private/OraHome1
my $ORACLE_SID      = S_EMPTY;     # 
my @ORACLE_SIDs     = ();          # -os mjgdb817,msdfsjk,sdnms23
my @ORACLE_ASMs     = ();          # -os mjgdb817,msdfsjk,sdnms23
my $tns_admin       = S_EMPTY;     # Location of listener config file
my $sqlscript        = S_EMPTY;     # Location of sql script to execute
my $listener_list   = S_EMPTY;     # List of comma separated listeners
my @db_listeners    = ();          # List of listeners
my $shared          = 0;           # Whether shared or not
my $rac             = 0;           # Whether RAC setup or not
my $shutdown_type   = '15';        # -st 1=instance,2=listener,4=agent,8=startup
my $shutdown_sids   = S_EMPTY;     # -ss mjgdb817
my $shutdown_lsnrs  = S_EMPTY;     # -sl LISTENER
my $patch_id        = S_EMPTY;     #  -i 1390304
my $patch_size      = '0';         #  -s 5237
my $patch_file      = S_EMPTY;     #  -f p1390304.zip
my $patch_index     = '1';         #  -x 1..n
my $patch_count     = '1';         #  -c n
my $patch_lock      = 'PATCH.lck';
my $dbsnmp_lock     = 'DBSNMP.lck';
my $lsnr_lock       = 'LSNR.lck';
my $db_lock         = 'DB.lck';
my $db_name         = S_EMPTY;
my @db_instances    = ();
my $OS              = $^O;         # OS type (solaris|linux)
my $PERL            = $^X;         # Perl executable
my $PERL5LIB        = S_EMPTY;
my $EMDROOT         = S_EMPTY;
my $JAVA_HOME       = S_EMPTY;
my $OUILOC          = S_EMPTY;
my $isRAC           = B_FALSE;     # true if RAC patch
my $db_shutdown     = B_FALSE;     # true if database shutdown required
my $lsnr_shutdown   = B_FALSE;     # true if listener shutdown required
my $dbsnmp_shutdown = B_FALSE;     # true if Oracle agent shutdown required
my $isProduct       = B_FALSE;     # true if RTM release
my $isDebug         = B_FALSE;     # true if debugging
my $isHelp          = B_FALSE;     # true if help request
my $isLogging       = B_FALSE;     # true if logging output
my $isHeader        = B_FALSE;     # true if header printed
my $local_opt       = S_EMPTY;     # if -local specified
my $inv_loc         = S_EMPTY;     # invPtrLoc option for apply
my $isShared        = 0;           # if it is a shared home
my $Shared          = S_EMPTY;     # sharedMove string
my $INVPTRLOCFILE   = 'oraInst.loc';
my $INVPTRLOCFILEPATH = S_EMPTY;

my $file;
my $item;
my @lit;
my $count;
my @loc;
my $script_loc;
my $m_sql_script;
my $m_cpu_name;
my $output_string;
my @tokens;
my $str;
my $len;

# --------------------- Command-line arguments --------------------------
use constant CTYPE_HELP     => 'help';
use constant CTYPE_APPLY    => 'applyPatch';
use constant CTYPE_CHECK    => 'checkTarget';
use constant CTYPE_EXPAND   => 'expandPatch';
use constant CTYPE_SHOW     => 'showResults';
use constant CTYPE_SHUTDOWN => 'shutdown';
use constant CTYPE_STARTUP  => 'startup';
my $CT     = '-c';                 #  -c count
my $CF     = '-cf';                # -cf
my $DP     = '-d';                 #  -d directory
my $PF     = '-f';                 #  -f patchfile
my $DB     = '-g';                 #  -g debug
my $HP     = '-h';                 #  -h
my $ID     = '-i';                 #  -i patchid
my $OH     = '-oh';                # -oh ORACLE_HOME
my $SID    = '-os';                # -os ORACLE_SID
my $PT     = '-p';                 #  -p patchtype
my $QT     = '-q';                 #  -q
my $SI     = '-s';                 #  -s size
my $SL     = '-sl';                # -sl shutownlisteners
my $SS     = '-ss';                # -ss shutdownsids
my $ST     = '-st';                # -st shutdownttype
my $TT     = '-t';                 #  -t targettype
my $PI     = '-x';                 #  -x index
my $VE     = '-v';                 #  -v
my $SHOME  = '-sharedMove';        #  -sharedMove, an unary operator
my %OPTV   = ();
my %UNARYS = ();
my %DEFS   = ();
my %ARGS   = ();
my %CMDS   = ();

# --------------------- OSD platform-specific ---------------------------
my $DF           = '/usr/bin/df';
my $dfOpt        = '-k';
my $ECHO         = '/usr/bin/echo';             # obsolete
my $EGREP        = '/usr/bin/egrep';            # obsolete
my $EMUNZIP      = 'emunzip';
my $EMZIP        = 'emzip';
my $LS           = '/usr/bin/ls';
my $AWK          = '/usr/bin/awk';              # obsolete
my $NULL_DEVICE  = '/dev/null';
my $PS           = '/usr/bin/ps';               # obsolete
my $LSNRCTL      = 'lsnrctl';                   # obsolete
my $SHELL        = '/bin/sh';
my $SQLPLUS      = 'sqlplus';                   # obsolete
my $SRVCTL       = 'srvctl';                    # obsolete
my $TAR          = '/usr/bin/tar';
my $tarOpt       = 'xvf';
my $UNZIP        = '/usr/bin/unzip';
my $unzipOpt     = '-o';
my $ZIP          = '/usr/bin/zip';
my $zipOpt       = S_EMPTY;
my $oratab       = '/var/opt/oracle/oratab';    # obsolete
my $BAT_SUFFIX   = S_EMPTY;
my $EXE_SUFFIX   = S_EMPTY;
my $CLASSPATHSEP = ':';
my $PATHSEP      = ':';
my $FILESEP      = '/';
my $DEF_PATH     =
  '/bin:/sbin:/usr/bin:/usr/sbin:/etc:/usr/etc:/usr/ccs/bin:/usr/ucb';
my $DEF_LD_LIBRARY_PATH = '/usr/lib';
my $DEF_JAVA_HOME       = '/usr/local/packages/jdk1.3.1';

# --------------------- Subroutines -------------------------------------

# setupOSD()
#
# Setup OSD commands
#
sub setupOSD
{
    if (onWindows())
    {
        $ECHO                = 'echo';
        $LS                  = 'dir';
        $NULL_DEVICE         = 'NUL';
        $SHELL               = 'cmd.exe /c';
        $TAR                 = 'tar.exe';
        $UNZIP               = 'unzip.exe';
        $ZIP                 = 'zip.exe';
        $BAT_SUFFIX          = '.bat';
        $EXE_SUFFIX          = '.exe';
        $CLASSPATHSEP        = ';';
        $PATHSEP             = ';';
        $FILESEP             = '\\';
        $DEF_PATH            = $ENV{'PATH'};
        $DEF_LD_LIBRARY_PATH = $ENV{'PATH'};
        $DEF_JAVA_HOME       = $ENV{'JAVA_HOME'};
        $ENV{'DIRCMD'}       = '';
        $ENV{'COPYCMD'}      = '';
    }
    else
    {
        if (!equalsIgnoreCase('solaris', $OS)
            && !equalsIgnoreCase('linux', $OS))
        {
            $dfOpt = '-Pk';
        }
        if (!equalsIgnoreCase('solaris', $OS))
        {
            $oratab = '/etc/oratab';
        }
        if (equalsIgnoreCase('hpux', $OS))
        {
           $DF =  '/usr/bin/bdf';
           $dfOpt = '';
        }
        if (equalsIgnoreCase('linux', $OS))
        {
            $DF    = '/bin/df';
            $ECHO  = '/bin/echo';
            $EGREP = '/bin/egrep';
            $LS    = '/bin/ls';
            $AWK   = '/bin/awk';
            $PS    = '/bin/ps';
            $TAR   = '/bin/tar';
        }
        elsif (equalsIgnoreCase('darwin', $OS))
        {
            $DF    = '/bin/df';
            $ECHO  = '/bin/echo';
            $LS    = '/bin/ls';
            $PS    = '/bin/ps';
        }
    }
}

#
# logf(<message>)
#
# Display the message with timestamp
#
#
sub logf($) 
{
    my ($msg) = @_;

    printf("\n%s - %s\n", scalar(localtime()), $msg);
}


# isRunning(<proc_match>)
#
# Return true if process is running
#
sub isRunning($)
{
    my ($proc_match) = @_;

    return (system("$PS -e -o args | $EGREP -s \'$proc_match\'") == E_SUCCESS);
}

# initOptions()
#
# Setup known options for parsing
#
sub initOptions
{
    $CMDS{lc(CTYPE_HELP)}   = '1';
    $CMDS{lc(CTYPE_APPLY)}  = '1';
    $CMDS{lc(CTYPE_CHECK)}  = '1';
    $CMDS{lc(CTYPE_EXPAND)} = '1';
    $CMDS{lc(CTYPE_SHOW)}   = '1';

    #$CMDS{lc(CTYPE_SHUTDOWN)} = '1';
    #$CMDS{lc(CTYPE_STARTUP)}  = '1';

    $OPTV{$CT}   = 'count';
    $DEFS{$CT}   = '1';              # -c
    $OPTV{$DP}   = 'directory';
    $DEFS{$DP}   = DEPOTROOT;        # -d
    $OPTV{$PF}   = 'patchfile';      # -f
    $OPTV{$DB}   = 'debug';
    $DEFS{$DB}   = '0';              # -g
    $OPTV{$HP}   = 'help';
    $UNARYS{$HP} = '1';              # -h
    $OPTV{$ID}   = 'patchid';        # -i
    $OPTV{$OH}   = 'ORACLE_HOME';    # -oh
    $OPTV{$SID}  = 'ORACLE_SID';
    $DEFS{$SID}  = 'none';           # -os
    $OPTV{$PT}   = 'patchtype';
    $DEFS{$PT}   = PTYPE_PATCH;      # -p
    $OPTV{$SI}   = 'size';
    $DEFS{$SI}   = '0';              # -s
    $OPTV{$SL}   = 'listeners';
    $DEFS{$SL}   = ' ';              # -sl
    $OPTV{$SS}   = 'ids';
    $DEFS{$SS}   = ' ';              # -ss
    $OPTV{$ST}   = 'shutdowntype';
    $DEFS{$ST}   = '15';             # -st
    $OPTV{$TT}   = 'targettype';
    $DEFS{$TT}   = TTYPE_DB;         # -t
    $OPTV{$PI}   = 'index';
    $DEFS{$PI}   = '1';              # -x
    $OPTV{$VE}   = 'version';
    $UNARYS{$VE} = '1';              # -v
    $OPTV{$CF}   = 'checkfree';
    $UNARYS{$CF} = '1';              # -cf
    $OPTV{$QT}   = 'quiet';
    $UNARYS{$QT} = '1';              # -q
    $OPTV{$SHOME} = 'sharedMove';    # -sharedMove
    $UNARYS{$SHOME} = '1';           # -sharedMove
    $DEFS{$SHOME} = '0';             # default is not-shared
}

# parseArgs()
#
# Store all the arguments in hashed table
#
sub parseArgs
{

    # Initialize the parameters;

    my $opt      = S_EMPTY;
    my $argcount = scalar(@ARGV);

    logf("Arguments: @ARGV");

    my $i = 0;
    while ($i < $argcount)
    {
        $opt = $ARGV[$i];
        if ($opt eq "-oh")
        {
            $i ++;
            $ORACLE_HOME = $ARGV[$i];
        }
        elsif ($opt eq "-os")
        {
            $i ++;
            # Parse the SID list and store it in SID array
            my $sidlist = $ARGV[$i];
            chomp $sidlist;
            @ORACLE_SIDs = split(/&/, $sidlist);

            # Get a list of uniq SIDs
            my @tempsids = ();
            for(my $sidi = 0; $sidi < scalar(@ORACLE_SIDs); $sidi++)
            {
                my $uniq = 1;
                for(my $sidj = 0; $sidj < scalar(@tempsids); $sidj++)
                {
                    if ($ORACLE_SIDs[$sidi] eq $tempsids[$sidj])
                    {
                        $uniq = 0;
                        last;
                    }
                }

                if ($uniq == 1)
                {
                    push(@tempsids, $ORACLE_SIDs[$sidi]);
                }
            }
            @ORACLE_SIDs = @tempsids;
        }
        elsif ($opt eq "-sql")
        {
            $i ++;
            $sqlscript = $ARGV[$i];
        }
        elsif ($opt eq S_ASM)
        {
            $i ++;
            # Parse the ASM list and store it in ASM array
            my $asmlist = $ARGV[$i];
            chomp $asmlist;
            @ORACLE_ASMs = split(/&/, $asmlist);

            # Get a list of uniq ASMs
            my @tempasms = ();
            for(my $asmi = 0; $asmi < scalar(@ORACLE_ASMs); $asmi++)
            {
                my $uniq = 1;
                for(my $asmj = 0; $asmj < scalar(@tempasms); $asmj++)
                {
                    if ($ORACLE_ASMs[$asmi] eq $tempasms[$asmj])
                    {
                        $uniq = 0;
                        last;
                    }
                }

                if ($uniq == 1)
                {
                    push(@tempasms, $ORACLE_ASMs[$asmi]);
                }
            }
            @ORACLE_ASMs = @tempasms;
        }
        else
        {
            printHeader($0, $#ARGV) if (!$isHeader);
            #abort($action, statusf(E_INV_ARG), "Invalid argument: $opt");
            printf("Following argument not supported: $opt");
        }
        
        $i ++; # increment the count
    }

    if ($ORACLE_HOME eq S_EMPTY)
    {
        abort($action, statusf(E_INV_ARG), "Oracle Home not set");
    }

    if ($sqlscript eq S_EMPTY)
    {
        abort($action, statusf(E_INV_ARG), "SQL script not specified");
    }

    if (! -f $sqlscript  && $sqlscript ne 'defaultSqlScript')
    {
        abort($action, statusf(E_INV_ARG), "SQL script does not exist");
    }
    
    if (scalar(@ORACLE_SIDs) == 0)
    {
        abort($action, statusf(E_INV_ARG), "No SIDs are specified");
    }
}

# isDBRunning(<sid>)
#
# Return true if db is running
#
sub isDBRunning($)
{
    my ($sid) = @_;

    my $old_sid   = $ENV{'ORACLE_SID'};
    my @lines     = ();
    my $isRunning = B_FALSE;

    $ENV{'ORACLE_SID'} = $sid;
    chomp(@lines =
          `$ECHO "SET pagesize 0\\nSET tab off\\nSELECT 1 FROM sys.dual;\\n" | $SQLPLUS -S '/ as sysdba'`
          );
    $ENV{'ORACLE_SID'} = $old_sid;

    if ($#lines >= 0)
    {
        $lines[0] =~ s/ //g;
        $isRunning = B_TRUE if (isEqual('1', $lines[0]));
    }

    return $isRunning;
}

# createLock(<lock_file>,<lock_id>)
#
# Place a marker in the depot to indicate patching state
#
sub createLock($$)
{
    my ($lock_file, $lock_id) = @_;

    my $lock_path = File::Basename::dirname($lock_file);

    File::Path::mkpath($lock_path, 0, 0775) if (!-d "$lock_path");
    open(OUTPUT, "> $lock_file")
      or abort($action, statusf($?),
               "Could not open lockfile $lock_file to write: $!");
    printf(OUTPUT "$lock_id\n");
    close(OUTPUT);
}

# isLocked(<lock_file>)
#
# Test a marker in the depot to indicate patching state
#
sub isLocked($)
{
    my ($lock_file) = @_;

    return (-f $lock_file);
}

# findLocks(<path>,<mask>)
#
# Return list of lock files
#
sub findLocks($$)
{
    my ($path, $mask) = @_;

    my @locks   = ();
    my $pattern = File::Spec->catfile($path, $mask);

    if (onWindows())
    {
        $pattern =~ s~/~\\~g;
        chomp(@locks = `DIR /B $pattern 2> $NULL_DEVICE`);
        for (my $i = 0 ; $i <= $#locks ; $i++)
        {
            $locks[$i] = File::Spec->catfile($path, $locks[$i]);
        }
    }
    else
    {
        chomp(@locks = `$LS -1 $pattern 2> $NULL_DEVICE`);
    }

    return @locks;
}

# getDB_NAME(<lock_file>)
#
# Find db_name for use in shutting down RAC instances
#
sub getDB_NAME($)
{
    my ($lock_file) = @_;

    my @lines = ();

    if (!isEmpty($lock_file))
    {
        open(INPUT, "< $lock_file");
        while (<INPUT>)
        {
            chomp;
            $db_name = $_ if (!isEmpty($_));
        }
        close(INPUT);

        return;
    }

    chomp(@lines =
          `$ECHO "SET pagesize 0\\nSET tab off\\nSELECT SYS_CONTEXT(\'USERENV\',\'DB_NAME\') FROM sys.dual;\\n" | $SQLPLUS -S '/ as sysdba'`
          );

    if ($#lines >= 0)
    {
        $lines[0] =~ s/ //g;
        $db_name = $lines[0];
    }
}

# getDB_LISTENERS()
#
# Populate db_listeners for use in shutting down all Oracle listeners
#
sub getDB_LISTENERS()
{

    my $lsnr  = S_EMPTY;
    my @lines = ();

    # Listener config file
    my $list_config_file = File::Spec->catfile($tns_admin, "listener.ora");
    print "Geting teh listenres  file=$list_config_file\n";
    if (! -e $list_config_file )
    {
        return;
    }

    # Parse the listener.ora file to get the list of listeners
    local *LISTDATA;
    open(LISTDATA, "<$list_config_file")
      or abort($action, statusf($?),
               "Could not open listerner config file \'$list_config_file\'");
    my @ldata = <LISTDATA>;
    close(LISTDATA);

    foreach my $line (@ldata)
    {
        chomp $line;
        print "Reading line=$line\n";
        if ($line =~ m#^LISTENER(.*)=(\s*)$#)
        {
        print "Special Reading line=$line\n";
            my @linewords = split(/ /, $line);
            if ($linewords[0] eq "") {next;}
            if ($linewords[0] =~ m#^\(#) {next;}
            if ($linewords[0] =~ m#^SID_LIST_#) {next;}
            push (@db_listeners, $linewords[0]);
        }
    }

    logf ("List of listeners detected: @db_listeners");
}


# createInitOra(<sid>,<initora>)
#
# Create an init$ORACLE_SID.ora file for use in startup
#
sub createInitOra($$)
{
    my ($sid, $initora) = @_;

    my $name  = S_EMPTY;
    my $value = S_EMPTY;
    my @lines = ();

    open(OUTPUT, "> $initora")
      or abort($action, statusf($?),
               "Could not open initfile $initora to write: $!");

    printf(OUTPUT "# $initora generated on %s\n", scalar(localtime()));

    chomp(@lines =
          `$ECHO "SET pagesize 0\\nSET tab off\\nSET linesize 512\\nSELECT CONCAT(CONCAT(name,'='),value) FROM V\\\$PARAMETER WHERE isdefault='FALSE' ORDER BY name;\\n" | $SQLPLUS -S '/ as sysdba'`
          );

    for (my $i = 0 ; $i <= $#lines ; $i++)
    {
        $lines[$i] =~ s/ //g;
        next if (!$lines[$i] =~ m/=/);

        ($name, $value) = split (/=/, $lines[$i], 2);
        next if (!defined $value);

        if ($value =~ m/,/)
        {
            $value =~ s/,/","/g;
            $value = '"' . $value . '"';
        }
        elsif ($value =~ m/=/)
        {
            $value = '"' . $value . '"';
        }
        if ($name =~ m/control_files/i)
        {
            $value = '(' . $value . ')';
        }
        else
        {
            $value = '""' if (isEmpty($value));
        }

        printf(OUTPUT "%s=%s\n", $name, $value);
    }
    printf(OUTPUT "# end of generated file\n");
    close(OUTPUT);
}

# shutdownLSNR(<name>)
#
# Shutdown database listener <name> if running
# Create shutdown marker to indicate what we did
#
sub shutdownLSNR($)
{
    my ($name) = @_;

    $! = E_SUCCESS;
    logf("Stopping Oracle database listener ${name}...");
    echodo("$LSNRCTL stop $name");
    if ($! != E_SUCCESS)
    {
      logf("Could not shutdown database listener: $!");
    }
}

# shutdownDBSNMP()
#
# Shutdown Oracle Agent if running
# Create shutdown marker to indicate what we did
#
sub shutdownDBSNMP
{
    my $DBSNMP   = 'dbsnmp';
    my $AGENTCTL = "$LSNRCTL dbsnmp_stop";
    my $status   = E_SUCCESS;

    if (-f File::Spec->catfile($ORACLE_HOME, 'bin', 'agentctl'))
    {
        $DBSNMP = File::Spec->catfile($ORACLE_HOME, 'bin', $DBSNMP);
        $AGENTCTL =
          File::Spec->catfile($ORACLE_HOME, 'bin', 'agentctl') . ' stop';
    }

    if (isRunning("^${DBSNMP}\$"))
    {
        printf(STDOUT "Stopping Oracle agent...\n");
        $status = echodo("$AGENTCTL");

        $status == E_SUCCESS
          or abort($action, statusf($?),
                   "Could not shutdown Oracle agent: $!");

        createLock($dbsnmp_lock, 'AGENT');
    }
}

# executesqlSID(<sid>, <script location>)
#
# Shutdown database instance if running
# Create shutdown marker to indicate what we did
#
sub executesqlSID($$)
{
    my ($sid, $sqlpscript) = @_;
    my $retval = 0;

    our $oracle_home=$ORACLE_HOME;
    our $oracle_sid=$sid;
    our $db_name="";
    our $username="";
    our $password="";
    our $role="sysdba";
    our $arrayElem="";
    our @SQLCMDS=();
    our $IGNORE_SQL_ERROR=0;

    # Spool name OH/<sid>_sql.lst
    my $spool_name = $sid . "_sql.lst";
    my $spool_file = File::Spec->catfile($ORACLE_HOME, $spool_name);

    open(script_txt_handle, $sqlscript) || abort($action, statusf(E_INV_ARG), "Could not read the SQL script ($sqlscript)");
    close script_txt_handle;
    open(spool_out, ">$spool_file") || abort($action, statusf(E_INV_ARG), "Could not open file ($sqlscript) to write SQL script output");
    close spool_out;

    # Start spooling
    $arrayElem = "\@$sqlscript\n";
    @SQLCMDS=(@SQLCMDS,$arrayElem);

    my $connStr = constructConnectStr($username, $password, "", $role);
    $retval = executeSQLPlus($connStr, $spool_file);
    logf ("SQL script $sqlscript returned with ErrorCode=$retval");

    # Parse the SQL out for errors
    open(spool_out, "$spool_file") || abort($action, statusf(E_INV_ARG), "Could not open file ($sqlscript) to write SQL script output");
    my @spool_lines = <spool_out>;
    logf("Output for execution of SQL script: $sqlscript");
    my $spool_errors = "";
    foreach my $spool_line (@spool_lines)
    {
        chomp $spool_line;
        print "$spool_line\n";
        if ($spool_line =~ m#^ORA-[0-9]+.*#)
        {
            my @errors = split (/: /, $spool_line);
            my $ora_error = $errors[0];
            chomp $ora_error;
            if ( !($ora_error eq "ORA-29558") && 
                 !($ora_error eq "ORA-06512")
               )
            {
                $spool_errors = $spool_errors . $spool_line . "\n";
                $retval = E_FAIL;
            }
        }
    }
    logf("End of output for execution of SQL script: $sqlscript");
    if ($retval != 0)
    {
        logf ("Error detected: \n$spool_errors");
    }

    return $retval;
}

# setShutdownTypes()
#
# Set up requested shutdown/startup types
#
sub setShutdownTypes
{
    my $sid   = S_EMPTY;
    my $lsnr  = S_EMPTY;
    my $count = 0;

    if (equalsIgnoreCase(CTYPE_SHUTDOWN, $action))
    {
        $db_shutdown     = B_TRUE if ($shutdown_type & 1);
        $lsnr_shutdown   = B_TRUE if (($shutdown_type & 2) && !$isRAC);
        $dbsnmp_shutdown = B_TRUE if ($shutdown_type & 4);
    }
    elsif (equalsIgnoreCase(CTYPE_STARTUP, $action) && ($shutdown_type & 8))
    {
        $db_shutdown     = B_TRUE if ($shutdown_type & 1);
        $lsnr_shutdown   = B_TRUE if (($shutdown_type & 2) && !$isRAC);
        $dbsnmp_shutdown = B_TRUE if ($shutdown_type & 4);
    }

    if ($db_shutdown && !isEmpty($shutdown_sids))
    {
        $count = $#db_instances;
        $shutdown_sids =~ s/ //g;
        LOOP: foreach $sid (split (/,/, $shutdown_sids))
        {
            next LOOP if (isEmpty($sid));
            for (my $i = 0 ; $i <= $count ; $i++)
            {
                next LOOP if (isEqual($sid, $db_instances[$i]));
            }
            $count += 1;
            $db_instances[$count] = $sid;
        }
    }

    if ($lsnr_shutdown && !isEmpty($shutdown_lsnrs))
    {
        $count = $#db_listeners;
        $shutdown_lsnrs =~ s/ //g;
        LOOP: foreach $lsnr (split (/,/, $shutdown_lsnrs))
        {
            next LOOP if (isEmpty($lsnr));
            for (my $i = 0 ; $i <= $count ; $i++)
            {
                next LOOP if (isEqual($lsnr, $db_listeners[$i]));
            }
            $count += 1;
            $db_listeners[$count] = $lsnr;
        }
    }
}

# startupDBSNMP()
#
# Start Oracle agent if it was running
# Remove shutdown marker to indicate the current state
#
sub startupDBSNMP
{
    my $DBSNMP   = 'dbsnmp';
    my $AGENTCTL = "$LSNRCTL dbsnmp_start";
    my $status   = E_SUCCESS;

    if (-f File::Spec->catfile($ORACLE_HOME, 'bin', 'agentctl'))
    {
        $DBSNMP = File::Spec->catfile($ORACLE_HOME, 'bin', $DBSNMP);
        $AGENTCTL =
          File::Spec->catfile($ORACLE_HOME, 'bin', 'agentctl') . ' start';
    }

    if (isLocked($dbsnmp_lock))
    {
        if (!isRunning("^${DBSNMP}\$"))
        {
            $! = E_SUCCESS;
            printf(STDOUT "Starting Oracle agent...\n");
            $status = echodo("$AGENTCTL");

            $status == E_SUCCESS
              or abort($action, statusf($?),
                       "Could not startup Oracle agent: $!");
        }
        unlink($dbsnmp_lock);
    }
}

# addPATH(<new_path>)
#
# Add the directory at the beginning of PATH
#
sub addPATH($)
{
    my ($new_path) = @_;

    my $PATH = $ENV{'PATH'};

    if (onWindows())
    {
        $new_path =~ s~/~\\~g;
    }

    if (isEmpty($PATH))
    {
        $PATH = $DEF_PATH;
    }
    else
    {
        if (onWindows())
        {
            $new_path =~ s~\\~\\\\~g;
        }
        $PATH =~ s~(^|${PATHSEP})${new_path}(${PATHSEP}|$)~${PATHSEP}~g;
        $PATH =~ s~(^${PATHSEP}|${PATHSEP}$)~~g;
        if (onWindows())
        {
            $new_path =~ s~\\\\~\\~g;
        }
    }
    $ENV{'PATH'} = "${new_path}${PATHSEP}${PATH}";
}

# addLDPATH(<NEW_LDPATH>)
#
# Add the directory at the beginning of LD_LIBRARY_PATH
#
sub addLDPATH($)
{
    my ($new_ldpath) = @_;

    my $LD_LIBRARY_PATH = $ENV{'LD_LIBRARY_PATH'};

    if (onWindows())
    {
        addPATH($new_ldpath);
        return;
    }

    if (isEmpty($LD_LIBRARY_PATH))
    {
        $LD_LIBRARY_PATH = $DEF_LD_LIBRARY_PATH;
    }
    else
    {
        $LD_LIBRARY_PATH =~
          s~(^|${PATHSEP})${new_ldpath}(${PATHSEP}|$)~${PATHSEP}~g;
        $LD_LIBRARY_PATH =~ s~(^${PATHSEP}|${PATHSEP}$)~~g;
    }
    $ENV{'LD_LIBRARY_PATH'} = "${new_ldpath}${PATHSEP}${LD_LIBRARY_PATH}";
}

# oraenv(<oracle_home>,<oracle_sid>)
#
# Set ORACLE_HOME environment
#
sub oraenv($$)
{
    ($ORACLE_HOME, $ORACLE_SID) = @_;

    $ORACLE_HOME = trim($ORACLE_HOME);
    $ORACLE_SID  = trim($ORACLE_SID);
    @db_instances = split (/,/, $ORACLE_SID);
    $ORACLE_SID = trim($db_instances[0]);
    $db_name = $ORACLE_SID;
    $LSNRCTL = File::Spec->catfile($ORACLE_HOME, 'bin', $LSNRCTL);
    $SQLPLUS = File::Spec->catfile($ORACLE_HOME, 'bin', "$SQLPLUS$EXE_SUFFIX");
    $SRVCTL  = File::Spec->catfile($ORACLE_HOME, 'bin', $SRVCTL);
    if (-f $SRVCTL)
    {
        $isRAC = B_TRUE;
    }
    my %oldenv = %ENV;
    if (!onWindows())
    {
        %ENV = ();

        #   my ($login,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) = getpwuid($>);
        my $pw = getpw($>);
        $ENV{'HOME'}    = $pw->dir;
        $ENV{'LOGNAME'} = $pw->name;
        $ENV{'USER'}    = $pw->name;
        $ENV{'LC_ALL'}  = 'C';
    }
    $ENV{'EMDROOT'}     = $EMDROOT;
    $ENV{'PERL5LIB'}    = $PERL5LIB;
    $ENV{'JAVA_HOME'}   = $JAVA_HOME;
    $ENV{'PWD'}         = $oldenv{'PWD'} if (defined $oldenv{'PWD'});
    $ENV{'SHELL'}       = $SHELL;
    $ENV{'TZ'}          = $oldenv{'TZ'} if (defined $oldenv{'TZ'});
    $ENV{'ORACLE_HOME'} = $ORACLE_HOME;
    $ENV{'ORACLE_SID'}  = $ORACLE_SID;
    $ENV{'TNS_ADMIN'}   = File::Spec->catfile($ORACLE_HOME, 'network', 'admin');

    $ENV{'PATH'} = S_EMPTY;
    addPATH(File::Spec->catfile($JAVA_HOME,   'bin'));
    addPATH(File::Spec->catfile($ORACLE_HOME, 'bin'));

    $ENV{'LD_LIBRARY_PATH'} = S_EMPTY;
    addLDPATH(File::Spec->catfile($ORACLE_HOME, 'network', 'lib'));
    addLDPATH(File::Spec->catfile($ORACLE_HOME, 'lib'));

    #printf(STDOUT "PATH = %s\nLD_LIBRARY_PATH = %s\n",$ENV{'PATH'},$ENV{'LD_LIBRARY_PATH'});
}

# emdenv()
#
# Set EMD environment
#
sub emdenv
{
    $EMDROOT = $ENV{'EMDROOT'};
    if (isEmpty($EMDROOT))
    {
        abort($action, statusf(E_INV_ARG), "EMDROOT not set");
        # ($EMDROOT = $osmScriptDir) =~ s/.sysman.admin.scripts.osm$//;
    }
    $JAVA_HOME = $ENV{'JAVA_HOME'};
    if (isEmpty($JAVA_HOME))
    {
        $JAVA_HOME = $DEF_JAVA_HOME;
    }
    $UNZIP = File::Spec->catfile($EMDROOT, 'bin', "unzip$EXE_SUFFIX");
    if (!-x $UNZIP)
    {
        $UNZIP = File::Spec->catfile($EMDROOT, 'bin', "$EMUNZIP$BAT_SUFFIX");
    }
    if (!-x $UNZIP)
    {
        $UNZIP = 'unzip';
    }
    $ZIP = File::Spec->catfile($EMDROOT, 'bin', "zip$EXE_SUFFIX");
    if (!-x $ZIP)
    {
        $ZIP = File::Spec->catfile($EMDROOT, 'bin', "$EMZIP$BAT_SUFFIX");
    }
    if (!-x $ZIP)
    {
        $ZIP = 'zip';
    }
    $OUILOC   = $ENV{'OUILOC'};
    $PERL5LIB = getPERL5LIB();
}

# usage()
#
# Display a usage help page
#
sub usage
{
    if (defined $ARGS{$VE})
    {
        printf(STDOUT "\n%s Version %s\n%s\n\n", $scriptName, VERSION, COPYRIGHT);
        cleanAndExit(0);
    }

    # Start of "here doc".
    print STDOUT <<_EOM_
    USAGE
      perl empexecuteSQL.pl <options>

    options
          -oh <ORACLE_HOME>
          -os <List of SIDs(separated by ampersand(&)) running out 
               the ORACLE_HOME>
          -sql Absolute path to the SQL script

Examples:

  empexecuteSQL.pl -oh /private/OraHome1 -os smcore1&smcore2
                -sql /private/OraHome1/apply.sql

_EOM_
      ;

}

# statusf(<status>)
#
# Returns the termination status of command
#
sub statusf($)
{
    my ($status) = @_;

    $status = E_FAIL if (!defined $status);
    $status = ($status >> 8);
    return $status;
}

# cleanAndExit(<status>)
#
# Clean up and terminate with status
#
sub cleanAndExit($)
{
    my ($status) = @_;

    exit statusf($status);
}

# subroutines to parse the patch metadata file
# The following routines parse an XML file to construct a node tree
# Based on the parser utility functions on www.xml.com
# http://www.xml.com/pub/a/98/09/xml-perl.html

sub COUNT () {0;}
sub MINLEV () {1;}
sub SEEN  () {2;}
sub CHARS () {3;}
sub EMPTY () {4;}
sub PTAB  () {5;}
sub KTAB  () {6;}
sub ATAB  () {7;}
sub VTAB  () {8;}


sub new
{
    my $class = shift;
    my $self = [0, undef, 0, 0, 1, {}, {}, {},{}];
    
    bless $self, $class;
}

# The start handler subroutine. This creates the node tree, and does the  parsing of the XML file.
# The first 2 parameters passed to a start handler are the parser object and the name of the element just started. 
# After shifting these two things off the list, we lookup what information we've got for that element in the %elements hashtable.
# If there's no object there, we create one, and set the SEEN field for it.
# The $seen variable gets incremented for each new element type we see. 
# Refer to http://www.xml.com/pub/a/98/09/xml-perl.html for more explanation

sub start_handler
{
    my $p = shift;
    my $el = shift;
    
    my $elinf = $elements{$el};


    if (not defined($elinf))
    {
        $elements{$el} = $elinf = new;
        $elinf->[SEEN] = $seen++; 
    }
    
    $elinf->[COUNT]++;

    my $partab = $elinf->[PTAB];

    my $parent = $p->current_element;
    if (defined($parent))
    {
        # store parent info
        $partab->{$parent}++;
        my $pinf = $elements{$parent};

        # Increment our slot in parent's child table
        $pinf->[KTAB]->{$el}++;
        $pinf->[EMPTY] = 0;
    }
    else
    {
        $root = $el;
    }

    # Deal with attributes

    my $atab = $elinf->[ATAB];
    my $vtab = $elinf->[VTAB];

    my $flag = "0";
    while (@_)
    {
                                                                                       
       # Read the attribute name and its value, and store the value in a comma seperated
        # list asscoiated with the attribute name
        my $att = shift;

        my $val = shift;
        $atab->{$att} = $atab->{$att}.",".$val; # store the value in a comma separated list.
        if($att eq "defaultValue")
        {
          $flag = "1";
        }

    }
    # If no default value provided for attribute, put '_' as default value.
    if( $flag eq "0")
    {
       $atab->{"defaultValue"} = $atab->{"defaultValue"}.","."_";
    }

}  # End start_handler

# subroutine to set the level of each node in the tree
sub set_minlev
{
    my ($el, $lev) = @_;

    my $elinfo = $elements{$el};
    if (! defined($elinfo->[MINLEV]) or $elinfo->[MINLEV] > $lev)
    {
        my $newlev = $lev + 1;

        $elinfo->[MINLEV] = $lev;
        foreach (keys %{$elinfo->[KTAB]})
        {
            set_minlev($_, $newlev);
        }
    }
}  # End set_minlev

sub bystruct
{
    my $refa = $elements{$a};
    my $refb = $elements{$b};

    $refa->[MINLEV] <=> $refb->[MINLEV]
        or $refa->[SEEN] <=> $refb->[SEEN];
}  # End bystruct

#subroutine to analyze the parsed metadata file 
sub sql_file 
{

  my $parser = new XML::Parser(ErrorContext => 2); # create an instance of the XML parser 

  # Associate start handler
  $parser->setHandlers(Start => \&start_handler
                    );

  # parse the file
  $parser->parsefile($file);

  # set the level of the parsed tree root to 0
  set_minlev($root, 0);

  my $el;

  # loop over all the nodes of the parsed tree
  foreach $el (sort bystruct keys %elements)
  {
    my $ref = $elements{$el};

    # for 'parameter' nodes, look at the script_loc attribute value, and find the corresponding value in the 'assignment' attribute.
    if($el eq "proc:Parameter")
   {
        my @list = %{$ref->[ATAB]};
        my $pos = 0;
        my $size = @list;
        # for every item in the list of attributes of the 'parameter node', look for the one whose attribute value is script_loc. 
        # Mark its position in the variable 'pos'.
        foreach $item (@list)
        {
           @lit = split(/,/, $ref->[ATAB]->{$item});
           for ($count=@lit; $count>0; $count--)
           {
                 if($lit[$count] eq "script_loc")
                 {
                      $pos = $count;
                      last;

                 }
           }
        }
        # Now look at the attribute values for the attribute 'assignment' 
        @loc = split(/,/, $ref->[ATAB]->{"assignment"});
        # The desired SQL script location is the value at the location 'pos'.
        $script_loc = $loc[$pos];
     }

    # for 'Argument' nodes, look at the sql_script and cpu_name attribute values
     if($el eq "proc:Argument")
     {
        my @list = %{$ref->[ATAB]};
        my $pos1 = 0;
        my $pos2 = 0;
        my $size = @list;
        foreach $item (@list)
        {

           @lit = split(/,/, $ref->[ATAB]->{$item});
           # Mark the positions of 'sql_script' and 'cpuname' attributes in the list of attributes.
           for ($count=@lit; $count>0; $count--)
           {
                 if($lit[$count] eq "sql_script")
                 {
                      $pos1 = $count;
                 }
                 if($lit[$count] eq "cpuname")
                 {
                      $pos2 = $count;
                 }
           }
        }
        # read the default values assigned to sql_script and cpuname
        @loc = split(/,/, $ref->[ATAB]->{"defaultValue"});
        $m_sql_script = $loc[$pos1];
        $m_cpu_name = $loc[$pos2];
     }

  }

  $output_string =  $script_loc."/".$m_sql_script;
   @tokens = split(/\//, $output_string);
   $str = "";
   # substitute A$oracle_home by $oracle_home, and A$cpuname by the default name of the cpu.
   foreach $item (@tokens)
   {
     if($item eq 'A$oracle_home' )
     {
       $str = $str.'$oracle_home'.'/';
     }
     elsif($item eq 'A$cpuname')
     {
       $str = $str.$m_cpu_name.'/';
     }
     else
     {
      $str = $str.$item.'/';
     }

   }
   # Remove the last '/'
   $len = length($str);
   $len = $len - 1;
   $str = substr($str,0,$len);
   return $str
}



# --------------------- Main program ----------------------------------
setOutputAutoflush();

parseArgs();

#
# Check for help requested
#
if ($isHelp)
{
    usage();
    cleanAndExit(0);
}

#
# Set the EMDROOT environment
#
setupOSD();
emdenv();


# If the user chose to apply the default sql script, look at the patch metadata file to find
# the sql script and its location
if($sqlscript eq 'defaultSqlScript')
{
  # find the patch metadata file
  $file = findFile($ORACLE_HOME, 'patchmd.xml');

  # Log an error if the patch metadata file is not found
  if (isEmpty($file))
  {
    logf("Error: Patch metadata file not found");
  }
  # Parse the metadata file, extract the sql script location out of it
  $sqlscript =  sql_file();

  # Now we need to replace the token '$oracle_home' in the sql script location
  # with the actual value of the oracle home. Hence split the string on '/', 
  # and replace the token. Note that '/' is used in the patch metadata file
  # irrespective of whether it is a linux patch or a windows patch
  @tokens = split(/\//, $sqlscript);
  $str = "";

  foreach $item (@tokens)
  {
     if($item eq '$oracle_home' )
     {
       $str = $str.$ORACLE_HOME.'/';
     }
     else
     {
       $str = $str.$item.'/';
     }

   }

   # Remove the last '/'
   $len = length($str);
   $len = $len - 1;
   $str = substr($str,0,$len);
   $sqlscript = $str;

   # Try to find the sql script. If not found, log an error
   if (! -f $sqlscript)
   {
     logf("Error: SQL script to execute not found");
   }

}


#
# Set ORACLE_HOME, ORACLE_SID environment and invoke SQL
#
my $overall_sql_result = 0; # Overall result over sids
for(my $i=0; $i < scalar(@ORACLE_SIDs); $i++)
{
    my $oracle_sid = $ORACLE_SIDs[$i];
    my $retval = 0;
    logf ("Executing SQL script ($sqlscript) on database [$ORACLE_HOME (OH), $oracle_sid (SID)]");
    oraenv($ORACLE_HOME, $oracle_sid);
    $retval = executesqlSID($oracle_sid, $sqlscript);
    if ($retval != 0)
    {
        logf("Error: SQL script failed on database [$ORACLE_HOME (OH), $oracle_sid (SID)]");
        $overall_sql_result = $retval;
    }
}

cleanAndExit($overall_sql_result);
