#!/usr/local/bin/perl
# 
# $Header: opsm/utl/rhpmovedb.pl /st_has_12.2.0.1.0ocwpsu/1 2017/05/31 23:08:22 vgunredd Exp $
#
# rhpmovedb.pl
# 
# Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      rhpmovedb.pl - Perl script to execute Datapatch from DB Home 
#
#    DESCRIPTION
#      <short description of component this file declares/defines>
#
#    NOTES
#      <other useful comments, qualifications, etc.>
#
#    MODIFIED   (MM/DD/YY)
#    vgunredd    05/24/17 - Backport vgunredd_bug-25505841 from main
#    vgunredd    02/10/17 - fix bug 25505841
#    ksviswan    08/04/16 - ojvm patch support
#    aaniyeri    03/11/16 - Creation
#

use strict;
use English;
use Scalar::Util qw(looks_like_number);
use File::Copy;
use File::Path;
use File::Find;
use File::Basename;
use File::Spec::Functions;
use DBI;

use constant TRUE                 => "1";
use constant FALSE                => "0";

#Command Line Arguments

#source workingcopy home
my $srchome = $ARGV[0];

#destination workingcopy home
my $dsthome = $ARGV[1];

#SID of the db to be moved
my $sid = $ARGV[2];

#Path to temp file containing Patch ID's of source
my $src_file_patches = $ARGV[3];

#Path to file containing Patch ID's of destination
my $dst_file_patches = $ARGV[4];

#String indicating whether it is "rollback" or "apply" of datapatch
my $patch_command = $ARGV[5];

#Flag to check if the move is happening for a standalone usecase
#my $isStand = $ARGV[6];

chomp($patch_command);

my $isUpgrade;
my $srvctl = "$dsthome/bin/srvctl";
my $sqlplus = "$dsthome/bin/sqlplus";
my $dbname;
my $perlver = $^V;
substr ($perlver,0,1) = "";


#include the perl paths needed
BEGIN {
   #Add the directory of this file to the search path
   push @INC, dirname($PROGRAM_NAME);
   push @INC, "$dsthome/perl/lib/$perlver";
   push @INC, "$dsthome/perl/lib/site_perl/$perlver";
   push @INC, "$dsthome/lib";
}

if(uc($patch_command) eq uc("rollback"))
{

  $ENV{"ORACLE_HOME"} = "$srchome";
  $ENV{"ORACLE_SID"} = "$sid";

  system ('echo ORACLE_HOME $ORACLE_HOME');
  print "\n";
  system ('echo ORACLE_SID $ORACLE_SID');
  print "\n";
 
  my @spatches;
  my @dpatches; 
 
  my $src_patches;
  my $dst_patches;
  open sfile, $src_file_patches or die "Couldn't open file: $src_file_patches";
  open dfile, $dst_file_patches or die "Couldn't open file: $dst_file_patches";
  chomp($src_patches = <sfile>);
  chomp($dst_patches = <dfile>);
  close sfile;
  close dfile;
  @spatches = split(',',$src_patches);
  @dpatches = split(',',$dst_patches);
  

 my $src_datapatch = "$srchome/OPatch/datapatch";
 my $src_sqlpatch_path = "$srchome/sqlpatch";

 #ls in dbhome/sqlpatch/ directory will also list other
 ###unnecessary files. All patches are numbers. May need to
 ###fix this logic, for the user may create a file/directory
 ###within the sqlpatch directory with the name containing all 
 ###numericals.
 ##[betausr1@rwsak11 sqlpatch]$ ls
 ##19769480  20831110  rhpmovedb.pl  sqlpatch.bat            sqlpatch.pl
 ##20299023  21359755  sqlpatch      sqlpatch_bootstrap.sql  sqlpatch.pm
 ##[betausr1@rwsak11 sqlpatch]$ 
 ##[betausr1@rwsak11 sqlpatch]$ ls -d [0-9]*
 ##19769480  20299023  20831110  21359755
 ##[betausr1@rwsak11 sqlpatch]$

 opendir (my $ab, $src_sqlpatch_path) or die "Could not open dir $src_sqlpatch_path";
 my @afiles = readdir $ab;
 closedir $ab;
 my $i;
 my @src_db_patches;
 foreach $i (@afiles)
 {
   my $b = looks_like_number($i);
   if ($b == 1)
   {
    push @src_db_patches, $i;
   }
 }
 


 #Get delta of source - destination.
 #If source = {1,2,3,4}  and destination = {3,4}
 #delta = {1,2}. src_db_patches will contain list
 #of source specific db patches. For example if
 #src_db_patches = {2} then the patches that needs to be
 #rolled back is the intersection of this list with delta.i.e {2}
 my @del_all_patches = array_delta(\@spatches,\@dpatches);
 my @del_db_patches = array_intersect(\@del_all_patches,\@src_db_patches); 

 my $del = join(',',@del_db_patches);

 print "Delta patches are ".$del."\n";

 $isUpgrade = isPatchUpg($srchome);

 if ($isUpgrade)
 {
    startdb_upgrade(TRUE);
 }


 my $cmd = "$src_datapatch -db $sid  -rollback $del -force";

 #print "Executing $cmd \n";
 #my $res = `$cmd`;
 #print $res;
 
 print "Exectuing datapatch rollback\n";
 my @res = run_cmd($cmd);
 my $rc = shift @res;

 output_lines(@res);
 if($rc ne "0")
 {
    exit($rc);    
 }

 if ($isUpgrade)
 {
    startdb_upgrade(FALSE);
 }

}
elsif(uc($patch_command) eq uc("apply"))
{
  $isUpgrade = isPatchUpg($dsthome);
  if ($isUpgrade)
  {
     startdb_upgrade(TRUE);
  }

  $ENV{"ORACLE_HOME"} = "$dsthome";
  $ENV{"ORACLE_SID"} = "$sid";

  system ('echo $ORACLE_HOME');
  print "\n";
  system ('echo $ORACLE_SID');
  print "\n";

  my $des_datapatch = "$dsthome/OPatch/datapatch";
 
  my $cmd = "$des_datapatch -db $sid ";

  #print $cmd;
  #my $res = `$cmd`;
  #print $res;

  my @res = run_cmd($cmd);
  my $rc = shift @res;

  output_lines(@res);
  if($rc ne "0")
  {
     exit($rc);    
  }

  if ($isUpgrade)
  {
     startdb_upgrade(FALSE);
  }
}
else
{
 print "\nInvalid Argument $ARGV[5] passed  to datapatch";
 print "\n Usage :- \n";
 print "$ARGV[0]/perl/bin/perl -I $ARGV[0]/perl/lib/ $ARGV[0]/crs/install/rhpdata/rhpmovedb.pl 	$ARGV[3] $ARGV[4] rollback\n";
}

sub array_intersect {


  my($rarray1, $rarray2, $risect) = @_;

  my (%union, %isect, $e);
  %union = %isect = ();

  foreach $e (@$rarray1) {
    $union{$e} = 1;
  }

  foreach $e (@$rarray2) {
    $isect{$e} = 1 if $union{$e};
  }

  @$risect = keys %isect;

}

sub array_delta {

 
  my($rarray1, $rarray2, $risect) = @_;

  my (%union, %isect, $e);
  %union = %isect = ();

  foreach $e (@$rarray2) {
    $union{$e} = 1;
  }

  foreach $e (@$rarray1) {
    $isect{$e} = 1 if !$union{$e};
  }

  @$risect = keys %isect;

}

sub array_print {

 my($array)= @_;
 print join(",",@$array);

}

sub startdb_upgrade
{

  my $isPre = $_[0];
  my $usr;
  my $passwd;
  my %session_mode;
  my $driver;
  my $sth;
  my @output;

  if(uc($patch_command) eq uc("apply"))
  {
    $ENV{'ORACLE_HOME'} = $dsthome;
  } else {
    $ENV{'ORACLE_HOME'} = $srchome;
  }
  $ENV{'ORACLE_SID'} = $sid;

  $session_mode{'ora_session_mode'} = 2; # sysdba = 2
  $driver = 'dbi:Oracle:';

  if ($isPre) {
    print "Performing operations to put Database in upgrade mode\n";
    # Try to connect DB instance
    # ora_session_mode => 2 means to connect as sysdba
    my $dbh = DBI->connect($driver, $usr, $passwd, \%session_mode);
    if (! $dbh)
    {
      print "Couldn't connect to database: ". $DBI::errstr . "\n";
      exit(1);
    }

    #Execute SQL stmts to start the db in upgrade mode 
    my $stmt1 = "alter system set cluster_database=false scope=spfile";
    my $sth = $dbh->do($stmt1);
    if (! $sth)
    {
      print "Couldn't execute SQL statement: ". $DBI::errstr . "\n";
      $dbh->disconnect
        or warn "Failed to disconnect: " . $DBI::errstr ."\n";
      exit(1);
    }

    #my $stmt2 =  "select DB_UNIQUE_NAME from v$database";
    $dbname = $dbh->selectrow_array("select DB_UNIQUE_NAME from v\$database");

    $dbh->disconnect
        or warn "Failed to disconnect: " . $DBI::errstr ."\n";

    my $cmd = "$srvctl stop database -d $dbname";
    my @res = run_cmd($cmd);
    my $rc = shift @res;

    sqlstartdb();
    print "Database started in upgrade mode\n";

  } else {

    print "Performing operations to start Database in normal mode\n";
    # Try to connect DB instance
    # ora_session_mode => 2 means to connect as sysdba
    my $dbh = DBI->connect($driver, $usr, $passwd, \%session_mode);
    if (! $dbh)
    {
      print "Couldn't connect to database: ". $DBI::errstr . "\n";
      exit(1);
    }

    #Execute SQL stmts to start the db in upgrade mode 

    my $stmt1 = "alter system set cluster_database=true scope=spfile";
    my $sth = $dbh->do($stmt1);
    if (! $sth)
    {
       print "Couldn't execute SQL statement: ". $DBI::errstr . "\n";
       $dbh->disconnect
        or warn "Failed to disconnect: " . $DBI::errstr ."\n";
       exit(1);
    }

    $dbh->disconnect
      or warn "Failed to disconnect: " . $DBI::errstr ."\n";

    sqlstopdb();

    my $cmd = "$srvctl start database -d $dbname";

    my @res = run_cmd($cmd);
    my $rc = shift @res;
   if($rc ne "0")
   {
      exit($rc);    
   }

    print "Database started back in normal mode\n";

  }

}

sub isPatchUpg
{

   my $dbhome = $_[0];
   my @output;
   my @upgtxt;
   $ENV{"ORACLE_HOME"} = "$dbhome";
   $ENV{"ORACLE_SID"} = "$sid";

   my $des_datapatch = "$dbhome/OPatch/datapatch";

   my $cmd = "$des_datapatch -db $sid  -prereq";

   print "Check if the patch requires Database in upgrade mode\n";

   my @res = run_cmd($cmd);
   my $rc = shift @res;

   my @upgtxt = grep(/must be in upgrade mode/, @res);

   if (scalar(@upgtxt > 0))
   {
      print "Patch needs database to be in upgrade mode\n";
      return TRUE;
   }
   else
   {  print "Patch does not need database to be in upgrade mode\n";
      return FALSE;
   }
}

sub sqlstartdb
{
  my $connect_string = '/ as sysdba';
  my $sqlplus_settings = '';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
startup upgrade;
exit;
EOF
};
}

sub sqlstopdb
{
  my $connect_string = '/ as sysdba';
  my $sqlplus_settings = '';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
shutdown;
exit;
EOF
};
}

sub run_cmd
{
  my $cmd = $_[0];

  my @output = `$cmd`;
  my $rc = $?;
  my $retCode = $rc >> 8;

  chomp(@output);

  return ($retCode, @output);
}


sub output_lines
{
  foreach my $line (@_)
  {
      print "$line\n";
  }
}


 
