# 
# $Header: rhp/utl/rhpmovedb.pl /st_rhp_19/4 2019/02/03 22:03:58 prajm Exp $
#
# rhpmovedb.pl
# 
# Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      rhpmovedb.pl - Perl script to execute Datapatch from DB Home 
#
#    DESCRIPTION
#     This file contains functions that computes the patches associated
#     with a database home. This is then used during datapatch apply 
#     operation.
#
#    NOTES
#      <other useful comments, qualifications, etc.>
#
#    MODIFIED   (MM/DD/YY)
#    prajm       12/25/18 - bacport of transaction prajm_bug-28942694
#    prajm       11/20/18 - fix bug#28942694
#    prajm       12/26/18 - fix lrg#21777429
#    prajm       11/19/18 - XbranchMerge prajm_bug-28831277 from main
#    prajm       11/04/18 - fix bug# 28831277
#    prajm       10/09/18 - fix bug#28747282
#    vgunredd    10/09/18 - fix bug 28762163
#    prajm       08/13/18 - RTI - 21071742
#    prajm       07/05/18 - fix bug#28220651
#    vgunredd    06/18/18 - add DV check
#    prajm       06/15/18 - fix bug#28182503
#    vgunredd    05/30/18 - fix rc for prereq
#    vgunredd    04/11/18 - fix bug 27811439
#    vgunredd    03/01/18 - fix bug 27577122
#    yifyang     12/14/17 - setting LD_LIBRARY_PATH
#    vgunredd    12/10/17 - workaround dbi connect problem
#    aaniyeri    08/14/17 - fix bug 26583245
#    yifyang     06/28/17 - bug-25804698
#    vgunredd    04/18/17 - fix err handling
#    yifyang     02/21/17 - ojvm check for rollong or nonrolling mode
#    vgunredd    02/10/17 - fix bug 25505841
#    lureyes     12/01/16 - Fix bug 25186865 
#    ksviswan    08/15/16 - XbranchMerge ksviswan_dbmove from st_has_12.2.0.1.0
#    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";

#OS type
my $SOLARIS = ($^O eq 'solaris' || $^O eq 'sunos');

# OCR config file location
my $OCR_CONFIG_FILE = "/etc/oracle/ocr.loc";

if ($SOLARIS)
{
    $OCR_CONFIG_FILE = "/var/opt/oracle/ocr.loc";
}

# OLR config file location
my $OLR_CONFIG_FILE = "/etc/oracle/olr.loc";

if ($SOLARIS)
{
    $OLR_CONFIG_FILE = "/var/opt/oracle/olr.loc";
}

# Error tags
my $RHPMOVEDB_FILE_FAIL = "PrGh-1105"; #could not open file or directory
my $RHPMOVEDB_CONNECTDB_FAIL = "PrGh-1106"; #could not connect to database
my $RHPMOVEDB_EXE_SQL_FAIL = "PrGh-1107"; #could execute SQL statement
my $RHPMOVEDB_INTERNAL_ERROR = "PrGo-1069"; #internal error
my $RHPMOVEDB_DVOWNER_FAIL = "PrGo-4125"; #cannot grant dv_patch_admin role to sys

# startup modes:
my $NORMAL_MODE = "NORMAL_MODE";
my $RESTRICTED_MODE = "RESTRICT";
my $UPGRADE_MODE = "UPGRADE";

my $DEBUG = $ENV{'SRVM_TRACE'};

my $isNonGI = isNonGI(); #true if local node is a standalone non-GI and non-SIHA node

#Command Line Arguments

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

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

#SID of the db to be moved - needed in case of non-GI and non-SIHA env
my $sid = getSID(); #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];

#Options: such as "nonrolling"
my $option = $ARGV[6];

#DB name of DB to be moved - needed in case of cluster env
my $dbname = getDBName(); #ARGV[7]

#Stop option for stopping DB
my $stopoption = $ARGV[8];

#get the src home.
$srchome = $ARGV[9];

my $pdblistfilename = $ARGV[10];


chomp($patch_command);

my $isPrimary = FALSE;
my $isCDB = FALSE;
my $isDVPatchAdminGranted = FALSE; #if true, DV_PATCH_ADMIN role has already been granted
my $isUpgrade = FALSE;
my $isNonrolling = FALSE;
my $isSkipprereq = FALSE;
my $isDBinUpgMode = FALSE;
my $srvctl = "$dsthome/bin/srvctl";
my $sqlplus = "$dsthome/bin/sqlplus";
my $isRac;
my $isPre12 = isPre12($dsthome);
my $isPre122 = isPre122($dsthome);
my $perlver = $^V;
substr ($perlver,0,1) = "";
my $pdblisttoopen = "null";
my $isPdbParallelDist = FALSE;
my $isCDBRootList = FALSE;


#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";
}
              
my $isStartNormalMode =  ($option =~ /startdbnormalmode/);
trace("start in normal mode $isStartNormalMode\n");
if($isStartNormalMode)
{
   startDBInNormalMode($dsthome, $sid);
}

my $pdblisttoopen = "null";
my $pdblisttoclose = "";
my $isPdbParallelDist = FALSE;
my $isCDBRootList = FALSE;

if(!($pdblistfilename eq "null"))
{
   $isPdbParallelDist = TRUE;
   trace("reading file $pdblistfilename\n");
   open(my $fh, '<:encoding(UTF-8)', $pdblistfilename)
   or die "Could not open file '$pdblistfilename' $!";
 
   my $pdbliststr = <$fh>;
   trace("pdb list $pdbliststr\n");
   
   close $fh;
  
   trace("deleting file $pdblistfilename\n"); 
   unlink $pdblistfilename or warn "Cannot unlink file $pdblistfilename: $!";
  
   my @pdbInofList = split /,/, $pdbliststr;
   my $pdbs  = "";
   my $closedPdbs = "";
   foreach (@pdbInofList) 
   {
      my $pdbInfoStr = $_;
      trace("$pdbInfoStr\n");
      my @pdbInfo = split /:/, $pdbInfoStr;
      trace("$pdbInfo[0]\n");
      $pdbs = $pdbs . $pdbInfo[0] . ",";
      trace("$pdbs\n");
  
      if($pdbInfo[1] eq "closed")
      {
         $closedPdbs =  $closedPdbs .  $pdbInfo[0] . ",";
      }
   }

   $pdbs = substr($pdbs, 0, length($pdbs)-1);

   $closedPdbs = substr($closedPdbs, 0, length($closedPdbs)-1);

   $pdblisttoopen = $pdbs;
   $pdblisttoclose = $closedPdbs;

   trace("pdbs $pdbs closed pdbs $closedPdbs \n");
   
   #check if the pdblist contains CBD$ROOT
   $isCDBRootList = ($pdblisttoopen =~ /CDB\\\$ROOT/);
   trace("cdb root $isCDBRootList\n");
}

if(!$isNonGI && ($option =~ /nonrolling/) && isDBRunning())
{ 
   #save the state of all the pdbs, before opening in restricted mode.
   my $vardbhome = $dsthome;

   trace("vardbhome : $vardbhome\n");
   if(checkIfDBExistsInSrcHome())
   {
      $vardbhome = $srchome;
   }

   my $isCDBDatabase = isCDB($vardbhome, $sid, TRUE);
   trace("isCDB $isCDBDatabase\n");

   if($isCDBDatabase && ($isCDBRootList || !$isPdbParallelDist)  
	 && !isPdbsStateSaved($vardbhome, $sid))
   {
      if($pdblisttoopen && !($pdblisttoopen eq "null"))
      {
	 my $pdbstosave = $pdblisttoopen;
	 if($isCDBRootList)
	 {
	    trace("updating pdb list\n");
	    my $cdbrootindex  = index ($pdbstosave, "CDB\\\$ROOT");
	    $pdbstosave = substr $pdbstosave, 0, $cdbrootindex-1;
	 }
	 trace("saving state for $pdbstosave\n");
	 runsqlquery($vardbhome, $sid, "alter pluggable database $pdbstosave save state instances=all");
      }
      else
      {
	 trace("saving state for all\n");
	 runsqlquery($vardbhome, $sid, "alter pluggable database all save state instances=all");
      }
   }
}


if(uc($patch_command) eq uc("rollback"))
{
  $ENV{"LD_LIBRARY_PATH"} = "$srchome/lib";
  $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 reportAndDie($RHPMOVEDB_FILE_FAIL, ($src_file_patches));
  open dfile, $dst_file_patches or reportAndDie($RHPMOVEDB_FILE_FAIL, ($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 reportAndDie($RHPMOVEDB_FILE_FAIL, ($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);

 trace("Delta patches are ".$del."\n");
 #Keep isPatchUpg() api available but use the passed option now
 if ($option =~ /nonrolling/)
  {
     $isUpgrade = TRUE;
  }
 else
  {
     $isUpgrade = FALSE;
  }

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


 my $cmd = "$src_datapatch -db $sid  -rollback $del -force";
 trace("Exectuing datapatch rollback\n");

 #print "Executing $cmd \n";
 #my $res = `$cmd`;
 #print $res;
 
 my @res = run_cmd($cmd, TRUE);
 my $rc = shift @res;

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

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

}
elsif(uc($patch_command) eq uc("apply"))
{
  $ENV{"LD_LIBRARY_PATH"} = "$dsthome/lib";
  $ENV{"ORACLE_HOME"} = "$dsthome";
  $ENV{"ORACLE_SID"} = "$sid";
  system ('echo $ORACLE_HOME');
  print "\n";
  system ('echo $ORACLE_SID');
  print "\n";

  $isUpgrade = FALSE;

  $isSkipprereq = ($option =~ /skipprereq/);
  trace("isSkipprereq = $isSkipprereq\n");
  if ($option =~ /nonrolling/)
  {
     $isNonrolling = TRUE; 
     trace("non-rolling mode ...\n");
     #Only the thread with CDB$ROOT in the pdb list 
     #can stop and start the db in restricted mode.
     #check if the CDB$ROOT is there in the pdb list 
     #or if it is not a pdbdistribution.
     if(($isCDBRootList || !$isPdbParallelDist) && !$isSkipprereq)
     {
        stopdb($dbname); #globally stop DB always so that
                        #script is re-runnable 
        trace("stopped db opening in restrict mode\n");
        startdb($dbname, $sid, $dsthome, $RESTRICTED_MODE);
        trace("started $dbname in restricted mode ...\n");
     }
     #In case of pdb distribution the threads which 
     #do not have CDB$ROOT in the pdb list should wait until 
     #the db is statrted in restricted mode.
     if((!$isCDBRootList && $isPdbParallelDist))
     {
         #if the script is invoked to patch only the 
         #the pdbs and not the cdb root then wait untill
         #the db is in restricted mode.
         while(!checkIfDBIsInRESTRICTEDMode())
         {
            trace("waiting $pdblisttoopen\n");
         }
         #after the db comes up 
         #wait for 60 secs to to open the pdbs
         #If we try to open immedialtely it fails 
         #as the db is still starting up
         sleep(60);
         runsqlquery($dsthome, $sid, 'select status from v\$INSTANCE');
         openpdbs($RESTRICTED_MODE, $dsthome, $sid, FALSE);
     }
  }
  
  my $home;
  if(!$isSkipprereq)
  {  
     #normal case
     $home = $dsthome;
  }
  else
  {
    # In case of re run if the failure is after starting 
    # the db in upgrade mode then we need use the dsthome and not srchome
    # checking if the db exists in srchome 
    # if it is then use srchome else use dsthome
    my $validHome = checkIfDBExistsInSrcHome();
    if($validHome)
    {
       trace("Running from srchome $srchome\n");
       $home = $srchome;
    }
    else
    {
       trace("Running from dsthome $dsthome\n");
       $srchome = $dsthome;
       $home = $dsthome;

       trace("setting the ORACLE_HOME to $dsthome\n");
       $ENV{"ORACLE_HOME"} = "$dsthome";
       $ENV{"ORACLE_SID"} = "$sid";

       # This is a re run case so check if the database is 
       # running or not. If the database is not running then 
       # start the database in upgrade mode directly from the dst home.
       if(!isDBRunning())
       {
          trace("start db sql...\n");
          sqlstartdb($UPGRADE_MODE);
          $isDBinUpgMode = TRUE;
       }
       else
       {
          if(checkIfDBIsInUPGMode())
          {
             trace("db in upg mode already skip start db sql...\n");
             $isDBinUpgMode = TRUE;
          }
       }
    }
  }
  
  $isCDB = isCDB($home, $sid);
  trace("isCDB $isCDB\n");
  my $val;

  if($isSkipprereq)
  {
     $val = runsqlquery($home, $sid, 'select database_role from v\$database');
  }
  else
  {
    my @sqlout = runSelectRowsSQL("select database_role from v\$database", $home, $sid);
    $val  = shift @sqlout;
  }
  if ($val =~ "PRIMARY")
  {
      $isPrimary = TRUE;
      trace("is a PRIMARY DB\n");
  }
  if (!$isPrimary || $isPre12)
  {
     trace("skip datapatch ...\n");
     if ($isNonrolling)
     {
         stopdb($dbname);
         startdb($dbname, $sid, $home, $NORMAL_MODE);
         trace("started $dbname in normal mode ...\n");
     }   
     exit(0);
  }

  if ($isNonrolling )
  {
      #detect if DB needs to be in upgrade mode for datapatch
      $isUpgrade = isPatchUpg($dsthome);
  }
  elsif ($isCDB)
  {
      trace("opening PDBs for datapatch ...\n");
      openpdbs($NORMAL_MODE, $dsthome, $sid, TRUE);
  }
  
  if ($isUpgrade)
  {
      if($isPdbParallelDist)
      {
         #report the error as pdb parallelisation 
         #cannot be performed for patches which need 
         #db in upgrade mode.
         reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-isPatchUpg-2"))
      } 
      trace("datapatch requires DB in upgrade mode.\n");
      startdb_upgrade(TRUE);
  }
  
  #resetting the oracle home env after 
  #starting the db in upg mode.
  if( $isSkipprereq )
  {
     trace("setting the ORACLE_HOME to $dsthome\n");
     $ENV{"ORACLE_HOME"} = "$dsthome";
     $ENV{"ORACLE_SID"} = "$sid";
  }
  grantDVPatchAdminRole($dsthome, $sid);

  my $rc = "1";
  # use data patch counter to run datapatch 5 times if it fails.
  # This has been necessitated by the scenario with BofA where
  # with 121 DB we see multiple runs being required to clean off datapatch run.
  my $datapatch_runcounter = 1;

  do
  {
     if ($isSkipprereq)
     {
        print ("trying datapatch run for $sid, attempt### $datapatch_runcounter ###\n");
     }
     my $des_datapatch = "$dsthome/OPatch/datapatch";
     my $cmd = "$des_datapatch -db $sid ";
     if($isPdbParallelDist)
     {
        $cmd = "$des_datapatch -db $sid -pdbs $pdblisttoopen -allow_pdb_mismatch";
     }
     trace("datapatch command $cmd\n");
     my @res = run_cmd($cmd, TRUE);
     $rc = shift @res;
  }
  while ($isSkipprereq && $rc ne "0" && ++$datapatch_runcounter < 7);
  

  #restart DB in normal mode before
  #checking if datapatch failed so that
  #the script is reentrant
  revokeDVPatchAdminRole($dsthome, $sid);
  #FIXE ME chekc if this needs to be checked for upgrade case.
  if ($isUpgrade && !$isPdbParallelDist)
  {
     if (!$isSkipprereq || $rc eq "0")
     {
        print ("datapatch succeeded for $sid \n");
        trace("restarting DB in normal mode.\n");
        startdb_upgrade(FALSE);
     }
  }
  elsif ($isNonrolling && !$isPdbParallelDist)
  {
     trace("restarting DB in normal mode ...\n");
     stopdb($dbname);
     startdb($dbname, $sid, $dsthome, $NORMAL_MODE);
  }

  if(!$isNonrolling && $isPdbParallelDist)
  {
     #close the pdbs which were in mounted state
     #and were opened for patching.
     runsqlquery($dsthome, $sid,"alter pluggable database $pdblisttoclose close");
  }
  if($rc ne "0")
  {
      print ("datapatch failed for $sid \n");
      trace("datapatch returned non-zero exit code.\n");
      exit($rc);
  }
}
else
{
 trace("\nInvalid Argument $ARGV[5] passed  to datapatch");
 trace( "\n Usage :- \n");
 trace("$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 @output;

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

  if ($isPre)
  {
  
    #set the home to src home if skiprereq is true
    if($isSkipprereq && !$isDBinUpgMode)
    {
       trace("setting the ORACLE_HOME to $srchome\n");
       $ENV{'ORACLE_HOME'} = $srchome;
       $home = $srchome;
    }

    if(!$isDBinUpgMode)
    {	
       trace("Performing operations to put Database in upgrade mode\n");
       #Need to check the database type
       my @sqlout =
	  runSelectRowsSQL("select value from v\$parameter where name = 'cluster_database'", $home, $sid);
       $isRac = shift @sqlout;
       trace("value of cluster_database param : $isRac\n");

       #the cluster_database is only changed if the database type is RAC
       if ($isRac eq "TRUE")
       {
	  trace("un-setting cluster_database param ...\n");
          #Execute SQL stmts to start the db in upgrade mode 
          runSQL("alter system set cluster_database=false scope=spfile", $home, $sid);
       }
    }
	
    #reset the home to dest home if skiprereq is true
    if($isSkipprereq && !$isDBinUpgMode)
    {
       trace("setting the ORACLE_HOME to $dsthome\n");
       $ENV{'ORACLE_HOME'} = $dsthome;
       $home = $dsthome;
    }

    if(!$isDBinUpgMode)
    {	
       stopdb($dbname);
       trace("stopped database\n");
       trace("start db sql...\n");
       sqlstartdb($UPGRADE_MODE);
       trace("Database started in upgrade mode\n");
    }
    if ($isCDB)
    {
       openpdbs($UPGRADE_MODE, $home, $sid, FALSE);
       trace("PDBs opened in upgrade mode\n");
    }
  }
  else
  {
    trace("Performing operations to start Database in normal mode\n");

    #the cluster_database is only changed if the database type is RAC
    if ($isRac eq "TRUE" || $isSkipprereq)
    {   
        trace("re-setting cluster_database param to true ...\n");
        #Execute SQL stmts to change cluster_database to true
        if($isSkipprereq)
        {
           runsqlquery($home, $sid,'alter system set cluster_database=true scope=spfile');
        }
        else
        { 
           runSQL("alter system set cluster_database=true scope=spfile", $home, $sid);
        }
    }

    sqlstopdb();
    trace("stopped database\n");

    startdb($dbname, $sid, $home, $NORMAL_MODE);
    trace("Database started back in normal mode\n");
  }
}

sub trace {
    print @_ if ($DEBUG);
}

# Reports the error in below given format and call die.
#
# Format derived from opsm/jsrc/oracle/cluster/gridhome/client/GridHomeActionResult.java
# that encapsulates the result of the Grid Home operations. Here it is utilized only
# to report error.
#
# String result with the following DTD:
#        <STATUS>0|1|2</STATUS>    where 0=SUCCESS, 1=WARNING, 2=EXCEPTION
#        <OUTPUT>
#        <FACILITY>PrGo</FACILITY>
#        <KEY>1000</KEY>
#        <PRINTKEY>true|false</PRINTKEY>
#        <ARG>arg1</ARG>
#        <ARG>arg2</ARG>
#        ....
#        </OUTPUT>
#        <OUTPUT>
#        ...
#        </OUTPUT>
#        <EXCEPTION>stack trace</EXCEPTION>
#    The STATUS tag is mandatory. There can be 0 or more OUTPUT tags. When 
#    there is an OUTPUT tag, it will always be followed by FACILITY, KEY and
#    PRINTKEY tags. The PRINTKEY tag should has only true or false value where
#    false means not to print the facility and key with the message and 
#    true means the facility and key will be printed with the message. There 
#    can be 0 or more ARG tags under the OUTPUT tag. There is only one EXCEPTION
#    tag which is used for sending stack trace to ghctl for printing into the
#    trace file.
#
sub reportAndDie {
    my $msgId = shift;
    my @args = shift;
    my $native = shift;
    my @final;

    trace("Message ID $msgId\n");
    my ($facility, $key) = split(/-/, $msgId, 2);

    # collate the args
    my @out_args;
    foreach (@args)
    {
        trace ("args $_");
        push(@out_args, "<ARG>".$_."</ARG>");
    }

    push(@final, "<OUTPUT>");
    push(@final, "<FACILITY>".$facility."</FACILITY><KEY>".$key."</KEY>");
    # always print key in message output
    push(@final, "<PRINTKEY>true</PRINTKEY>");
    push(@final, @out_args);
    push(@final, "</OUTPUT>");
    if (defined $native)
    {
        push(@final, "<OUTPUT>Native Error: ".$native."</OUTPUT>");
    }
    my $errStr = join("", @final);
    die "$errStr\n";
}

=head1 isPatchUpg

  Executes datapatch prereq to determine if
  DB needs to be in upgrade mode.

=head2 Parameters

  [0] Oracle home path

=head2 Returns

   true if datapatch reports that DB needs to be in upgrade mode

=cut
sub isPatchUpg
{

   if($isSkipprereq)
   {
      trace("return true skip pre checks");
      return TRUE;
   }
   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";

   trace("Check if the patch requires Database in upgrade mode\n");
   my @res = run_cmd($cmd, TRUE);
   my $rc = shift @res;

   my @upgtxt = grep(/must be in upgrade mode/, @res);
   if (scalar(@upgtxt > 0))
   {
      trace("Patch needs database to be in upgrade mode\n");
      return TRUE;
   }
   else
   {
      if($rc ne "0")
      {
          reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-isPatchUpg-1"))
      }
      return FALSE;
   }
}

sub sqlstartdb
{
  my $mode = $_[0];
  if ($mode eq $NORMAL_MODE)
  {
      $mode = "";
  }
  my $connect_string = '/ as sysdba';
  my $sqlplus_settings = '';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
startup $mode;
exit;
EOF
};
}

sub startDBInNormalMode
{
     my $home = $_[0];
     my $sid = $_[1];
     $ENV{"ORACLE_HOME"} = "$home";
     $ENV{"ORACLE_SID"} = "$sid";
 
     trace("starting DB in normal mode ...\n");
     stopdb($dbname);
     startdb($dbname, $sid, $home, $NORMAL_MODE);
     trace("started db in normal mode return");
     exit(0);
}

sub runsqlquery
{
  my $connect_string = '-S / as sysdba';
  my $sqlplus_settings = 'set pagesize 0 linesize 32767 feedback off verify off heading off echo off';
  my $home = $_[0];
  my $sid = $_[1];
  my $sql_query = $_[2];
  trace("stmt $sql_query\n");
  $ENV{'ORACLE_HOME'} = $home;
  $ENV{'ORACLE_SID'} = $sid;
  trace("ORACLE_HOME $home  ORACLE_SID $sid");
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$sql_query;
exit;
EOF
};
trace("result : $result\n");
return $result;
}



# This method checks if the database is there inside 
# the srchome or not in case the database is there 
# it returns true else it returns false.
sub checkIfDBExistsInSrcHome
{
  my $connect_string = '-S / as sysdba';
  my $sqlplus_settings = 'set pagesize 0 linesize 32767 feedback off verify off heading off echo off';

  $ENV{'ORACLE_HOME'} = $srchome;
  trace("srchome $srchome  sid $sid \n");
  $ENV{'ORACLE_SID'} = $sid;
  my $seltdbname = 'select DB_UNIQUE_NAME from v\$database';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$seltdbname;
exit;
EOF
};

  trace("result $result");
  if(index($result, $dbname) != -1)
  {
    return TRUE;
  } 
  else
  {
    return FALSE;
  }
}

sub checkIfDBIsInUPGMode
{
  my $connect_string = '-S / as sysdba';
  my $sqlplus_settings = 'set pagesize 0 linesize 32767 feedback off verify off heading off echo off';
  my $openmigrate = "OPEN MIGRATE";
  my $seltdbname = 'select STATUS from v\$INSTANCE';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$seltdbname;
exit;
EOF
};

  trace("result $result");
  if(index($result, $openmigrate) != -1)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

sub checkIfDBIsInRESTRICTEDMode
{
  my $connect_string = '-S / as sysdba';
  my $sqlplus_settings = 'set pagesize 0 linesize 32767 feedback off verify off heading off echo off';
  my $restricted = "RESTRICTED";
  my $seltdbname = 'select LOGINS from v\$INSTANCE';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$seltdbname;
exit;
EOF
};

  trace("result $result");
  if(index($result, $restricted) != -1)
  {
    my $selectstat = 'select status from v\$INSTANCE';
    $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$selectstat;
exit;
EOF
};
  trace("result $result");
  if(index($result, "OPEN") != -1)
  {
     return TRUE;
  }
  else
  {
     return FALSE;
  }
  }
  else
  {
    return FALSE;
  }
}

sub sqlstopdb
{
  my $connect_string = '/ as sysdba';
  my $sqlplus_settings = '';
  my $shutdownsql = "shutdown";
  if ($stopoption)
  {
      if (lc $stopoption ne "null")
      {
          trace("stopping DB with stop option $stopoption\n");
          $shutdownsql .= " $stopoption"; 
      } 
  }
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$shutdownsql;
exit;
EOF
};
}

sub openpdbs
{
  #bug 26409756: Datapatch skips closed PDBs.
  #So find PDBs in MOUNTED mode and open them.
  #https://docs.oracle.com/en/database/oracle/oracle-database/18/multi/administering-a-cdb-with-sql-plus.html#GUID-817703E2-B3E8-486F-8E3D-CFBB38911813
  #
  #bug 28762163: Only attempt to open closed PDBs in "normal" mode.
  #Can open all PDBs in "upgrade" and "restricted" modes
  #which are nonrolling operations.
  my $mode = $_[0];
  my $home = $_[1];
  my $sid = $_[2];
  my $openpdbsfordatapatch = $_[3];
  my $pdbstoopen = "all";

  if(!($pdblisttoopen eq "null"))
  {
     $pdbstoopen = $pdblisttoopen;
     if($isCDBRootList)
     {
        trace("updating pdb list\n");
        my $cdbrootindex  = index ($pdbstoopen, "CDB\\\$ROOT");
        $pdbstoopen = substr $pdbstoopen, 0, $cdbrootindex-1;
     }
  }
  trace("pdbs to open $pdbstoopen\n");
  if ($mode eq $NORMAL_MODE)
  {
      if(!$openpdbsfordatapatch)
      {
         trace("In normal mode pdbs are restored to saved state\n");
      }
      else
      {
          trace("opening PDBs $pdbstoopen in normal mode\n");
          runsqlquery($home, $sid,"alter pluggable database $pdbstoopen open read only");
      }
  }
  elsif ($mode eq $UPGRADE_MODE)
  {
      if($isSkipprereq)
      {
        runsqlquery($home, $sid,'alter pluggable database all open upgrade');
      }
      else
      { 
        runSQL("alter pluggable database all open upgrade", $home, $sid);
      }
  }
  elsif ($mode eq $RESTRICTED_MODE)
  {
      if($isPdbParallelDist)
      {
         runsqlquery($home, $sid,"alter pluggable database $pdbstoopen open read write restricted");
      }
      else 
      {
         runSQL("alter pluggable database $pdbstoopen open read write restricted", $home, $sid);
      }
  }
}


sub isPdbsStateSaved
{
  my $home = $_[0];
  my $sid = $_[1];

  $ENV{'ORACLE_HOME'} = $home;
  $ENV{'ORACLE_SID'} = $sid;

  my $regex="CON_NAME";
  my $connect_string = '-S / as sysdba';
  my $sqlplus_settings = '';
  my $pdbsstate = 'SELECT con_name, instance_name, state FROM dba_pdb_saved_states';
  my $result = qx { $sqlplus $connect_string <<EOF
$sqlplus_settings
$pdbsstate;
exit;
EOF
};

  trace("result $result\n");
  if(index($result, $regex) != -1)
  {
    return TRUE;
  }
  else
  {
    return FALSE;
  }
}

sub getPDBsToOpen
{
    my $home = $_[0];
    my $sid = $_[1];

    my $retval = "";
    my @sqlout = runSelectRowsSQL("select count(*) from v\$pdbs where open_mode = 'MOUNTED'",
                                 $home, $sid);
    my $val = shift @sqlout;
    trace("number of closed PDBs = $val\n");
    if ($val ne "0")
    {
        trace("querying names of closed PDBs\n");
        @sqlout = runSelectRowsSQL("select name from v\$pdbs where open_mode = 'MOUNTED'",
                                   $home, $sid);
        $retval = join(',', @sqlout);
        trace("closed PDBs : $retval\n");
    }
    return $retval;
}


sub run_cmd
{
  my $cmd = $_[0];
  my $isPrintOut = $_[1];
  my @stdout = ("");
  my $exitcode = 0;

  open (CMD, "$cmd 2>&1 |") or die "cannot execute command $cmd: $!";
  while (my $line  = <CMD>)
  {
      chomp $line;
      if ($isPrintOut)
      {
          print "$line\n";
      }
      else
      {
          trace("$line\n");
      }
      push @stdout, $line;
  }
  close (CMD);
  my $exitcode = $CHILD_ERROR >> 8;

  return ($exitcode, @stdout);
}

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


=head1 runSelectRowsSQL

  Executes SQL to select rows from a single column.

=head2 Parameters

  [0] statement selecting a single column
  [1] Oracle home path
  [2] Oracle SID

=head2 Returns

   array containing rows from the selected column

=cut
sub runSelectRowsSQL
{
    my $stmt = $_[0];
    my $home = $_[1];
    my $inst = $_[2];

    my $usr;
    my $passwd;
    my %attr;
    my $driver;
    my $sth;
    my @result;

    $ENV{'ORACLE_HOME'} = $home;
    $ENV{'ORACLE_SID'} = $inst;

    $attr{'ora_session_mode'} = 2; # connect as sysdba

    $driver = 'dbi:Oracle:';

    trace("connecting to DB instance ... $home, $inst, $stmt\n");
    my $dbh = DBI->connect($driver, $usr, $passwd, \%attr);
    if (! $dbh)
    {
        my @args = ($DBI::errstr);
        reportAndDie($RHPMOVEDB_CONNECTDB_FAIL, @args);
    }

    #workaround for Oracle bug
    $SIG{CHLD} = 'DEFAULT'; 

    my $sth = $dbh->prepare($stmt);
    if (!$sth)
    {
        $dbh->disconnect
            or warn "Failed to disconnect: " . $DBI::errstr ."\n";
        my @args = ($DBI::errstr);
        reportAndDie($RHPMOVEDB_EXE_SQL_FAIL, @args);
    }
  
    my $rv = $sth->execute();
    if (!$rv)
    {
        $dbh->disconnect
            or warn "Failed to disconnect: " . $DBI::errstr ."\n";
        my @args = ($DBI::errstr);
        reportAndDie($RHPMOVEDB_EXE_SQL_FAIL, @args);
    }

    my @row;
    while (@row = $sth->fetchrow_array())
    {
        push(@result, @row);
    }

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

    return @result;
}

=head1 runSQL

  Executes non-SELECT SQL statement.

=head2 Parameters

  [0] non-SELECT SQL statement
  [1] Oracle home path
  [2] Oracle SID

=cut
sub runSQL
{
    my $stmt = $_[0];
    my $home = $_[1];
    my $inst = $_[2];

    my $usr;
    my $passwd;
    my %attr;
    my $driver;
    my $sth;

    $ENV{'ORACLE_HOME'} = $home;
    $ENV{'ORACLE_SID'} = $inst;

    $attr{'ora_session_mode'} = 2; # connect as sysdba

    $driver = 'dbi:Oracle:';

    trace("connecting to DB instance ...\n");
    my $dbh = DBI->connect($driver, $usr, $passwd, \%attr);
    if (! $dbh)
    {
        my @args = ($DBI::errstr);
        reportAndDie($RHPMOVEDB_CONNECTDB_FAIL, @args);
    }

    #workaround for Oracle bug
    $SIG{CHLD} = 'DEFAULT'; 

    my $sth = $dbh->do($stmt);
    if (!$sth)
    {
        $dbh->disconnect
            or warn "Failed to disconnect: " . $DBI::errstr ."\n";
            my @args = ($DBI::errstr);
            reportAndDie($RHPMOVEDB_EXE_SQL_FAIL, @args);
    }

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

}

=head1 runSQLStmtms

  Executes non-SELECT SQL statements in same session.

=head2 Parameters

  [0] Oracle home path
  [1] Oracle SID
  [2..n] non-SELECT SQL statements

=cut
sub runSQLStmts
{
    my $home = $_[0];
    my $inst = $_[1];
    my @stmts = @_[2..$#_];

    my $usr;
    my $passwd;
    my %attr;
    my $driver;
    my $sth;

    $ENV{'ORACLE_HOME'} = $home;
    $ENV{'ORACLE_SID'} = $inst;

    $attr{'ora_session_mode'} = 2; # connect as sysdba

    $driver = 'dbi:Oracle:';

    trace("connecting to DB instance ...\n");
    my $dbh = DBI->connect($driver, $usr, $passwd, \%attr);
    if (! $dbh)
    {
        my @args = ($DBI::errstr);
        reportAndDie($RHPMOVEDB_CONNECTDB_FAIL, @args);
    }

    #workaround for Oracle bug
    $SIG{CHLD} = 'DEFAULT'; 

    for (my $idx = 0; $idx < scalar(@stmts); $idx++)
    {
       my $stmt = $stmts[$idx];
       my $sth = $dbh->do($stmt);
       if (!$sth)
       {
           $dbh->disconnect
               or warn "Failed to disconnect: " . $DBI::errstr ."\n";
           my @args = ($DBI::errstr);
           reportAndDie($RHPMOVEDB_EXE_SQL_FAIL, @args);
       }
    }
    $dbh->disconnect
        or warn "Failed to disconnect: " . $DBI::errstr ."\n";

}


sub startdb
{
    my $sid = $_[1];
    my $home = $_[2];
    my $mode = $_[3];

    if ($isNonGI)
    {
        sqlstartdb($mode);
    } 
    else

    {
        my $dbname = $_[0];
        my $cmd;
        if ($mode eq $RESTRICTED_MODE)
        {
            $cmd = "$srvctl start database -d $dbname -o RESTRICT";
        }
        else
        {
            $cmd = "$srvctl start database -d $dbname";
        }
        trace("executing $cmd\n");
        my @res = run_cmd($cmd, TRUE);
        my $rc = shift @res;
        if($rc ne "0")
        {
           exit($rc);
        }
    }

    if (isCDB($home, $sid)) #don't use global var $isCDB
    {
        openpdbs($mode, $home, $sid, FALSE);
        trace("opened PDBs in $mode mode\n");
    }
}

sub isCDB
{
    if ($isPre12)
    {
        return FALSE;
    }
    my $home = $_[0];
    my $sid = $_[1];
    #flag to run the cdb check through sqlplus
    #this is used to ignore errors 
    my $usesql =  $_[2];
    if($isSkipprereq || $usesql)
    {
      my $res = runsqlquery($home, $sid, 'select cdb from v\$database');
      if(index($res, "YES") != -1)
      {
          trace("is a CDB\n");
          return TRUE;
      }
      return FALSE;
    }

    my @sqlout = runSelectRowsSQL("select cdb from v\$database", $home, $sid);
    my $val = shift @sqlout;
    if ($val eq "YES")
    {
        trace("is a CDB\n");
        return TRUE;
    } 
    return FALSE;
}

sub isDVEnabled
{
    my $home = $_[0];
    my $sid = $_[1];
    my @sqlout = runSelectRowsSQL("select value from v\$option where parameter = 'Oracle Database Vault'",
                                  $home, $sid);
    my $val = shift @sqlout;
    if ($val eq "TRUE")
    {
        trace("Data vault is enabled\n");
        return TRUE;
    }
    return FALSE;
}

sub isDVPatchAdminRoleGranted
{
    my $home = $_[0];
    my $sid = $_[1];
    my $isDVEnabled = isDVEnabled($home, $sid);
    if (!$isDVEnabled)
    {
        return FALSE;
    }
    my @sqlout;
    if ($isCDB)
    {
       @sqlout = runSelectRowsSQL("select count(*) from cdb_role_privs where granted_role in ('DV_PATCH_ADMIN') and grantee = 'SYS'",
                                  $home, $sid);
    }
    else
    {
       @sqlout = runSelectRowsSQL("select count(*) from dba_role_privs where granted_role in ('DV_PATCH_ADMIN') and grantee = 'SYS'",
                                  $home, $sid);

    }
    my $val = shift @sqlout;
    if ($val ne "0")
    {
        trace("dv_patch_admin role has already been granted\n");
        return TRUE;
    }
    return FALSE;
}

sub grantDVPatchAdminRole
{
    my $home = $_[0];
    my $sid = $_[1];
    my $isDVEnabled = isDVEnabled($home, $sid);
    $isDVPatchAdminGranted = isDVPatchAdminRoleGranted($home, $sid);
    if ($isDVEnabled && !$isDVPatchAdminGranted)
    {
        trace("dv_patch_admin role needs to be granted\n");
        my $isDVOwnerRoleGranted = isDVOwnerRoleGranted($home, $sid);
        if ($isDVOwnerRoleGranted)
        {
            trace("granting dv_patch_admin role ...\n");
            runSQLStmts($home, $sid, "begin DBMS_MACSEC_ROLES.SET_ROLE('DV_OWNER'); end;",
                                     "grant dv_patch_admin to sys",
                                     "begin DBMS_MACADM.ENABLE_DV_PATCH_ADMIN_AUDIT; end;");
        }
        else
        {
            reportAndDie($RHPMOVEDB_DVOWNER_FAIL, ($sid));
        }
    }
}

sub isDVOwnerRoleGranted
{
    my $home = $_[0];
    my $sid = $_[1];
    my $isDVEnabled = isDVEnabled($home, $sid);
    if (!$isDVEnabled)
    {
        return FALSE;
    }
    my @sqlout;
    $isCDB = isCDB($home, $sid);
    if ($isCDB)
    {
       @sqlout = runSelectRowsSQL("select granted_role from cdb_role_privs where grantee = 'SYS'",
                                  $home, $sid);
    }
    else
    {
       @sqlout = runSelectRowsSQL("select granted_role from dba_role_privs where grantee = 'SYS'",
                                  $home, $sid);
    }
    for (my $idx = 0; $idx < scalar(@sqlout); $idx++)
    {
       my $role = $sqlout[$idx];
       trace("checking if role $role is DV_OWNER ...\n");
       if ($role eq "DV_OWNER")
       {
           return TRUE;
       }
       else
       {   
           trace("checking if role $role has been granted DV_OWNER role ...\n");
           my @sqlout2;
           if ($isCDB)
           {
               @sqlout2 = runSelectRowsSQL("select count(*) from cdb_role_privs where granted_role in ('DV_OWNER') and grantee = '$role'",
                                           $home, $sid);
           }
           else
           {
               @sqlout2 = runSelectRowsSQL("select count(*) from dba_role_privs where granted_role in ('DV_OWNER') and grantee = '$role'",
                                           $home, $sid);
           }
           my $val = shift @sqlout2;
           if ($val ne "0")
           {
               trace("dv_owner role has been granted\n");
               return TRUE;
           }
       }
    }
    return FALSE;
}

sub revokeDVPatchAdminRole
{
    my $home = $_[0];
    my $sid = $_[1];
    my $isDVEnabled = isDVEnabled($home, $sid);
    if ($isDVEnabled && !$isDVPatchAdminGranted)
    {
        trace("dv_patch_admin role needs to be revoked\n");
        runSQLStmts($home, $sid, 
                    "revoke dv_patch_admin from sys",
                    "begin DBMS_MACADM.DISABLE_DV_PATCH_ADMIN_AUDIT; end;");
    }
}

sub isPre12
{
    my $home = $_[0];
    $ENV{"LD_LIBRARY_PATH"} = "$home/lib";
    $ENV{"ORACLE_HOME"} = "$home";
    $sqlplus = "$dsthome/bin/sqlplus";
    my $cmd = "$sqlplus -V";
    my @res = run_cmd($cmd, FALSE);
    my $rc = shift @res;
    if($rc ne "0")
    {
        reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-isPre12-1"))
    }
    my $vrsnRegex = "Release 11\.";
    my @vrsnCheck = grep(/$vrsnRegex/, @res);
    if (scalar(@vrsnCheck > 0))
    {
        trace("DB version is pre-12.1\n");
        return TRUE;
    }
    trace("DB version is at least 12.1\n");
    return FALSE;
}

sub isPre122
{
    my $home = $_[0];
    if (isPre12($home))
    {
        trace("DB version is pre-12.2\n");
        return TRUE;
    }
    $ENV{"LD_LIBRARY_PATH"} = "$home/lib";
    $ENV{"ORACLE_HOME"} = "$home";
    $sqlplus = "$dsthome/bin/sqlplus";
    my $cmd = "$sqlplus -V";
    my @res = run_cmd($cmd, FALSE);
    my $rc = shift @res;
    if($rc ne "0")
    {
        reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-isPre122-1"))
    }
    my $vrsn121Regex = "Release 12\\.1";
    my @vrsn121Check = grep(/$vrsn121Regex/, @res);
    if (scalar(@vrsn121Check > 0))
    {
        trace("DB version is pre-12.2\n");
        return TRUE;
    }
    trace("DB version is at least 12.2\n");
    return FALSE;
}

sub stopdb
{
    if ($isNonGI)
    {
        sqlstopdb();
    }
    else
    {
        my $dbname = $_[0];
        my $cmd = "$srvctl stop database -d $dbname";
        if ($stopoption)
        {
            if (lc $stopoption ne "null")
            {
                trace("stopping DB with stop option $stopoption\n");
                $cmd .= " -o $stopoption"; 
            }
        }
        trace("executing $cmd\n");
        my @res = run_cmd($cmd, TRUE);
        my $rc = shift @res;
        if($rc eq "1")
        { #ignore AlreadyStoppedException indicated by 2
            exit($rc);
        }
    }
}


=head1 isNonGI

  Determines if this node is a standalone node
  i.e. non-GI and non-SIHA node.
  Basically, to decide whether to execute
  SQL or srvctl to restart DB.

=cut
sub isNonGI
{
    if (-e $OCR_CONFIG_FILE)
    {
        return FALSE;
    }
    trace("standalone non-GI and non-SIHA node\n");
    return TRUE;
}

=head1 getSID

  Returns name of local instance (Oracle SID)
  of the database being patched.

=cut
sub getSID
{
    $ENV{"ORACLE_HOME"} = "$dsthome";
    my $sidArg = $ARGV[2];
    if (lc $sidArg eq "null")
    {
        if ($isNonGI)
        {   #instance name must be specified in non-cluster env
            reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-SID-1"))
        }

        my $dbnameArg = $ARGV[7];

        my $nodename = getLocalNode();

        my $srvctl = "$dsthome/bin/srvctl";
        if (isPre122($dsthome))
        {
            trace("DB version is pre-12.2\n");
            my $cmd = "$srvctl status database -d $dbnameArg -S 1";
            my @res = run_cmd($cmd, FALSE);
            my $rc = shift @res;
            if($rc ne "0")
            {
                reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-SID-2"))
            }
            my $noderegex = "node_name={$nodename}";
            my @instdetails = grep(/$noderegex/, @res);
            my $inststr;
            if (scalar(@instdetails > 0))
            {
                $inststr = join " ", @instdetails;
                trace("instance details : $inststr\n");
            }
            else
            {
                my $instregex = "inst_name=";
                @instdetails = grep(/$instregex/, @res);
                if (scalar(@instdetails > 0))
                {
                    $inststr = join " ", @instdetails;
                    trace("Oracle Restart DB instance details : $inststr\n");
                }
            }
            if ($inststr)
            {
                my $loc1 = index($inststr, "inst_name="); 
                $loc1 = index($inststr, "{", $loc1+1)+1;
                my $loc2 = index($inststr, "}", $loc1); 
                $sidArg = (substr($inststr, $loc1, $loc2-$loc1));
                trace("Oracle SID : $sidArg\n");
                return $sidArg;
            }
            reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-SID-3"))
        }
        else
        {
            trace("DB version is at least 12.2\n");
            my $cmd = "$srvctl status database -d $dbnameArg -sid";
            my @res = run_cmd($cmd, FALSE);
            my $rc = shift @res;
            if($rc ne "0")
            {
                reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-SID-4"))
            }
            chomp($sidArg = (join("", @res)));
            trace("Oracle SID : $sidArg\n");
            return $sidArg;
        }
    }
    return $sidArg;
}

# runs the command srvctl status database -d dbname -S 1 
# to get the status of the database if none of the 
# instances are running returns false else if an 
# instance is running returns true
sub isDBRunning()
{
   my $cmd = "$srvctl status database -d $dbname -S 1";
   my @res = run_cmd($cmd, FALSE);
   my $rc = shift @res;
   if($rc ne "0")
   {
     reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-SID-5"))
   }
   my $isOpen     = 'state_details=\{Open';
   my $isResAcess = 'state_details=\{Restricted-Access';
 
   trace("output -> @res\n");
   if(grep(/$isOpen/i, @res))
   {
      return TRUE;
   }
   else
   {
      if(grep(/$isResAcess/i, @res))
      {
         return TRUE;
      }
      return FALSE;
   }
}

=head1 getDBName

  Returns the DB unique name of the database being patched.

=cut
sub getDBName
{
    $ENV{"ORACLE_HOME"} = "$dsthome";
    my $dbnameArg = $ARGV[7];
    my $useSQL = FALSE;
    if ($isNonGI)
    { #DB unique name not required in case of standalone non-cluster node
        trace("standalone non-cluster node\n");
        return $dbnameArg;
    }

    if ($dbnameArg)
    {
        if (lc $dbnameArg eq "null")
        {
            trace("query v\$database for DB unique name\n");
            $useSQL = TRUE;    
        }
    }
    else
    {
        trace("query v\$database for DB unique name\n");
        $useSQL = TRUE;
    }

    if ($useSQL)
    {
        trace("executing SQL query to retrieve DB unique name\n");
        my @sqlout = runSelectRowsSQL("select DB_UNIQUE_NAME from v\$database", $dsthome, $sid);
        my $val = shift @sqlout;
        trace("DB unique name: $val\n");
        return $val;
    }
    trace("using specified DB unique name\n");
    return $dbnameArg;
}

=head1 getLocalNode

  Returns name of the local node.
  To be called only in case of cluster/siha env.

=cut
sub getLocalNode
{
    my $crshome = getCRSHome();
    my $cmd = "$crshome/bin/olsnodes -l";
    my @res = run_cmd($cmd, FALSE);
    my $rc = shift @res;
    if($rc ne "0")
    {
        reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-localnode-1"))
    }
    my $localnode = join("", @res);
    trace ("local node : $localnode\n");
    return $localnode; 
}

=head1 getCRSHome

  Returns CRS home path.
  To be called only in case of cluster/siha env.

=cut
sub getCRSHome
{
    unless (-e $OLR_CONFIG_FILE)
    {
        reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-crs_home-1"));
    }
    trace("opening olr.loc \n");
    open (my $fh, "<", $OLR_CONFIG_FILE)
        or reportAndDie($RHPMOVEDB_FILE_FAIL, ($OLR_CONFIG_FILE)); 

    my $regex = "crs_home";
    while (my $row = <$fh>)
    {
        trace("olr.loc line: $row\n");
        if (index($row, $regex) != -1)
        {
            my $loc1 = index($row, $regex)+1; 
            $loc1 = index($row, "=", $loc1)+1;
            my $substrlen = length($row) - $loc1; 
            chomp(my $crshome = (substr($row, $loc1, $substrlen)));
            trace("crs home : $crshome\n");
            return $crshome;
        }
    }
    reportAndDie($RHPMOVEDB_INTERNAL_ERROR, ("rhpmovedb.pl-crs_home-2"))
}

