#!/usr/local/bin/perl
#
# patch112.pl
#
# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. 
#
#    NAME
#      patch112.pl - Auto patching tool for Oracle Clusterware/RAC home.
#
#    DESCRIPTION
#      patch112.pl -  Auto patching tool for Oracle Clusterware/RAC home.
#
#    MODIFIED   (MM/DD/YY)
#       ksviswan   03/02/11  - Fix Bugs 11807070, 11824391, 9965477
#       ksviswan   01/25/11  - Fix bug 11670519.
#       ksviswan   01/25/11  - Out of place patching support
#       ksviswan   10/01/10  - XbranchMerge ksviswan_bug-10147375 from main
#       ksviswan   09/27/10  - XbranchMerge ksviswan_opauto_copy_initfiles from
#                              main
#       ksviswan   09/22/10  - XbranchMerge ksviswan_bug-10132822 from main
#       ksviswan   09/20/10  - Fix Bugs 9475556,9869238,9869250
#       ksviswan   08/25/10  - Fix Bug 9863515,9869222,10055092,9869222
#       ksviswan   06/24/10  - Fix Bug 9849173
#       ksviswan   06/07/10  - Fix Bug 9786647
#       ksviswan   05/31/10  - Fix Bug 9755296
#       ksviswan   05/25/10  - Fix Bugs 9719845,9719790,9750704 and 9750739
#       ksviswan   04/21/10  - Support Automation for New PSU patch format.
#       ksviswan   03/06/10  - Fix Bugs 9453069 and 9437772
#       ksviswan   02/25/10  - Fix Bugs 9362121,9358228,9409808
#                              add checkCompoent logic
#       ksviswan   01/08/10  - Fix bug 9209886 and set default log dir.
#       ksviswan   05/08/09  - Implement 11.2 autopatching support 
#
#   NOTES
#
# Documentation: usage output,
#   execute this script with -h option
#
#

################ Documentation ################

# The SYNOPSIS section is printed out as usage when incorrect parameters
# are passed

=head1 NAME

  patch112.pl - Auto patching tool for Oracle Clusterware/RAC home.

=head1 SYNOPSIS

  opatch auto <patch directory>
              [-oh  <oracle home location>]
              [-och <crs home location>]
              [-rollback]
              [-ocmrf <ocm response file location>]
              [-norestart]

  Options:

   patch_directory

             Default is the current directory.
             This is the top level directory path where the patch is 
             unzipped. 

             Example:
            
             Always unzip the gipsu/gi one-off in a empty directory
             where there are no other directory/files existing.  
            
             If the directory under which the patch is unzipped is /tmp/patchdir 

             opatch auto /tmp/patchdir

            


 -rollback  

             The patch will be rolled back, not applied
             This option requires patch_directory to be passed

             Example:
             opatch auto /tmp/patchdir -rollback

 -oh         Database/Clusterware home locations    

             comma_delimited_list_of_oralcehomes_to_patch
             The default is all applicable Oracle Homes including
             the clusterware home .Use this option  to patch RDBMS homes where
             no database is registered or any specific database home or
             clusterware home.

             Example:
             opatch auto /tmp/patchdir -oh <oracle_home1_path,oracle_home2_path>

 -och        Grid infrastructure home location
             absolute path of crs home location. This is only used
             to patch Clusterware home when the clusterware stack is down
         
             Example:
             opatch auto /tmp/patchdir -och <oracle_gridhome_path>

 -ocmrf      OCM response file location

             Absolute path of the OCM response file. This is required for
	     silent mode patching

 -norestart  
	     The clusterware stack and database home resources will not be 
	     started after patching. 


  To patch a shared Clusterware or Database home, 
  Refer to Section "Patching installation to RAC database and/or the GI home" in the 
  Patch Readme document and follow the steps.

                                          
=head1 DESCRIPTION


  This script automates the complete patching process for a Clusterware
  or RAC database home. This script must be run as root user and needs Opatch version
  10.2.0.3.3 or above. 
  Case 1 -  On each node of the CRS cluster in case of Non Shared CRS Home.
  Case 2 -  On any one node of the CRS cluster is case of Shared CRS home.          

=cut

################ End Documentation ################

use strict;
use English;
use Cwd;
use Cwd 'abs_path';
use FileHandle;
use File::Basename;
use File::Spec::Functions;
use File::Copy;
use Sys::Hostname;        
use Net::Ping;
use Getopt::Long;
use Pod::Usage;

BEGIN {
  # Add the directory of this file to the search path
  unshift @INC, dirname($PROGRAM_NAME);
}

use crsconfig_lib;
require crsdelete;
require crspatch;


#Global variables
our $g_help = 0;
our $g_unlock = 0;
our $g_patch = 0;

# pull all parameters defined in crsconfig_params and s_crsconfig_defs (if
# it exists) as variables in Perl
my $paramfile_default = catfile (dirname ($0), "crsconfig_params");

# pull all definitions in s_crsconfig_defs (if it exists) as variables in Perl
# this file might not exist for all platforms
my $defsfile = catfile (dirname ($0), "s_crsconfig_defs");
my $timestamp = gentimeStamp();

my $scrdir = abs_path(dirname ($0));

my $deflogdir = "$scrdir/../../cfgtoollogs";
my $logfile;

if (-e $deflogdir)
{
   $logfile = catfile($deflogdir,  "opatchauto$timestamp.log");
}
else
{
   $logfile  = catfile ($scrdir, "log", "opatchauto$timestamp.log");
}

print "opatch auto log file location is $logfile\n";


my $PARAM_FILE_PATH = $paramfile_default;
my $patchdir;
my $patchdbdir;
my $patchfile;
my $patchnum;
my $rollback;
my $ohome;
my $chome;
my $ocmrf;
my $norestart;
my $pwd = $ENV{'PWD'}?$ENV{'PWD'}:getcwd();
my %ohdb = ();
my %dboh = ();
my %ohowner = ();
my $OS = `uname`;
chomp $OS;
my $ORA_CRS_HOME;
my $ORA_CRS_USER;
my $patchType;
my $homeType;
my @dbhomes = ();
my @homestopatch = ();
my @homestostart = ();
my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid( $< );
my $crs_running;
my @gipatches = ();
my @dbpatches = ();
my @patchIds = ();
my $isconflictok;
my $op_silent;
my $sihainst = 0;
my $cfg;
my $dest_crshome;

#TBR
my $overbose;

my $unzip;
my $rsh;
my $ssh;
my $su;
my $sed;
my $echo;
my $mkdir;
my $cat;
my $rcp;
my $kill;
my $olrloc;
my $ocrloc;
my $fuser;

if ( $OS eq "Linux")
{
$unzip = "/usr/bin/unzip";
$rsh = "/usr/bin/rsh";
$ssh = "/usr/bin/ssh";
$su = "/bin/su";
$sed = "/bin/sed";
$echo = "/bin/echo";
$mkdir = "/bin/mkdir";
$cat = "/bin/cat";
$rcp = "/usr/bin/rcp";
$kill = "/bin/kill";
$olrloc = "/etc/oracle/olr.loc";
$ocrloc = "/etc/oracle/ocr.loc"; 
$fuser = "/sbin/fuser";
}
elsif ($OS eq "HP-UX")
{
$unzip = "/usr/local/bin/unzip";
$rsh = "/usr/bin/remsh";
$ssh = "/usr/bin/ssh";
$su =  "/usr/bin/su";
$sed = "/usr/bin/sed";
$echo = "/usr/bin/echo";
$mkdir = "/usr/bin/mkdir";
$cat = "/usr/bin/cat";
$rcp = "/usr/bin/rcp";
$kill = "/usr/bin/kill";
$olrloc = "/var/opt/oracle/olr.loc";
$ocrloc = "/var/opt/oracle/ocr.loc";
$fuser = "/usr/sbin/fuser";
}
elsif ($OS eq "AIX" )
{
$unzip = "/usr/local/bin/unzip";
$rsh = "/usr/bin/rsh";
$ssh = "/usr/bin/ssh";
$su =  "/usr/bin/su";
$sed = "/usr/bin/sed";
$echo = "/usr/bin/echo";
$mkdir = "/usr/bin/mkdir";
$cat = "/usr/bin/cat";
$rcp = "/usr/bin/rcp";
$kill = "/usr/bin/kill";
$olrloc = "/etc/oracle/olr.loc";
$ocrloc = "/etc/oracle/ocr.loc";
$fuser = "/usr/sbin/fuser";
}
elsif ( $OS eq "SunOS" )
{
$unzip = "/usr/bin/unzip";
$rsh = "/usr/bin/rsh";
$ssh = "/usr/local/bin/ssh";
$su =  "/usr/bin/su";
$sed = "/usr/bin/sed";
$echo = "/usr/bin/echo";
$mkdir = "/usr/bin/mkdir";
$cat = "/usr/bin/cat";
$rcp = "/usr/bin/rcp";
$kill = "/usr/bin/kill";
$olrloc = "/var/opt/oracle/olr.loc";
$ocrloc = "/var/opt/oracle/ocr.loc";
$fuser = "/usr/sbin/fuser";
}
else
{
  die "ERROR: $OS is an Unknown Operating System\n";
}

# the return code to give when the incorrect parameters are passed
my $usage_rc = 1;

GetOptions('patchdir=s'     => \$patchdir,
           'patchfile=s'    => \$patchfile,
           'patchnum=s'     => \$patchnum,
           'paramfile=s'    => \$PARAM_FILE_PATH,
           'rollback'       => \$rollback,
           'oh=s'           => \$ohome,
           'och=s'          => \$chome,
           'ocmrf=s'        => \$ocmrf,
           'norestart'      => \$norestart,
           'unlock!'        => \$g_unlock,
           'patch!'         => \$g_patch,
           'desthome=s'     => \$dest_crshome,
           'help!'          => \$g_help) or pod2usage($usage_rc);


# Check validity of args
pod2usage(-msg => "Invalid extra options passed: @ARGV",
          -exitval => $usage_rc) if (@ARGV);

if ($g_unlock && $dest_crshome) {
   $PARAM_FILE_PATH = catfile ($dest_crshome, 'crs', 'install', "crsconfig_params");
} elsif ($g_patch && $dest_crshome) {
   $PARAM_FILE_PATH = catfile ($dest_crshome, 'crs', 'install', "crsconfig_params");
}

### Set this host name (lower case and no domain name)
our $HOST = tolower_host();
die "$!" if ($HOST eq "");

# Set the following vars appropriately for cluster env
### check if run as super user
our $SUPERUSER = check_SuperUser ();
if (!$SUPERUSER) {
  error("Insufficient privileges to execute this script");
  exit 1;
}

$patchdir = "$patchdir/$patchnum";

if (!$g_help && ! $patchdir)
{
   error("-patchdir is a  Mandatory option");
   pod2usage(1);
}


if ( isSIHA()) {
   print "Detected Oracle Restart install\n";
   $sihainst = 1;
} else {
   print "Detected Oracle Clusterware install\n";
}

if ($sihainst) {
   $cfg =
      crsconfig_lib->new(IS_SIHA             => $sihainst,
                     paramfile           => $PARAM_FILE_PATH,
                     osdfile             => $defsfile,
                     crscfg_trace        => TRUE,
                     crscfg_trace_file   => $logfile,
                     HOST                => $HOST
                     );
} else {
   $cfg =
      crsconfig_lib->new(paramfile           => $PARAM_FILE_PATH,
                     osdfile             => $defsfile,
                     crscfg_trace        => TRUE,
                     crscfg_trace_file   => $logfile,
                     HOST                => $HOST,
                     HAS_USER            => $SUPERUSER,
                     UNLOCK              => $g_unlock
                     );
}


if ($g_unlock && $dest_crshome) { unlockPatchHome($dest_crshome); exit 0 }
elsif  ($g_patch && $dest_crshome)  { CRSPatchhome($dest_crshome); exit 0 }


#Subroutines used by this tool


#Remove trailing white spaces in a string
sub trim($)
{
    my $string = shift;
    $string =~ s/^\s+//;
    $string =~ s/\s+$//;
    return $string;
}


sub parseOptions
{
   if    ($g_help)   { pod2usage(0); }

   if ( ! $patchdir )
   {
      $patchdir = $pwd;
      trace("using current directory $patchdir for patch directory");
   }

   if( ! $patchfile )
   {
      trace("No -patchfile specified, assuming the patch is already uncompressed" );
   }
 
   if ($ohome && $chome)
   {
      error("Can't specify -oh with -och");
      pod2usage(1);
   }
}

sub createPatchdir
{
   my $cmd;
   my $status;

   #Create the patch directory
   if( $patchdir && ! -d $patchdir )
   {
       $cmd = "$mkdir -p $patchdir";
       trace( "Executing $cmd as $ORA_CRS_USER" );
       $status = run_as_user($ORA_CRS_USER, $cmd );
   }

}

sub unzipPatch
{
   my $rpatchfile = "";
   my $cmd = "";
   my $node;
   my $ask_skip;
   my @ocp_nodes_topatch;
   my $olocal;
   my $host;
   my $status;
   my @output;

   my $patchfile = $ENV{'PATCH_ZIP_FILE'};

   if ( -e $patchfile )
   {
      $cmd = "$unzip -o $patchfile -d $patchdir";
      trace( "Executing $cmd as $ORA_CRS_USER" );
      $status = run_as_user2($ORA_CRS_USER, \@output, $cmd );
      trace("unzip output is @output");
   }
}

sub findPatchType
{
  #Determine the type of patch
  my $pdir = $_[0];
  my $patchtype;
  my @cmdout;
  my @output;
  my @outp;
  my $rc;
  my $patchcount;
  my $opatch = catfile ($ORA_CRS_HOME, "OPatch", "opatch");
  my $cmd = "$opatch query -get_patch_type $pdir -oh $ORA_CRS_HOME";
  $rc = run_as_user2($ORA_CRS_USER, \@cmdout, $cmd );
  @output = grep(/This patch/, @cmdout);
  trace("output is @output");
  @outp = split(" ", $output[0]);
  $patchtype = $outp[4];
  trace ("Patch type is $patchtype");
  return $patchtype;
}

sub getpatches
{
  my @patches = ();
  my $patchcount;
  my $patchids;
  my $usrinput;

  my $bfile = catfile($patchdir, "bundle.xml");
  if (-e $bfile) {
     @patches = getpids($patchdir);
  } else {
    print "Enter 'yes' if you have unzipped this patch to an empty directory to proceed  (yes/no):";
          $usrinput = <STDIN>;
          if ($usrinput =~ m/yes/i) {
            ($patchcount, $patchids) = getPatchids($patchdir);
            @patches = split(/\,/, $patchids);
          } else {
            exit 1;
          }
  }
  trace("The patch ids are @patches");
  return @patches
}

sub constructpatchlist
{
  my $patchcount;
  my $patchids;
  my $patch;
  my $ptype;

  @patchIds =  getpatches();
  my $patchtype = "DB"; 
  foreach $patch (@patchIds)
  {
     $ptype = findPatchType("$patchdir/$patch");
     if ($ptype =~ m/legacy_bundle_top/) {
        push @gipatches, "$patchdir/$patch";
        push @dbpatches,  "$patchdir/$patch/custom/server/$patch";
        $patchtype = $ptype;
     }  else {
        push @gipatches, "$patchdir/$patch";
        push @dbpatches, "$patchdir/$patch";
     }
  }
  $patchType = $patchtype;
}


sub checkApplicable
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $pdir; 
   my $status = FAILED;
 

   #Check if the patch is applicable
   foreach $pdir (@$patchlist)
   {
      
      $cmd = "$opatch prereq CheckApplicable -ph $pdir -oh $home";
      $rc = run_as_user2($ohown, \@output, $cmd);
 
      @cmdout = grep(/passed/, @output);

      if ((scalar(@cmdout) > 0) && ($rc == 0)) {
         $status = SUCCESS;
      }  else {
         error("The opatch Applicable check failed for $home.  The patch is not applicable for $home");
         
      }
   }
   return $status;
}

sub checkConflict
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $pdir;
   my $status = SUCCESS;

   foreach $pdir (@$patchlist)
   {
      #Check if there are patch conflicts.
      $cmd = "$opatch prereq CheckConflictAgainstOH -ph $pdir -oh $home";
   
      $rc = run_as_user2($ohown, \@output, $cmd);

      @cmdout = grep(/ZOP-40/, @output);

      # if scalar(@cmdout) > 0, we found the msg we were looking for
      if ((scalar(@cmdout) > 0) && ($rc == 0)) {
         $status = FAILED;
         if (! $rollback) {
         error("The opatch Conflict check failed  for $home. The patch $pdir is either already applied or conflicting with another patch applied in the home");
         trace("The error ouput from opatch is @output");
         }
      } 
   }
   return $status;
}

sub checkComponent
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $pdir;
   my $status = FAILED;
   
   foreach $pdir (@$patchlist)
   {
      #Check if the patch is applicable
      $cmd = "$opatch prereq CheckComponents -ph $pdir -oh $home";

      $rc = run_as_user2($ohown, \@output, $cmd);


      @cmdout = grep(/passed/, @output);

      if ((scalar(@cmdout) > 0) && ($rc == 0)) {
         $status = SUCCESS;
      } else {
         error("The opatch Component check failed. This patch is not applicable for $home");
         trace("The component check failed with following error");
         trace("@output");
      }
   }
   return $status;
}

sub getcrshome
{
  my $crsHome;

  if (! $chome) {
    $crsHome = s_get_olr_file ("crs_home")
  } else {
    $crsHome = $chome;
    if ((-f $olrloc) && ($crsHome ne s_get_olr_file ("crs_home"))) {
       error("Incorrect clusterware home path provided for -och option");
       exit 1;
    } 
  }
  if (! -e $crsHome) {
     error("Clusterware home location $crsHome does not exist");
     exit 1;
  }
  return $crsHome;
}

sub getoracleowner
{ 
  my $oh = $_[0];
  my $getoh_ox = "$oh/bin/oracle";
  my $getoh_u;
  if (  -f $getoh_ox )
  {
     my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat( $getoh_ox );
     ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid( $uid );
     $getoh_u = $name;
     trace( "Oracle user for $oh is $getoh_u" );
  }
  else {
     error("unable to get oracle owner for $oh");
     exit 1;
  }
  return $getoh_u;
}
  
sub findHomes
{
  
        my @tmps = ();
        my $db = "";
        my $oh = "";
        my @ohs = ();
        my $getoh_ox = "";
        my $getoh_u = "";
        my $getoh_n = "";
        my @ocp_ohs = ();
        my @ocp_databases;
        my @ocp_ous;
        my $ocp_dblist;
        my $ocp_ohlist;
        my @tmp_ohlist;
        my @tmp_ohlist1; 
        my $ocp_oulist;
        my @out;
        my $rc;
        my $cmd;
        my $path;

        if ($ohome)
        {
           @tmp_ohlist = split( /\,/, $ohome);
           foreach $path (@tmp_ohlist) {
              $path =~ s/\/$//;
              push (@ocp_ohs, $path);
           }
        } elsif ($chome) {
           @ocp_ohs = $chome
        }         
        else
        {
      
        my $srvctl    = catfile ($ORA_CRS_HOME, "bin", "srvctl");

        trace( "Looking for configured databases on node $HOST" );
        @ocp_databases = `$srvctl config`;
        chomp @ocp_databases;
        $ocp_dblist = join " ", @ocp_databases;
        trace( "Databases configured on node $HOST are: $ocp_dblist" );
        trace( "Determining ORACLE_HOME paths for configured databases" );
        $ocp_ohlist = "";
        @ocp_ohs = ();
        foreach $db ( @ocp_databases )
        {
            #trace( "Looking at database $db" );
                
            $cmd = "$srvctl config database -d $db";
            @out = system_cmd_capture($cmd);
            $rc = shift @out;

            if ( $rc != 0 )
            {
               trace("try with srvctl config database -v");
               $cmd = "$srvctl config database -v";
               @out = system_cmd_capture($cmd);
               $rc = shift @out;
               if ($rc == 0) {
                  foreach my $str (@out)
                  { 
                     my ($dbname, $dbhome, $dbver) = split(" ", $str);
                     trace("db name is $dbname");
                     trace("db home is $dbhome");
                     trace("db ver is $dbver"); 
                     chomp $dbname;
                     chomp $dbhome;
                     $dboh{$dbname} = trim($dbhome); 
                     trace("Oracle home for database $dbname is $dboh{$dbname}");
                  }
               } else {
                   error("Not able to retreive database home information");
                   exit 1;
               }
               last;
           }
           else
           {
              @tmps = grep(/Oracle home:/, @out);
              trace("output is @tmps");
              my ($dummy, $ohpath) = split( /\:/, $tmps[0] );
              $dboh{$db} = trim($ohpath);
              trace("Oracle home for database $db is $dboh{$db}");
           }
        }

        #create hash oracle home to dbs
        foreach $db (keys%dboh)
        {
         if(defined($ohdb{$dboh{$db}})) {
         $ohdb{$dboh{$db}} = "$ohdb{$dboh{$db}}:$db";
         } else {
           $ohdb{$dboh{$db}} = "$db";
         }
        }

        #get unique oracle home list
        @ocp_ohs = keys%ohdb;

        foreach $oh (keys%ohdb)
        {
         trace( "Oracle Home $oh is configured with Database\(s\)\-\> $ohdb{$oh}");
        }

        }
        foreach $oh ( @ocp_ohs )
        {
          my $ohown = getoracleowner($oh);
          $ohowner{$oh} = $ohown;
        }
 trace("oracle home list is @ocp_ohs");
 return @ocp_ohs;
}


sub isSIHA
{
   my $ret= FAILED;
   my $local_only = get_config_key("ocr", "local_only");
   if ($local_only =~ m/true/i) {
      $ret = SUCCESS;
   }
   return $ret;
}

sub get_config_key
{
   my $src = $_[0];
   my $key = $_[1];
   $src    =~ tr/a-z/A-Z/;
   my ($val, $cfgfile);

   if ($src eq 'OCR') {
      $cfgfile = $ocrloc;
   }
   elsif ($src eq 'OLR') {
      $cfgfile = $olrloc;
   }

   open (CFGFL, "<$cfgfile") or return $val;
   while (<CFGFL>) {
      if (/^$key=(\S+)/) {
         $val = $1;
         last;
      }
   }

   close (CFGFL);
   return $val;
}

      
sub findHomeType
{
   my $home = $_[0];
   my $crshome;
   my $type;
   my $local_only = s_get_config_key("ocr", "local_only");
   $crshome = getcrshome();

   if (($home eq $crshome) && ($local_only =~ m/true/i)) {
     $type = "HA";
   } elsif (($home eq $crshome) && ($local_only =~ m/false/i)) {
     $type = "CRS";
   } else {
     $type = "DB";
   }

   return $type;
}

sub applyPatch
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $cmd = "";
   my $status;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $pdir;
   my @output = ();
   if  ($OS eq "AIX" ) {
         unloadAIXfiles();
       }
   foreach $pdir (@$patchlist)
   {
      $cmd = "$opatch napply $pdir -local $op_silent -oh $home";
      trace("Executing command $cmd as $ohown");
      $status = run_as_user2($ohown, \@output, $cmd );
      trace("status of apply patch is $status");
      trace("The apply patch output is @output");
      if (0 != $status) {
        error("patch $pdir  apply  failed  for home  $home");
        last;
      } else {
        error("patch $pdir  apply successful for home  $home");
      }

   }

}

sub rollbackPatch
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $status = 0;
   my @output = ();
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $pnum;
   my $skip;

   if  ($OS eq "AIX" ) {
         unloadAIXfiles();
       }
   foreach $pnum (@patchIds)
   {
      $skip = FALSE;
      trace("checking if patch $pnum exists in $home");
      if (checkRollback($pnum, $home)) {
         my $cmd = "$opatch rollback -id $pnum -local -silent -oh $home";
         trace("Executing command $cmd as $ohown");
         $status = run_as_user2($ohown, \@output, $cmd );
         trace("status of rollback patch is $status");
         trace("The rollback patch output is @output");
      } else {
         trace("$pnum does not exist . skipping rollback");
         $skip = TRUE;
      }
      if (0 != $status) {
        error("patch $pnum  rollback failed for home  $home");
        last;
      } else {
          if (! $skip) {
             error("patch $pnum  rollback successful for home $home");
         }
      }

   }
}


sub setrdbmsfileperms
{
  my $home = $_[0];
  my $cmd ;
  my @out;
  my $rc;

  $cmd = "$home/rdbms/install/rootadd_rdbms.sh";
  @out = system_cmd_capture("$cmd");
  $rc = shift @out;

  if (0 == $rc) {
     trace("setrdbmsfileperms succeeded");
  } else {
     error("setrdbmsfileperms failed. output is @out");
  }
}

sub removeproc
{
  my $home = $_[0];
  my $proc = $_[1];
  my $cmd ;
  my @out;
  my $rc;  

  trace("Invoking removeproc to clean oracle client procs");
  my $fpath = catfile($home, "bin", $proc);
  $cmd = "$fuser -k $fpath";
  @out = system_cmd_capture("$cmd");
  $rc = shift @out;

  if (0 == $rc) {
     trace("fuser command success. output for $fpath is @out");
  } else {
     trace("fuser command output for $fpath is @out");
  }
}

sub getcrsversion {
  my $crsctlbin = catfile ($ORA_CRS_HOME, 'bin', 'crsctl');
  my @cmd;
  if ($sihainst) {
     @cmd       = ($crsctlbin, 'query', 'has', 'releaseversion');
  } else {
     @cmd       = ($crsctlbin, 'query', 'crs', 'activeversion');
  }
  my @out       = system_cmd_capture(@cmd);
  my $rc        = shift @out;
  my @versionarr = (0, 0, 0, 0, 0);
  my $verstring = $out[0];

  if ($rc == 0) {
     $verstring  =~ m/\[(\d*)\.(\d*)\.(\d*)\.(\d*)\.(\d*)\].*$/;
     @versionarr = ($1, $2, $3, $4, $5);
     trace("crs version is @versionarr");
  }
  else {
     error ("@cmd ... failed rc=$rc with message:\n @out \n");
  }

  return @versionarr;
}


sub PerformDBPatch
{
  my $home = shift;
  my $patchlist = shift;
  my $ohown = $ohowner{$home};
  my $cmd;
  my $status;
  my @output = ();
  my $pdir;
 
  my @crs_version = getcrsversion();


  my $issiha = isSIHA();  
  if (Stopdbhomeres($home, $issiha, $ohown)) {
     if ($crs_version[0] == 11 && $crs_version[1] == 2 && $crs_version[2] == 0 && $crs_version[3] == 2) {
        #Bug 10131381
        removeproc($home, "oracle");
     }
  }

  foreach $pdir (@$patchlist)
  {
     if (-e "$pdir/custom/scripts/prepatch.sh") {
        $cmd = "$pdir/custom/scripts/prepatch.sh -dbhome $home";
     } else {
        $cmd = "true";
     }
      $status = run_as_user2 ($ohowner{$home}, \@output, $cmd);
  }
  if ($status == 0) {
     trace ("prepatch execution for DB home ... success");
     if ( $rollback ) {
       rollbackPatch($home, \@$patchlist);
     } else {
       applyPatch($home, \@$patchlist);
     }
   } else {
      error ("prepatch execution for DB home ... failed");
      exit 1;
   }
  
  foreach $pdir (@$patchlist)
  {
     if (-e "$pdir/custom/scripts/postpatch.sh") {
        $cmd = "$pdir/custom/scripts/postpatch.sh -dbhome $home";
     } else {
        $cmd = "true";
     }
      $status = run_as_user2 ($ohowner{$home}, \@output, $cmd);
  }

  if ($status == 0) {
      trace ("postpatch execution for DB home ... success");
   } else {
      error ("postpatch execution for DB home ... failed");
      exit 1;
  }
}

sub gentimeStamp
{
  my ($sec, $min, $hour, $day, $month, $year) =
        (localtime) [0, 1, 2, 3, 4, 5];
  $month = $month + 1;
  $year = $year + 1900;

  my $ts = sprintf("%04d-%02d-%02d_%02d-%02d-%02d",$year, $month, $day, $hour, $min, $sec);
  return $ts;
}

sub getPatchids
{
 my $searchdir = $_[0];
 my $ids;
 my $line;
 my $idx;

 my @dbids = <$searchdir/*>;
 foreach $line (@dbids)
 {
  chomp($line);
  if ( -d $line) {
   $idx++;
   my @temp = split(/\//, $line);
   $ids = $ids . $temp[-1] . ",";
  }
 }
 chop $ids;
 return ($idx, $ids);
}

sub getpids
{
 my $searchdir = $_[0];
 my $bfile = catfile($searchdir, "bundle.xml");
 my @list;
 my @out;
 my $ids;
 my $tag;
 my @pids;

 @list = read_file( $bfile);

 trace("Bundle.xml content is @list");
 @out = grep(/entity location/, @list);
 foreach my $rec (@out) {
   chomp($rec);
   if ($rec =~ m/custom/) {
      next;
   } else {
      $rec =~ s/<|>|"//g;
      ($tag, $ids) = split(/=/,$rec);
      push @pids, $ids;
   }
 }
 trace("The patch ids are @pids");
 return @pids;
}

sub isServerready
{
  my $crsctl    = catfile ($ORA_CRS_HOME, "bin", "crsctl");
  my @output;
  my $rc;
  my $retries = 360;
  my $ready = FALSE;
  my $grep_val = "STATE=ONLINE";
  my @cmdout;

  while ( $retries) {
     @output = system_cmd_capture($crsctl, 'stat', 'resource', '-c' , $HOST);
     $rc     = shift @output;
     
     @cmdout = grep(/$grep_val/, @output);
     if (scalar(@cmdout) > 0) {
       $ready = TRUE;
       last;
     }

     trace ("Waiting for Server assignments");
     sleep (5);
     $retries--;
  } 

  if ($ready) {
    trace ("Server assignments completed. Ready to start databases");
  } else {
    error ("Timed out waiting for server assignments");
  }

  return $ready;
  
}

sub checkClusterstat
{
  my $crsctl    = catfile ($ORA_CRS_HOME, "bin", "crsctl");
  my @output;
  my $rc;
  my $grep_val = "4537|4529|4533";
  my @cmdout;
  my $nodelist = $CFG->params('NODE_NAME_LIST');
  my @nodes = split(/,/, $nodelist);
  my $node;
  my $count = 0;

  foreach $node (@nodes) {
     if ($node !~ /\b$HOST\b/i) {
        @output = system_cmd_capture($crsctl, 'check', 'cluster', '-n' , $node);
        $rc     = shift @output;
    
        @cmdout = grep(/$grep_val/, @output);
        if (scalar(@cmdout) > 0) {
        error("Clusterware stack up on node $node");
        $count++;
        }
     }
  }

  if ($count == 0) {
    trace ("Clusterware stack is not running on remote nodes");
  } else {
    error ("Clusterware stack is running on remote nodes");
    print "Refer to opatch auto help for patching shared homes and follow the steps\n";
    exit 1;
  }

}

sub checkdbhomestat
{
  my $home = $_[0];
  my $ohown = $ohowner{$home};
  my @cmdout;
  my $nodelist = $CFG->params('NODE_NAME_LIST');
  my @nodes = split(/,/, $nodelist);
  my $node;
  my $count = 0;
  my $rc;
  my $retstat;

  foreach $node (@nodes) {
     if ($node !~ /\b$HOST\b/i) {
        @cmdout = Statusdbhomeres($home, 0, $node,$ohown);
        my $retstat = shift @cmdout;
        
        trace("ret code for status db home on $node is $retstat");

        if (($retstat == 0) && (scalar(@cmdout) > 0)) {
           error("Database home resources for $home are running  on node $node");
           $count++;
        }
     }
  }

  if ($count == 0) {
    trace ("Database home resources for $home is not running on remote nodes");
  } else {
    error ("Database home resources for $home is running on remote nodes");
    print "Refer to opatch auto help for patching shared homes and follow the steps\n";
    exit 1;
  }

}


sub checkConflictforhomes
{
   my @homelist = @_;
   my $chkconflict = SUCCESS;
   my $count;

   foreach my $home (@homelist) {
     my $status;
     trace("Processing oracle home $home");
     $homeType = findHomeType($home);
     trace("Home type of $home is $homeType");
     if ($homeType eq "CRS" || $homeType eq "HA")
     {

        if ( $rollback) {
            $status = checkComponent ($home, \@gipatches);
         } else {
            $status = (checkComponent ($home, \@gipatches) &&  checkConflict ($home, \@gipatches));
         }
     } else {
        if ( $rollback) {
             $status = checkComponent ($home, \@dbpatches);
        } else {
             $status = (checkComponent ($home, \@dbpatches) &&  checkConflict ($home, \@dbpatches));
        }
     }

     trace("Status of component/conflict check  for $home is $status");

     if ($status) {
         trace(" Conflict check passes for oracle home  $home");
     } else {
         error("Conflict check failed for oracle home  $home");
         $count++;
     }
  }

  
  if ($count == 0) {
    trace ("Conflict check passed  for all oracle homes");
  } else {
    error ("Conflict check failed");
    $chkconflict = FAILED;
  }

  return $chkconflict;
}

sub checkOCM
{
  my $silentflg;
  my $ocmrspfile;

  #check if opatch is bundled with OCM
  if ( -e "$ORA_CRS_HOME/OPatch/ocm/bin/emocmrsp")
  {
    if ($ocmrf)
    {
      $ocmrspfile = $ocmrf;
    } 
    else {
       error("OPatch  is bundled with OCM, Enter the absolute OCM response file path:") ;
       $ocmrspfile = <STDIN>;
       chomp $ocmrspfile;
    }
    if ( -e $ocmrspfile)
    {
      $silentflg = "-silent -ocmrf $ocmrspfile";
    }
    else
    {
      error("Invalid response file path, To regenerate an OCM response file run $ORA_CRS_HOME/OPatch/ocm/bin/emocmrsp");
      exit 1;
    }
  }
  else
  {
    #report( 0, 0, "$opatch is not bundled with OCM" );
    $silentflg = "-silent";
  }

  return $silentflg;

}


sub ispathShared
{
   my $path = $_[0];
   my @capout = ();
   my $rc;
   my $user = $CFG->params('ORACLE_OWNER');
   my $nodelist = $CFG->params('NODE_NAME_LIST');
   my $status;

   my @nodes = split(/,/, $nodelist);
   trace("The cluster nodes are @nodes");
   if (scalar(@nodes) == 1) {
      trace("Single node cluster");
      $status = FALSE;
      return $status;
   }
   my $CLUVFY = catfile( $ORA_CRS_HOME, 'bin', 'cluvfy');
   my @program = ($CLUVFY, 'comp ssa', '-t software', '-s' , $path , '-n', $nodelist, '-display_status');
   my $status;

   trace("checking if path $path is shared");
   # run as specific user, if requested
   $rc = run_as_user2($user, \@capout, @program);
   trace("return code for shared check is $rc");
   trace("output of sharedness check is @capout");

   if (scalar(grep(/EFAIL/, @capout)) > 0) {
     $status = ERROR;
   } elsif ((scalar(grep(/VFAIL/, @capout))) > 0){
     $status = FALSE;
   } else {
     trace("The path $path is shared ");
     $status = TRUE;
   }
   return $status;
}


sub isHomesShared
{
   my @homelist = @_;
   my $path;
   my $isshared = FALSE;
   my $usrinput;

   foreach my $home (@homelist) {
      my $homeType = findHomeType($home);
      if (($homeType eq "CRS") || ($homeType eq "HA")) {
         $path = catdir( $home, 'crs', 'install');
      } else {
         $path = $home;
      }

      $isshared = ispathShared($path);
      trace("the ishared value is $isshared");
      if  ( $isshared == TRUE) {
          if ($homeType eq "CRS")  {
             checkClusterstat();
          } else {
             checkdbhomestat($home);
          }
      } elsif ($isshared == ERROR) {
        print "\n";
        print "Unable to determine if $home is shared oracle home\n";
        print "Enter 'yes' if this is not a shared home or if the prerequiste actions are performed to patch this shared home (yes/no):";
          $usrinput = <STDIN>;
          if ($usrinput =~ m/yes/i) {
             next;
          }else {
             print "\n";
             print "Refer to opatch auto help (opatch auto -h) for patching shared homes and follow the steps\n";
             exit 1;
          }

      } else {
          trace("The oracle home $home is not shared");
      }
   }
}


sub getOpatchver
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $tmpstr;

   $cmd = "$opatch version -oh $home";
   $rc = run_as_user2($ohown, \@output, $cmd);
   @cmdout = grep(/Version/, @output);
   $cmdout[0] =~ s/ //g;
   my ($txt , $ver) = split(/:/, $cmdout[0]);
   trace("opatch version in oracle home $home  is $ver");
   chomp $ver;

   return $ver;

}


sub checkOpatchver
{
   my $home = shift;
   my $ohown = getoracleowner($home);
   my $patchlist = shift;
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $pdir;
   my $version;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $status = FAILED;
   $version = getOpatchver($home);   

   foreach $pdir (@$patchlist)
   {
      $cmd = "$opatch util checkMinimumOPatchVersion -ph $pdir -version $version -oh $home";
      $rc = run_as_user2($ohown, \@output, $cmd);

      @cmdout = grep(/true/, @output);
      if ((scalar(@cmdout) > 0) && ($rc == 0)) {
         $status = SUCCESS;
      } else {
         error("The opatch minimum version  check for patch $pdir failed  for $home");
         trace("The opatch version check failed with following error");
         trace("@output");
      }
   }
   return $status;
}

sub checkOpatchverforhomes
{
   my @homelist = @_;
   my $chkver = SUCCESS;
   my $count;

   foreach my $home (@homelist) {
     my $status;
     trace("Processing oracle home $home");
     $homeType = findHomeType($home);
     trace("Home type of $home is $homeType");
     if ($homeType eq "CRS" || $homeType eq "HA")
     {
        $status = checkOpatchver ($home, \@gipatches);
     } else {
        $status = checkOpatchver ($home, \@dbpatches);
     }

     trace("Status of opatch version check  for $home is $status");

     if ($status) {
         trace("Opatch version check passed for oracle home  $home");
     } else {
         error("Opatch version check failed for oracle home  $home");
         $count++;
     }
  }

  if ($count == 0) {
    trace ("Opatch version check passed  for all oracle homes");
  } else {
    error ("Opatch version  check failed");
    $chkver = FAILED;
  }

  return $chkver;
}

sub checkRollback
{
   my $pid = $_[0];
   my $home = $_[1];
   my $ohown = getoracleowner($home);
   my $rc;
   my @cmdout;
   my @output;
   my $cmd;
   my $opatch = catfile ($home, "OPatch", "opatch");
   my $status = FAILED;

   $cmd = "$opatch prereq checkRollbackable  -id $pid -oh $home";
   $rc = run_as_user2($ohown, \@output, $cmd);

   @cmdout = grep(/passed/, @output);
   if ((scalar(@cmdout) > 0) && ($rc == 0)) {
         $status = SUCCESS;
   } else {
      error("The patch  $pid does not exist in $home");
      trace("The rollback check output is @output");
   }
   return $status;
}

sub unloadAIXfiles
{
  my $cmd = "/usr/sbin/slibclean";
  my @out = system_cmd_capture("$cmd");
  my $rc = shift @out;

  trace("slibclean output is @out");
}

   
##MAIN BODY

parseOptions();


$ORA_CRS_HOME = getcrshome();
$ORA_CRS_USER = getoracleowner($ORA_CRS_HOME);

$op_silent = checkOCM();
trace("silent mode option is $op_silent");

createPatchdir();

#set patchdir permissions
s_set_perms("0775", $patchdir);
unzipPatch();

constructpatchlist();
trace("GI patches are @gipatches");
trace("DB patches are @dbpatches");


#check if clusterware running
my $status = isSIHA();
if (! ($status == SUCCESS))
{
   $crs_running = check_service ("cluster", 2);
} else {
   $crs_running = check_service ("ohasd", 2);
}

if ((! $crs_running) && (! $chome)) {
   print "Clusterware is either  not running or not configured. You have the following 2 options\n";
   print "1. Configure and Start the Clusterware on this node and re-run the tool\n";
   print "2. or Run the tool with the -och <CRS_HOME_PATH> option and then invoke  tool with -oh <comma seperated ORACLE_HOME_LIST> to patch the RDBMS home\n"; 
   exit 1;
} 

@dbhomes = findHomes();


@homestopatch = @dbhomes;
@homestostart = @dbhomes;


if (($patchType =~ m/legacy_bundle_top/) && (! $ohome) && (! $chome))
{ 
   unshift (@homestostart, $ORA_CRS_HOME);
}

if (($patchType =~ m/legacy_bundle_top/) && (! $ohome) && (! $chome))
{   
   push (@homestopatch , $ORA_CRS_HOME);
}  

if (! checkOpatchverforhomes(@homestopatch)) {
error("update the opatch version for the failed homes and retry");
exit 1;
}

if (! isSIHA()) {
isHomesShared(@homestopatch);
}

$isconflictok = checkConflictforhomes(@homestopatch);


if ($isconflictok == SUCCESS) {
foreach my $home (@homestopatch) {

  my $status;

  trace("Processing oracle home $home");
  $homeType = findHomeType($home);
  trace("Home type of $home is $homeType");
    if ($homeType eq "CRS")
    {
       unlockCRSHomeforpatch();
       #wait for complete stack to shutdown.
       sleep(120);
       trace("Waiting for complete CRS stack to stop");
       removeproc($home, "crsctl.bin");
       $status = checkApplicable ($home, \@gipatches);
       trace("Status of Applicable  check  for $home is $status");

       if (($status == SUCCESS) || ($rollback))
       {
          if ( $rollback ) {
             rollbackPatch($home, \@gipatches);
          }  else {
             applyPatch($home, \@gipatches);
          }
       } else {
           error("Patch Applicable check failed for $home");
           Instantiatepatchfiles ();
           StartCRS();
           exit 1;
       }
    }
    elsif ($homeType eq "HA")
    {

       unlockHAHomeforpatch();
       removeproc($home, "crsctl.bin");
       $status = checkApplicable ($home, \@gipatches);
       trace("Status of Applicable  check  for $home is $status");
       if (($status == SUCCESS) || ($rollback))
       {
          if ( $rollback ) {
             rollbackPatch($home, \@gipatches);
          }  else {
             applyPatch($home, \@gipatches);
          }
       } else {
           error("Patch Applicable check failed for $home");
           Instantiatepatchfiles ();
           StartHA();
           exit 1;
       }
    } 
    else
    {
       trace("Performing DB patch");
       $status = checkApplicable ($home, \@dbpatches);

       trace("Status of Applicable  check  for $home is $status");
       if (($status == SUCCESS) || ($rollback))
       {
          PerformDBPatch($home, \@dbpatches); 
       }  else {
           error("Patch Applicable check failed for $home");
           exit 1;
       }
    } 
}
} else {
error ("Conflict-Check has failed . Please refer to $logfile for details");
exit 1;
}

foreach my $home (@homestostart) {

#post patch sequence
   
   trace("Performing Post patch actions");
   trace("norestart flag is set to $norestart");
   $homeType = findHomeType($home);
   my $ohown = $ohowner{$home};
   my $issiha = isSIHA(); 
   if ($homeType eq "CRS")
   {
      if (-f $olrloc) {
          trace("Performing Post patch actions for Grid Home $home");
          setrdbmsfileperms($home);
          CRSPatch($norestart);
          
      }
   } elsif ($homeType eq "HA") {
      if (-f $olrloc) {
          trace("Performing Post patch actions for HA Home $home");
          setrdbmsfileperms($home);
          HAPatch($norestart);
      }
   } else {
     trace("Performing Post Patch start  action for DB Home $home");
     if  (! $norestart &&  isServerready() ) {
           Startdbhomeres($home, $issiha,$ohown);
     }
   }
}
     
0;
