# $Header: ecmPatchDatabase.pl 05-apr-2006.13:45:45 milshah Exp $
#
# Copyright (c) 2002, 2006, Oracle. All rights reserved.  
#
#    DESCRIPTION
#      ECM script to patch Oracle RDBMS server with Interim|Patchset patches
#
#    USAGE
#      perl ecmPatchDatabase.pl <command> [<options>]
#
#    command
#           checkTarget | expandPatch | applyPatch | showResults
#    options
#	    -pa_stage_loc absoluteStagingLocation
#           -c count
#          -cf
#           -d path
#           -f patchfile
#           -h
#           -i patchid
#          -oh ORACLE_HOME
#          -os ORACLE_SID
#           -p patchtype
#           -s size
#           -t targettype
#           -x index
#           -v
#           -sharedMove
#           -stage_location <Location of the stage>
#
#    NOTES
#      <other useful comments,qualifications,etc>
#
#    MODIFIED     (MM/DD/YY)
#       sejain     21/05/07 - bug-5917213:if absolute dir is specified patch should not get staged inside oh
#       sejain     07/03/07 - bug 5920228: fixing missing arument in abort and abort if patch doesnot exists at staged location
#       sejain     07/03/07 - bug 5867796: fixing recursive copy issue
#       milshah    04/05/06 - Backport milshah_bug-5109697 from main 
#       milshah    04/03/06 - To support shared agent patching 
#       milshah    03/24/06 - Dont empty the PATH variable 
#       milshah    03/28/06 - Backport milshah_bug-5114359 from main 
#       rsah       03/09/06 - Replacing '-a' switch by '-pa_stage_loc'. 
#       rsah       03/08/06 - Fix for bug: 5059973 
#			      Introduced -a / AP option for checkTarget() &
#			      expandPatch().
#       milshah    02/02/06 - Backport milshah_bug-5001091 from main 
#       milshah    01/26/06 - modify cleanandexit to right shift only in case 
#                             where $status > E_FAIL 
#       milshah    11/11/05 - change statusf to always right shift return code 
#       shgangul   07/14/05 - support extra unknown parameters, -asm <asm 
#                             instances> 
#       shgangul   04/28/05 - Add File::Stat 
#       shgangul   04/27/05 - Add support for -stage_location 
#       shgangul   01/31/05 - Bug 3488889: Use cmd.exe to run dir 
#       shgangul   01/31/05 - bug 3845819: MAC OS port 
#       shgangul   01/28/05 - Add options to detect shared agent 
#       mgoodric   01/31/05 - move functions to ecmCommon.pl 
#       shgangul   12/01/04 - Review comments for bug fix 3313446 
#       shgangul   11/25/04 - Bug 3313446: Add -local option 
#       mningomb   04/13/04 - Checking out ecmPatchDatabase.pl for bug 3499817 
#                             - NLS available pace of staging directory on 
#                             native windows hosts is unknown 
#       mbhoopat   03/10/04 - linux port 
#       mgoodric   03/03/04 - Fix getFreeKB for non-English locales
#       mgoodric   02/13/04 - Fix File::Find on Windows
#       nsharma    01/21/04 - Fixing utilities path on linux 
#       mgoodric   09/19/03 - Fix broken quoting programs for NT
#       mgoodric   09/15/03 - Fix quoting filespecs for NT
#       mgoodric   09/08/03 - Use unzip/zip from $EMDROOT/bin if present
#       mgoodric   05/12/03 - Perltidy
#       mgoodric   04/24/03 - Fix stdin redirection for Linux
#       mgoodric   04/04/03 - Fix getFreeKB with long Filesystem names
#       mgoodric   02/25/03 - Add support for WinNT check freespace
#       mgoodric   12/27/02 - Use unzip instead of jar (no JDK with Agent)
#       mgoodric   09/10/02 - Fix Patch wizard selection
#       mgoodric   09/04/02 - Convert SQL page to uix jsp
#       mgoodric   09/03/02 - Reinstate schedule wizard page
#       xxu        06/25/02 - remove /usr/local/bin/perl
#       mgoodric   06/19/02 - Add missing resource bundle tag
#       mgoodric   06/18/02 - Port perl patch scripts to NT
#       mgoodric   06/17/02 - Fix usage of n/a in tables
#       mgoodric   06/08/02 - Use jar for unpacking
#       mgoodric   06/06/02 - Fix PatchAgent job summary results
#       mgoodric   06/05/02 - Fix PatchAgent job
#       mgoodric   06/04/02 - Fix script generation for ARU
#       mgoodric   06/03/02 - Add target_type parameter to patch job
#       mgoodric   05/30/02 - Fix for storing patch size in cache
#       mgoodric   03/09/02 - Fix for no oracle binary
#       mgoodric   01/17/02 - Add stage-only patch job
#       mgoodric   01/02/02 - Change step order for staging
#       mgoodric   12/28/01 - Fix cachePatchFile step
#       mgoodric   12/20/01 - Add script steps to patch job
#       mgoodric   12/14/01 - Use new MGMT_ECM_PATCH_CACHE for patch depot
#       mgoodric   12/07/01 - Add echodo function to display system cmd
#       mgoodric   12/06/01 - Fix PatchDatabase job for Patchset
#       mgoodric   12/04/01 - Fixes for PatchDatabase job
#       mgoodric   11/30/01 - Support multiple database instances
#       mgoodric   11/29/01 - Add PatchEMD job type
#       mgoodric   11/27/01 - Fix start/stop db with Patchset
#       mgoodric   11/26/01 - Change job to get properties using paramSource
#       mgoodric   11/19/01 - Fix support for multiple patches
#       mgoodric   11/16/01 - Update PatchDatabase job interface
#       mgoodric   11/14/01 - Add PatchDatabase job type
#

# --- Set up necessary variables for proper running of this environment ---
use strict;
use English;
use Cwd();
use Cwd;
use File::Basename;
use File::Find;
use File::Path;
use File::Spec();
use File::Spec::Functions qw (:ALL);
use File::Copy();
use File::Copy;
use FileHandle;
use File::stat;
use User::pwent;

my $scriptName   = File::Basename::basename($0);
my $osmScriptDir = File::Basename::dirname($0);
my $ecmCommon    = File::Spec->catfile($osmScriptDir, 'ecmCommon.pl');

require "$ecmCommon";

# ------ Initialize global variables -------------------------------------
use constant COPYRIGHT => "Copyright \251 2002, 2004, Oracle. All rights reserved.";
use constant VERSION   => '10.1.0.2';
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_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;     # -os mjgdb817
my $abs_stage_locn  = S_EMPTY;     #  -pa_stage_loc Absolute Staging
my $depot_path      = S_EMPTY;     #  -d EMStagedPatches
my $patch_type      = S_EMPTY;     #  -p (patch|patchset)
my $target_type     = S_EMPTY;     #  -t (oracle_database|oracle_emd|oracle_ias)
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 @db_listeners    = ();
my $OS              = $^O;         # OS type (solaris|linux)
my $PERL            = $^X;         # Perl executable
my $PERL5LIB        = S_EMPTY;
my $EMDROOT         = S_EMPTY;
my $EMSTATE         = 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 $isStaged        = 0;           # Whether the patch is staged
my $stage_location  = S_EMPTY;     # Location of the patch stage
my $StagedLoc       = S_EMPTY;     # Staged patch location if specified
my $INVPTRLOCFILE   = 'oraInst.loc';
my $INVPTRLOCFILEPATH = S_EMPTY;

# --------------------- 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 $AP     = '-pa_stage_loc';	   # (bug: 5059973)
				   #     absolute staging location for a patch.
				   #     Applicable for checkTarget()/expandPatch().
				   #     Explicitly specifies the absolute staging
				   # 	 location for a patch. It overrides -d.
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 $STAGELOC  = '-stage_location'; #  Location of the patch stage 
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';
        }
    }
}

# 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{$AP}   = 'absoluteStagingLocation';	# -pa_stage_loc
    $DEFS{$AP}   = ' ';          	        # default nothing
    $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
    $OPTV{$STAGELOC} = 'stageLoc';   # -stage_location
    $DEFS{$STAGELOC} = ' ';          # default nothing
}

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

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

    $action = $ARGV[0];    # checkTarget|expandPatch|applyPatch|showResults
    if (isEmpty($action) || isEqual($VE, $action) || isEqual($HP, $action))
    {
        $ARGS{$HP} = '1';
        $ARGS{$VE} = '1' if ((defined $action) && isEqual($VE, $action));
        $action = CTYPE_HELP;
    }

    if (!defined $CMDS{lc($action)})
    {
        printHeader($0, $#ARGV) if (!$isHeader);
        abort($action, statusf(E_INV_COMMAND), "Invalid command: $action");
    }

    for (my $i = 1 ; $i < $argcount ; $i++)
    {
        $opt = $ARGV[$i];
        if (defined $OPTV{$opt})
        {
            if (defined $UNARYS{$opt})
            {
                $ARGS{$opt} = '1';
            }
            elsif ($i < ($argcount - 1))
            {
                $i += 1;
                if (!isEqual('-', substr($ARGV[$i], 0, 1)))
                {
                    $ARGS{$opt} = $ARGV[$i];
                }
            }
            if (!defined $ARGS{$opt})
            {
                printHeader($0, $#ARGV) if (!$isHeader);
                abort($action, statusf(E_MISSING_ARG),
                      "Missing value: $opt <$OPTV{$opt}>");
            }
        }
        else
        {
            printHeader($0, $#ARGV) if (!$isHeader);
            #abort($action, statusf(E_INV_ARG), "Invalid argument: $opt");
            print("Following argument is not supported: $opt");
        }
    }

    # set up our global flags
    $isDebug   = getArg($DB);
    $isProduct = B_FALSE if ($isDebug);
    $isHelp    = (equalsIgnoreCase(CTYPE_HELP, $action) || getArg($HP, 0));

    # Set the shared flag
    $isShared  = getArg($SHOME);

    # Set the shared option to be passed to emdpatch.pl
    if ($isShared)
    {
        $Shared = $SHOME;
    }

    # Check if the patch is staged
    if (getArg($STAGELOC) ne ' ')
    {
        $isStaged = 1;
        $stage_location = getArg($STAGELOC);
    }

    if (!$isHelp)
    {
        # set invPtrLoc is not already set
        $ORACLE_HOME = getArg($OH);     # /private/OraHome1
        if (isEmpty($inv_loc))
        {
            $INVPTRLOCFILEPATH = File::Spec->catfile($ORACLE_HOME, $INVPTRLOCFILE);
            if (-f $INVPTRLOCFILEPATH)
            {
                $inv_loc = S_INVPTRLOC . S_SPACE . fnQuote($INVPTRLOCFILEPATH);
            }
        }
    }
}

# getArg(<opt>,<default>)
#
# Get a command argument
#
# Return argument
#
sub getArg($;$)
{
    my ($opt, $default) = @_;

    my $arg = S_EMPTY;

    if (defined $ARGS{$opt})
    {
        $arg = $ARGS{$opt};
    }
    elsif (defined $default)
    {
        $arg = $default;
    }
    else
    {
        $arg = $DEFS{$opt};
    }

    if (isEmpty($arg))
    {
        printHeader($0, $#ARGV) if (!$isHeader);
        abort($action, statusf(E_MISSING_ARG),
              "Missing argument: $opt $OPTV{$opt}");
    }

    return $arg;
}

# getFileSize(<filename>)
#
# Return the allocated size of file
#
sub getFileSize($)
{
    my ($filename) = @_;

    my $filesize = 0;

    if (-f $filename)
    {

        # my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
        my $st = stat($filename);
        $filesize = $st->size;
    }

    return $filesize;
}

# getFreeKB(<path>)
#
# Return the available KB of file system
#
sub getFreeKB($)
{
    my ($loc) = @_;

    my @lines  = ();
    my @bytes  = ();
    my $freeKB = 0;

    if (onWindows())
    {
        chomp(@lines = `%SYSTEMROOT%\\SYSTEM32\\CMD.EXE /C DIR /A:D /-C "$loc"`);
        @bytes = split (' ', $lines[@lines - 1]);
        if ($#bytes > 2)
        {
            $freeKB = $bytes[2];    # Win2K/XP
        }
        else
        {
            $freeKB = $bytes[0];    # WinNT
        }
        $freeKB = $freeKB / KB;
    }
    else
    {
        chomp(@lines = `$DF $dfOpt "$loc"`);
        @bytes = split (' ', $lines[@lines - 1]);
        $freeKB = $bytes[@bytes - 3];
    }

    return $freeKB;
}

# 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;
}

# clearLocks()
#
# Reset all patching state markers
#
sub clearLocks
{
    my @db_locks   = ();
    my @lsnr_locks = ();
    my @initoras   = ();

    unlink($patch_lock);
    unlink($dbsnmp_lock);

    @db_locks = findLocks($depot_path, '*-DB.lck');
    for (my $i = 0 ; $i <= $#db_locks ; $i++)
    {
        unlink($db_locks[$i]) if ($db_locks[$i] =~ m/\-DB\.lck$/);
    }

    @lsnr_locks = findLocks($depot_path, '*-LSNR.lck');
    for (my $i = 0 ; $i <= $#lsnr_locks ; $i++)
    {
        unlink($lsnr_locks[$i]) if ($lsnr_locks[$i] =~ m/\-LSNR\.lck$/);
    }

    @initoras = findLocks($depot_path, 'init*.ora');
    for (my $i = 0 ; $i <= $#initoras ; $i++)
    {
        unlink($initoras[$i]) if ($initoras[$i] =~ m/init.*\.ora$/);
    }
}

# 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(<lock_file>)
#
# Populate db_listeners for use in shutting down all Oracle listeners
#
sub getDB_LISTENERS($)
{
    my ($lock_file) = @_;

    my $lsnr  = S_EMPTY;
    my @lines = ();
    my $count = $#db_listeners;

    if (!isEmpty($lock_file))
    {
        chomp(@lines = `$LS -1 $lock_file 2> $NULL_DEVICE`);
        for (my $i = 0 ; $i <= $#lines ; $i++)
        {
            ($lines[$i] = File::Basename::basename($lines[$i])) =~
              s/[-]LSNR.lck$//;
        }
    }
    else
    {
        chomp(@lines =
              `$PS -e -o args | $EGREP "^$ORACLE_HOME/bin/tnslsnr[ ]+" | $AWK '{print \$2}'`
              );
    }

    LOOP: for (my $j = 0 ; $j <= $#lines ; $j++)
    {
        $lsnr = $lines[$j];
        for (my $i = 0 ; $i <= $count ; $i++)
        {
            next LOOP if (isEqual($lsnr, $db_listeners[$i]));
        }
        $count += 1;
        $db_listeners[$count] = $lsnr;
    }
}

# getDB_INSTANCES()
#
# Populate db_instances for use in shutting down all Oracle instances
#
sub getDB_INSTANCES
{
    my $sid   = $ORACLE_SID;
    my $home  = $ORACLE_HOME;
    my $start = 'N';
    my $count = $#db_instances;

    $! = E_SUCCESS;
    open(INPUT, "< $oratab")
      or return;

    INPUT: while (<INPUT>)
    {
        chomp;
        s/ //g;
        next INPUT if (m/^[#*]/ || isEmpty($_));
        ($sid, $home, $start) = split (/:/, $_, 3);
        next INPUT if (isEmpty($sid));
        if (isEqual($ORACLE_HOME, $home))
        {
            for (my $i = 0 ; $i <= $count ; $i++)
            {
                next INPUT if (isEqual($sid, $db_instances[$i]));
            }
            $count += 1;
            $db_instances[$count] = $sid;
        }
    }
    close(INPUT);
}

# 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) = @_;

    my $lsnr_lock = File::Spec->catfile($depot_path, "${name}-LSNR.lck");
    my $status    = E_SUCCESS;

    if (isRunning("^$ORACLE_HOME/bin/tnslsnr[ ]+ $name"))
    {
        $! = E_SUCCESS;
        printf(STDOUT "Stopping Oracle database listener ${name}...\n");
        $status = echodo("$LSNRCTL stop $name");
        $status = statusf($status);
        $status == E_SUCCESS
          or abort($action, $status,
                   "Could not shutdown database listener: $!");

        createLock($lsnr_lock, $name);
    }
}

# 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}\$"))
    {
        $! = E_SUCCESS;
        printf(STDOUT "Stopping Oracle agent...\n");
        $status = echodo("$AGENTCTL");
        $status = statusf($status);
        $status == E_SUCCESS
          or abort($action, $status,
                   "Could not shutdown Oracle agent: $!");

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

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

    my $db_lock = File::Spec->catfile($depot_path, "${sid}-DB.lck");
    my $initora = File::Spec->catfile($depot_path, "init${sid}.ora");
    my $oldSID  = $ENV{'ORACLE_SID'};
    my $status  = E_SUCCESS;

    if (isDBRunning($sid))
    {
        if ($isRAC)
        {
            getDB_NAME(S_EMPTY);
            printf(STDOUT "Stopping Oracle database instances and listeners...\n");
            $status = echodo("$SRVCTL stop -p $db_name");
            $status = statusf($status);
        }
        else
        {
            $ENV{'ORACLE_SID'} = $sid;
            createInitOra($sid, $initora);
            printf(STDOUT "Stopping Oracle database instance ${sid}...\n");
            $status =
              echodo("$ECHO shutdown immediate | $SQLPLUS \'/ as sysdba\'");
            $status = statusf($status);
            $ENV{'ORACLE_SID'} = $oldSID;
            $db_name = $sid;
        }

        $status == E_SUCCESS
          or abort($action, $status, "Could not shutdown database");

        if (isDBRunning($sid))
        {
            abort($action, statusf(E_FAIL), "Could not shutdown database");
        }

        createLock($db_lock, $db_name);
    }
}

# 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;
        }
    }
}

# shutdownDB(<force>)
#
# Shutdown database listener(s) and database instance(s) if running
# Create shutdown marker(s) to indicate what we did
#
sub shutdownDB($)
{
    my ($force) = @_;

    my $status = E_SUCCESS;

    clearLocks() if ($force);

    if ($lsnr_shutdown)
    {
        getDB_LISTENERS(S_EMPTY);
        for (my $i = 0 ; $i <= $#db_listeners ; $i++)
        {
            shutdownLSNR($db_listeners[$i]);
        }
    }

    if ($dbsnmp_shutdown)
    {
        shutdownDBSNMP();
    }

    if ($db_shutdown)
    {
        getDB_INSTANCES();
        for (my $i = 0 ; $i <= $#db_instances ; $i++)
        {
            shutdownSID($db_instances[$i]);
        }
    }
}

# startupLSNR(<name>)
#
# Start database listener if it was running
# Remove shutdown marker to indicate the current state
#
sub startupLSNR($)
{
    my ($name) = @_;

    my $lsnr_lock = File::Spec->catfile($depot_path, "${name}-LSNR.lck");
    my $status    = E_SUCCESS;

    if (isLocked($lsnr_lock))
    {
        if (!isRunning("^$ORACLE_HOME/bin/tnslsnr[ ]+ $name"))
        {
            $! = E_SUCCESS;
            printf(STDOUT "Starting Oracle database listener ${name}...\n");
            $status = echodo("$LSNRCTL start $name");
            $status = statusf($status);
            $status == E_SUCCESS
              or abort($action, $status,
                       "Could not startup database listener: $!");
        }
        unlink($lsnr_lock);
    }
}

# 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 = statusf($status);
            $status == E_SUCCESS
              or abort($action, $status,
                       "Could not startup Oracle agent: $!");
        }
        unlink($dbsnmp_lock);
    }
}

# startupSID(<sid>)
#
# Start database instance if it was running
# Remove shutdown marker to indicate the current state
#
sub startupSID($)
{
    my ($sid) = @_;

    my $db_lock = File::Spec->catfile($depot_path, "${sid}-DB.lck");
    my $initora = File::Spec->catfile($depot_path, "init${sid}.ora");
    my $oldSID  = $ENV{'ORACLE_SID'};
    my $status  = E_SUCCESS;

    if (isLocked($db_lock))
    {
        if (!isDBRunning($sid))
        {
            if ($isRAC)
            {
                getDB_NAME($db_lock);
                printf(STDOUT "Starting Oracle database instances and listeners...\n");
                $status = echodo("$SRVCTL start -p $db_name");
                $status = statusf($status);
            }
            else
            {
                $ENV{'ORACLE_SID'} = $sid;
                printf(STDOUT "Starting Oracle database instance ${sid}...\n");
                $status =
                  echodo(
                     "$ECHO startup pfile=$initora | $SQLPLUS \'/ as sysdba\'");
                $status = statusf($status);
                $ENV{'ORACLE_SID'} = $oldSID;
                $db_name = $sid;
            }

            $status == E_SUCCESS
              or abort($action, $status, "Could not startup database");

            if (!isDBRunning($sid))
            {
                abort($action, statusf(E_FAIL), "Could not startup database");
            }
        }
        unlink($db_lock);
        unlink($initora);
    }
}

# startupDB(<force>)
#
# Start database listener and database instance(s) if they were running
# Remove shutdown marker(s) to indicate the current state
#
sub startupDB($)
{
    my ($force) = @_;

    my $status = E_SUCCESS;

    if ($db_shutdown)
    {
        getDB_INSTANCES();
        for (my $i = 0 ; $i <= $#db_instances ; $i++)
        {
            startupSID($db_instances[$i]);
        }
    }

    if ($lsnr_shutdown)
    {
        getDB_LISTENERS("$depot_path/*-LSNR.lck");
        for (my $i = 0 ; $i <= $#db_listeners ; $i++)
        {
            startupLSNR($db_listeners[$i]);
        }
    }

    if ($dbsnmp_shutdown)
    {
        startupDBSNMP();
    }

    clearLocks() if ($force);
}

# cleanupPatch(<patch_path>,<patch_file>,<force>)
#
# Remove anything not needed for undo of patch
#
sub cleanupPatch($$$)
{
    my ($patch_path, $patch_file, $force) = @_;

    my $undo_patch    = S_EMPTY;
    my $patchset_file = S_EMPTY;

    if ($isProduct)
    {
        unlink($patch_file);
        if (isEqual(PTYPE_PATCHSET, $patch_type))
        {
            if (onWindows())
            {
                $patchset_file = findFile($patch_path, '.*\.zip');
            }
            else
            {
                $patchset_file = findFile($patch_path, '.*\.tar');
            }
            unlink($patchset_file) if (!isEmpty($patchset_file));
        }
        if (!$force)
        {
            $undo_patch = findFile($patch_path, 'undo_pre[0-9]*\.sh');
        }
        if (isEmpty($undo_patch))
        {

            # File::Path::rmtree($patch_path,0,1);
        }
    }
}

# checkTarget()
#
# Verify ORACLE_HOME
# Create depot directory to store patch
# Check to see if patch already applied (one-off patch)
# Check disk space for available free space
#
sub checkTarget
{
    my $patch_path = File::Spec->catfile($depot_path, $patch_id);
    my $oracle_file =
      File::Spec->catfile($ORACLE_HOME, 'bin', "oracle$EXE_SUFFIX");
    my $undo_patch = S_EMPTY;
    my $tempfile   = "_test_$$";
    my $freeKB     = 0;
    my $needKB     = 0;
    my $isDB       = isEqual(TTYPE_DB, $target_type);
    my $isEMD      = isEqual(TTYPE_EMD, $target_type);
    my $isIAS      = isEqual(TTYPE_IAS, $target_type);
    my $status     = E_FAIL;

    $! = E_SUCCESS;
    chdir($ORACLE_HOME)
      or abort($action, statusf($?), "Could not cd to >$ORACLE_HOME<: $!");

    if (!-e $patch_path)
    {
        File::Path::mkpath($patch_path, 0, 0775) > 0
          or abort($action, statusf($?), "Could not mkpath >$patch_path<: $!");
    }

    chdir($patch_path)
      or abort($action, statusf($?), "Could not cd to >$patch_path<: $!");

    if (isEqual(PTYPE_PATCH, $patch_type) && $isDB)
    {

        #   $undo_patch = findFile($patch_path, 'undo_pre[0-9]*\.sh');
        #   $undo_patch eq S_EMPTY
        #     or abort($action,statusf(E_FAIL),"$patch_id already applied");
    }

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

    close(OUTPUT);
    unlink($tempfile);

    $freeKB = getFreeKB('.');
    if (isEqual(PTYPE_PATCH, $patch_type) && $isDB)
    {
        $needKB = getFileSize($oracle_file);
    }
    $needKB = int((($needKB * 2) + (3 * $patch_size)) / KB);
    if (defined $ARGS{$CF})
    {
        printf(STDOUT "%d,%d\n", $freeKB, $needKB);
    }
    else
    {
        $freeKB > $needKB
          or abort($action, statusf(E_NOT_ENOUGH_SPACE),
                   "Not enough free disk space: ${freeKB}KB less than ${needKB}KB");
    }
}

# expandPatch()
#
# Unpack the patch
# Unpack the patchset file (if Patchset)
#
sub expandPatch
{
    my $patch_path = File::Spec->catfile($depot_path, $patch_id);
    my $patch_home = $patch_path;
    my $patchset_file = S_EMPTY;
    my $unpack        = S_EMPTY;
    my $unpackOpt     = S_EMPTY;
    my $status        = E_FAIL;

    $! = E_SUCCESS;
    chdir($patch_path)
      or abort($action, statusf($?), "Could not cd to >$patch_path<: $!");

    printf(STDOUT "Unpacking patch file ${patch_file}...\n");
    $status  = echodo(fnQuote($UNZIP) . ' ' . $unzipOpt . ' ' . fnQuote($patch_file));
    $status = statusf($status);
    $status == E_SUCCESS
      or abort($action, $status, "Could not unpack $patch_file");

    if (isEqual(PTYPE_PATCHSET, $patch_type))
    {
        if (onWindows())
        {
            $unpack    = $UNZIP;
            $unpackOpt = $unzipOpt;
            rename($patch_file, "${patch_file}.save");
            $patchset_file = findFile($patch_path, '.*\.zip');
            rename("${patch_file}.save", $patch_file);
        }
        else
        {
            $unpack        = $TAR;
            $unpackOpt     = $tarOpt;
            $patchset_file = findFile($patch_path, '.*\.tar');
        }
        if (!isEmpty($patchset_file))
        {
            printf(STDOUT "\nUnpacking patchset file ${patchset_file}...\n");
            $status = echodo(fnQuote($unpack) . ' ' . $unpackOpt . ' ' . fnQuote($patchset_file));
            $status = statusf($status);
            $status == E_SUCCESS
              or abort($action, $status,
                       "Could not unpack $patchset_file");
        }
    }
}

# applyPatch()
#
# run the patch script
#
sub applyPatch
{
    # Copy the patch to official stage location if it is a staged patch
    if ($isStaged == 1)
    {
        my $patch_src = $stage_location;
        my $patch_dst = File::Spec->catfile($depot_path, $patch_id);
        mkpath( $patch_dst ) if not -e $patch_dst;
        # remove trailing directory separators if any
        if ($patch_src =~ /\/$/)
        {
        chop($patch_src);
        }

       # bug 5867796 : add patch_id to patch source otherwise 
       # everything in patch_src will get copied to official location.
       if ( $patch_src !~ /$patch_id$/)
       {
       $patch_src = File::Spec->catfile($patch_src, $patch_id);
       }

       # bug 5920228 : Abort if patch doesnot exists at the given staged location
       if ( !( -d $patch_src ))
       {
       abort($action, statusf(E_FAIL),"Patch does not exists at staged location $patch_src\n");
       }

       # Do not copy if staged location and official location are same.
       # This is to avoid recursive copy of patch under patch dir
       if ( $patch_dst eq $patch_src)
       {
       print(STDOUT "Patch already staged at official location , continuing with patch application...\n");
       }
       else
       {
        print(STDOUT "Patch already staged. Copying the staged patch from $patch_src to $patch_dst...\n");
        if (copy_dir($patch_src, $patch_dst) != E_SUCCESS)
        {
            # bug 5920228 : Add missing argument in abort
            abort($action, statusf(E_FAIL),"Copy from $patch_src to $patch_dst failed");
        }
       }
    }

    my $patch_path = File::Spec->catfile($depot_path, $patch_id);
    my $patch_log =
      File::Spec->catfile($patch_path, CTYPE_APPLY . "_${patch_id}.log");
    my $patch_marker = CTYPE_APPLY . "$patch_id successful";
    my $opatch       = S_EMPTY;
    my $patch_args   = S_EMPTY;
    my $patch_home   = $patch_path;
    my $patchset_rsp = S_EMPTY;
    my $products_jar = S_EMPTY;
    my $failed       = 'skipped';
    my $isDB         = isEqual(TTYPE_DB, $target_type);
    my $isEMD        = isEqual(TTYPE_EMD, $target_type);
    my $isIAS        = isEqual(TTYPE_IAS, $target_type);
    my $status       = E_FAIL;

    # This script is always called for agent patches
    $isEMD = B_TRUE;

    $! = E_SUCCESS;
    chdir($patch_path)
      or abort($action, statusf($?),
               "Could not cd to >$patch_path<: $!", $patch_log);

    unlink($patch_lock) if ($patch_index == 1);

    if (!isLocked($patch_lock))
    {
        unlink($patch_log);

        createLock($patch_lock, $patch_id) if ($patch_count > 1);

        if ($isEMD)
        {
            $opatch = findFile($patch_path, mask('emdpatch.pl'));
            if (isEmpty($opatch))
            {
                $opatch = findFile($patch_path, mask('patch.sh'));
            }

            $opatch ne S_EMPTY
              or abort($action, statusf(E_NO_OPATCHPL),
                       "Could not find emdpatch.pl", $patch_log);

            $patch_home = File::Basename::dirname($opatch);

            chdir($patch_home)
              or abort($action, statusf($?),
                       "Could not cd to >$patch_home<: $!", $patch_log);
        }
#        elsif (isEqual(PTYPE_PATCH, $patch_type))
#        {
#            $opatch = findFile($patch_path, mask('opatch'));
#            if (isEmpty($opatch))
#            {
#                $opatch = findFile($patch_path, mask('patch.sh'));
#            }
#
#            $opatch ne S_EMPTY
#              or abort($action, statusf(E_NO_OPATCHPL),
#                       "Could not find opatch", $patch_log);
#
#            $patch_home = File::Basename::dirname($opatch);
#
#            $local_opt = S_LOCAL;
#            $patch_args = "apply -silent -oh \"$ORACLE_HOME\" $local_opt $inv_loc"
#              if (isEqual('opatch', File::Basename::basename($opatch)));
#
#            chdir($patch_home)
#              or abort($action, statusf($?),
#                       "Could not cd to >$patch_home<: $!", $patch_log);
#        }
#        elsif (isEqual(PTYPE_PATCHSET, $patch_type))
#        {
#            $ENV{'DISPLAY'} = ':0.0' if (!defined $ENV{'DISPLAY'});
#
#            $patchset_rsp = findFile(File::Spec->catfile($patch_path, 'response'),
#                                     '.*\.rsp');
#            $patchset_rsp ne S_EMPTY
#              or abort($action, statusf(E_NO_INVENTORY),
#                       "Could not find response file", $patch_log);
#
#            $products_jar = findFile(File::Spec->catfile($patch_path, 'stage'),
#                                     mask('products.jar'));
#            $products_jar ne S_EMPTY
#              or abort($action, statusf(E_NO_INVENTORY),
#                       "Could not find products.jar", $patch_log);
#
#            $local_opt = S_LOCAL;
#            $patch_args =
#              "-silent -responseFile $patchset_rsp session_FROM_LOCATION=\"$products_jar\" session_ORACLE_HOME=\"$ORACLE_HOME\" $local_opt $inv_loc";
#            chdir("$ORACLE_HOME/bin")
#              or abort($action, statusf($?),
#                     "Could not cd to >$ORACLE_HOME/bin<: $!", $patch_log);
#
#            $opatch = File::Spec->catfile($ORACLE_HOME, 'bin', 'runInstaller');
#        }

        if ((-f $opatch) && (!-x $opatch))
        {
            chmod(0777, $opatch)
              or abort($action, statusf($?),
                       "Could not chmod >$opatch<: $!", $patch_log);
        }

        $failed = 'failed';
        $ENV{'PWD'} = currentDir();
        if ($isEMD)
        {
            tee("Applying Patch ${patch_id}...\n", $patch_log);
            if (isEqual('emdpatch.pl', File::Basename::basename($opatch)))
            {
                if (onWindows())
                {
                    $status = echodo('start /b ' . fnQuote($PERL) . ' -w ' . fnQuote($opatch) . " ${patch_id} ${Shared} >> " . fnQuote($patch_log) . " 2>&1");
                }
                else
                {
                    $status = echodo(fnQuote($PERL) . ' -w ' . fnQuote($opatch) . " ${patch_id} ${Shared} >> " . fnQuote($patch_log) . " 2>&1 &"); 
                }
                $status = statusf($status);
            }
            else
            {
                if (onWindows())
                {
                    $status = echodo("start /b $ECHO Y | $SHELL " . fnQuote($opatch) . " >> " . fnQuote($patch_log) . " 2>&1");
                }
                else
                {
                    $status = echodo("$ECHO Y | $SHELL " . fnQuote($opatch) . " >> " . fnQuote($patch_log) . " 2>&1 &");
                }   
                $status = statusf($status);
            }
        }
#        elsif (isEqual(PTYPE_PATCH, $patch_type))
#        {
#            tee("Applying Patch ${patch_id}...\n", $patch_log);
#            $status =
#              echodo(
#                "$ECHO Y | $SHELL " . fnQuote($opatch) . " $patch_args >> " . fnQuote($patch_log) . " 2>&1");
#        }
#        elsif (isEqual(PTYPE_PATCHSET, $patch_type))
#        {
#            tee("Applying Patchset ${patch_id}...\n", $patch_log);
#            $status =
#              echodo("$ECHO Y | " . fnQuote($opatch) . " $patch_args >> " . fnQuote($patch_log) . " 2>&1");
#        }
        unlink($patch_lock) if (($patch_count > 1) && ($status == E_SUCCESS));
    }

    if ($patch_index == $patch_count)
    {
        unlink($patch_lock);
    }

    $status == E_SUCCESS
      or abort($action, $status,
               CTYPE_APPLY . " $patch_id $failed", $patch_log);

    if (!$isEMD)
    {
        open(LOG, ">> $patch_log")
          or abort($action, statusf($?),
                   "Could not open logfile $patch_log to write: $!");

        printf(LOG "\n\n$patch_marker\n\n");
        close(LOG);
    }

    chdir($ORACLE_HOME)
      or abort($action, statusf($?),
               "Could not cd to >$ORACLE_HOME<: $!", $patch_log);

    cleanupPatch($patch_path, $patch_file, !isEmpty($patch_args));
}

# showResults()
#
# Show the log of the actual Agent patch
#
sub showResults
{
    my $patch_path = File::Spec->catfile($depot_path, $patch_id);
    my $patch_log =
      File::Spec->catfile($patch_path, CTYPE_APPLY . "_${patch_id}.log");
    my $patch_marker = CTYPE_APPLY . " $patch_id successful";
    my $status       = E_FAIL;

    $! = E_SUCCESS;
    chdir($ORACLE_HOME)
      or abort($action, statusf($?), "Could not cd to >$ORACLE_HOME<: $!");

    printf(STDOUT "Patch results for ${patch_id}...\n\n");
    if (-f $patch_log)
    {
        open(LOG, "< $patch_log");
        while (<LOG>)
        {
            $status = E_SUCCESS if (m/^$patch_marker$/);
            print(STDOUT $_);
        }
        close(LOG);
        printf(STDOUT "\n");
    }

    if ($isProduct)
    {
        if ($status == E_SUCCESS)
        {
            unlink($patch_log);

            # File::Path::rmtree($patch_path,0,1);
        }
    }

    cleanAndExit($status);
}

# 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{'EMSTATE'}     = $EMSTATE;
    $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');

    # Initialize path and ld_library_path to the old environments path and ld_library_path
    # These variables dont exist in the current env because we empty the env.
    $ENV{'PATH'} = $oldenv{'PATH'};
    addPATH(File::Spec->catfile($JAVA_HOME,   'bin'));
    addPATH(File::Spec->catfile($ORACLE_HOME, 'bin'));

    $ENV{'LD_LIBRARY_PATH'} = $oldenv{'LD_LIBRARY_PATH'};
    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'};
    $EMSTATE = $ENV{'EMSTATE'};

    if (isEmpty($EMDROOT))
    {
        ($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);
    }

    my ($checkTarget, $expandPatch, $applyPatch, $showResults) =
      (CTYPE_CHECK, CTYPE_EXPAND, CTYPE_APPLY, CTYPE_SHOW);

    # Start of "here doc".
    print STDOUT <<_EOM_

Usage: perl $scriptName <command> [<options>]

  command
      $checkTarget | $expandPatch | $applyPatch | $showResults
  options
      $AP <$OPTV{$AP}>\t: Absolute Stage Location - Overrides '$DP <$OPTV{$DP}>' (defaults to '$DEFS{$AP}')
      $CT <$OPTV{$CT}>\t: Number of patches (defaults to $DEFS{$CT})
      $CF \t\t: Check freespace ($checkTarget only)
      $DP <$OPTV{$DP}>\t: Location of patches (defaults to "$DEFS{$DP}")
      $PF <$OPTV{$PF}>\t: Name of patch file (defaults to "p<patchid>.zip")
      $HP \t\t: Display this help
      $ID <$OPTV{$ID}>\t: Patch number (required)
      $OH <$OPTV{$OH}>\t: Location of Oracle Home (required)
      $SID <$OPTV{$SID}>\t: Name of database instance (defaults to "$DEFS{$SID}")
      $QT \t\t: Omit header/trailer output
      $PT <$OPTV{$PT}>\t: Type of patch (defaults to "$DEFS{$PT}")
      $SI <$OPTV{$SI}>\t\t: Size of patch file (defaults to $DEFS{$SI})
      $TT <$OPTV{$TT}>\t: Type of target (defaults to "$DEFS{$TT}")
      $PI <$OPTV{$PI}>\t: Index of current patch (defaults to $DEFS{$PI})
      $SHOME \t: The Agent home is a shared home

Examples:

  $checkTarget -i 1390304 -oh /private/OraHome1 -s 5192
  $expandPatch -i 1390304 -oh /private/OraHome1 -f p1390304.zip
  $applyPatch  -i 1390304 -oh /private/OraHome1 -t oracle_emd
  $applyPatch  -i 1390304 -oh /private/OraHome1 -t oracle_emd -sharedMove
  $showResults -i 1390304 -oh /private/OraHome1

_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) = @_;

    if ($status >= E_FAIL)
    {
        exit statusf($status);
    }
    else
    {
        exit $status;
    }

}

# Copy directory recursively to destination
# result = copy_dir ( <Source directory>, <Destination directory> )
# Copies the contents of Source directory to Destination directory
sub copy_dir
{
    my $src = $_[0]; # Source Directory
    my $dst = $_[1]; # Destination Directory
    my $result = E_FAIL; # Default to failure

    # Try to copy if both source and destination are directories
    if ( ( -d $src ) && ( -r $src ) && ( -d $dst ) && ( -w $dst) )
    {
        $src =~ s/\\/\//g;
        $dst =~ s/\\/\//g;

        # Do not copy if the source and destination are the same
        # Return failure
        if ( $src eq $dst )
        {
            return $result;
        }

        find
        (
            sub
            {
                my $targetdir = $File::Find::dir;
                my $target = $targetdir;
                $targetdir = $dst . substr($targetdir, length($src));

                mkpath( $targetdir ) if not -e $targetdir;

                my $file = $_;
                my $source = "./" . $file;
                my $dest   = "$targetdir/$file";

                if ( ($file ne $scriptName) && (-f $source) )
                {
                    # Preserve the permissions and timestamp
                    my $stats = stat ( $source );
                    my @filetime = ($stats->atime(), $stats->mtime());#(stat $source)[8,9];
                    copy ($source, $dest);
                    chmod $stats->mode(), $dest; # Copy permission
                    utime $stats->atime(), $stats->mtime(), $dest; # Copy timestamp
                    my $chown_result = 0;
                    chown $stats->uid(), $stats->gid(), $dest or $chown_result = 1; # Copy Owner
                    if ($chown_result)
                    {
                        my $s4 = $stats->uid();
                        my $s5 = $stats->gid();
                        printf(STDOUT "Could not chown file $dest to $s4, $s5...\n");
                        abort($action, statusf(E_FAIL));
                    }
                }
            },
            $src
        );

        $result = 0; # Copy success
    }

    return $result;
}

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

parseArgs();

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

#
# Check for call from PatchVerify.java (-q)
#
if (!defined $ARGS{$QT})
{
    printHeader($0, $#ARGV);
    $isHeader = B_TRUE;
}

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

#
# Set ORACLE_HOME environment
#
$ORACLE_HOME = getArg($OH);     # /private/OraHome1
$ORACLE_SID  = getArg($SID);    # mjgdb817
oraenv($ORACLE_HOME, $ORACLE_SID);
 
$abs_stage_locn = getArg($AP);
if ($abs_stage_locn ne ' ')
{
    $depot_path = $abs_stage_locn;
}
else
{
    $depot_path = getArg($DP);      # EMStagedPatches
    # bug-5917213:if depot does not start with EMStagedPatches means staged location is specified from UI
    # so do not append ORACLE_HOME otherwise deopt_path directory structure will get created inside ORCALE_HOME
    if ($depot_path =~ /^EMStagedPatches/)
    {
    $depot_path = File::Spec->catfile($ORACLE_HOME, $depot_path);
    }
}

$patch_lock = File::Spec->catfile($depot_path, "${db_name}-$patch_lock");
$dbsnmp_lock = File::Spec->catfile($depot_path, "${db_name}-$dbsnmp_lock");
$lsnr_lock   = File::Spec->catfile($depot_path, "${db_name}-$lsnr_lock");
$db_lock     = File::Spec->catfile($depot_path, "${db_name}-$db_lock");

$patch_type  = lc(getArg($PT));                                # patch
$target_type = lc(getArg($TT));                                # oracle_database

#
# Execute the action requested
#
if (equalsIgnoreCase(CTYPE_CHECK, $action))
{
    $patch_id   = getArg($ID);    # 1390304
    $patch_size = getArg($SI);    # 5237
    checkTarget();
}
elsif (equalsIgnoreCase(CTYPE_EXPAND, $action))
{
    $patch_id = getArg($ID);                          # 1390304
    # bug-5917213:if depot does not start with EMStagedPatches means staged location is specified from UI
    # and stagePatch had already staged at depot_path so expand according to depot_path.
    if ($depot_path =~ /^EMStagedPatches/)
    {
    $patch_file = getArg($PF, "p${patch_id}.zip");    # p1390304.zip
    }
    else
    {
    $patch_file = File::Spec->catfile($depot_path, "${patch_id}", "p${patch_id}.zip");
    }
    expandPatch();
}
elsif (equalsIgnoreCase(CTYPE_APPLY, $action))
{
    $patch_id    = getArg($ID);                        # 1390304
    $patch_file  = getArg($PF, "p${patch_id}.zip");    # p1390304.zip
    $patch_index = getArg($PI);
    $patch_count = getArg($CT);
    applyPatch();
}
elsif (equalsIgnoreCase(CTYPE_SHOW, $action))
{
    $patch_id = getArg($ID);                           # 1390304
    showResults();
}

#elsif (equalsIgnoreCase(CTYPE_SHUTDOWN, $action))
#{
#  $shutdown_type  = getArg($ST);  # 1=instance,2=listener,4=agent
#  $shutdown_sids  = getArg($SS);  # ora817,mjg817
#  $shutdown_lsnrs = getArg($SL);  # LISTENER
#  setShutdownTypes();
#  shutdownDB(1);
#}
#elsif (equalsIgnoreCase(CTYPE_STARTUP, $action))
#{
#  $shutdown_type  = getArg($ST);  # 1=instance,2=listener,4=agent
#  $shutdown_sids  = getArg($SS);  # ora817,mjg817
#  $shutdown_lsnrs = getArg($SL);  # LISTENER
#  setShutdownTypes();
#  startupDB(1);
#}
elsif (isEmpty($action))
{
    abort($action, statusf(E_NO_COMMAND), "Missing command:");
}
else
{
    abort($action, statusf(E_INV_COMMAND), "Invalid command: $action");
}

#
# Indicate a successful action; terminate with success status
#
if (!defined $ARGS{$QT})
{
    printf(STDOUT "\n\n$action $patch_id successful\n\n");
}

cleanAndExit(0);
