!/usr/bin/perl
$Header: rdbms/demo/rman_xttconvert/xttdriver.pl /main/10 2015/05/18 06:18:23 asathyam Exp $
xttdriver.pl
Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
NAME
xttdriver.pl - Cross Platform Transportable Tablespace DRIVER perl
script
DESCRIPTION
The script that is run to perform the main steps of the XTTS with Cross
Platform Incremental Backups procedure.
VERSION
2.0
NOTES
In version 2 12C options have been added which supports
"backup for transport" and "restore from platform"
In version 1.4 two new options -P and -G have been added.
In the current flow the datafiles are transported using a staging area
in the source and desination. This has been eliminated by using
dbms_file_transfer.
Parallelism is supported for rollforward if rollparallel is set
Bug Fixes
==========
Apr 24 2015 [Version 2]
----------------------------
1. Bug 20916691: --backup option broken
2. Bug 21028174: Add message when -L option is used
Nov 19 2014 [Version 2]
------------------------------
Project 47110: New options have been added
1. --backup
2. --restore
3. --bkpincr
4. --bkpexport
5. --resincrdmp
6. Bug 20192155: Fix for ORA-15126
7. Added --version option to give version
8. Added multiple source and destination directory support in getfile.
9. Removed putfile code
Nov 14 2014 [Version 1.4.2]
------------------------------
1. Enhancement: Added getparallel option to transfer datafiles in
parallel.
March 24 2014 [Version 1.4.1]
------------------------------
1. 18456868 - Made sure sqlplus and rman is present in the PATH
2. 18456834 - Added new option -F/--propfile to pass the location
of properties file if required. This was requested by EM.
3. 18459238 - dfcopydir was not required when we run using -G
option. The checking has been enhanced.
4. Even when convert instance is used, the -G should be run only
from destination instance and not the conversion instance
5. Impdp added ';' at the end which resulted in error. Fixed this
6. Added more debugging messages
7. Do more error checking before proceeding to next steps
8. Bug 17819946 - When number of tablespaces is more, the length
of list exceeds 2499 and fails. This is also fixed
9. If debug option is set to more than 1 the temporary files are
preserved. Otherwise it is deleted.
10. Bug 18797439 - Add new option -I/--propdir. All required files
will be picked from the runtime location of xttdriver.pl
11. Clean up the code such that the temp files are more readable
and can be mapped to backups
MODIFIED (MM/DD/YY)
asathyam 12/24/14 - Ver2 more changes
asathyam 11/18/14 - Version 2
asathyam 11/11/14 - Get parallel implemenation for getfile
asathyam 05/19/14 - EM wanted only propfile to be picked from
specified location.
asathyam 03/26/14 - Bug fixes. Details mentioned above
asathyam 01/30/14 - Implement doug's comments
asathyam 11/17/13 - Added support for same endian support
asathyam 10/20/13 - Bug 17673485 :- Added new option to support
individual specified tablespaces to be roll
forwarded in parallel
asathyam 10/02/13 - Bug 17673476 :- Fixed ASM related issues
asathyam 05/28/13 - New version 1.4
svrrao 03/15/13 - Enhance to v1.3. Introduce parallelism
fsanchez 03/15/13 - Creation
Globals
use Getopt::Long qw(:config no_ignore_case);
use POSIX ":sys_wait_h";
use strict;
use vars qw/ %opt /;
our %transfer = ();
our $context = %opt;
our @tablespaces = ();
our @properties = ();
our %props;
our $tmp;
our $xttpath;
our $rmantfrdf;
our $rmandstdf;
our $xttprep;
our $rmanpath;
our $connectstring;
our $sqlSettings;
our $errfile;
our $getfile;
our $xtts_incr_backup = "xib";
our @rolltbsArray = ();
my %tbsHash;
my $fixCnvSql;
my $fixRollSql;
our @forkArray = ();
my $xttrollforwardp;
my $rollParallel = 0;
my $getParallel = 0;
my $metaTransfer = 0;
my $scpParms = 0;
our $xttprop;
our $cnvrSql = "xttcnvrtbkupdest.sql";
our $stageondest;
our $backupondest;
our $platformid;
our $connectstringcnvinst;
our $myversion = "2.0";
our %dirHash;
our $dirmaxVal = 100;
Following constants are for the options that are used in the perl script
use constant PREPARE => 1;
use constant BCKPINCR => 2;
use constant NEWPLAN => 3;
use constant GETFILE => 5;
use constant BACKUP => 6;
use constant RESTORE => 7;
use constant RECOVER => 8;
use constant ROLLFORWARD => 9;
use constant RESINCRDMP => 10;
Used for generating xttplugin.txt
use constant LOCALFILE => 1;
use constant LINK => 2;
Used for checking version
use constant VER11 => 1;
use constant VER12 => 2;
Function : parseArg
Purpose : Command line options processing
Inputs : None
Returns : None
sub parseArg
{
GetOptions ($context,
'backup|b',
'bkpincr|B',
'convert|c',
'debug|d:i',
'bkpexport|E',
'generate|e',
'incremental|i',
'orahome|o=s',
'prepare|p',
'rollforward|r',
'determinescn|s',
'xttdir|D=s',
'propfile|F=s',
'getfile|G',
'propdir|I',
'clearerrorfile|L',
'orasid|O=s',
'restore|R',
'recover|X',
'resincrdmp|M',
'rolltbs|T=s',
'setupgetfile|S',
'ignoreerrors',
'version|v',
'help|h'
) or usage();
if ($context->{"help"})
{
usage();
}
if ($context->{"version"})
{
PrintMessage ("Version is $myversion");
exit (1);
}
if (defined ($context->{"debug"}) && ($context->{"debug"} == 0))
{
$context->{"debug"} = 1;
}
if ($ENV{'XTTDEBUG'})
{
$context->{"debug"} = $ENV{'XTTDEBUG'};
}
# User provided a directory, start from there to pick up files
if ($context->{"propdir"})
{
use File::Basename;
chdir (dirname($0));
}
if ($context->{"propfile"})
{
$xttprop = $context->{"propfile"};
}
else
{
$xttprop = "xtt.properties";
}
}
Function : touchErrFile
Purpose : Create the error file if requested
sub touchErrFile
{
my $message = $_[0];
open ERRFILE, ">>$errfile";
print ERRFILE "$messagen";
close ERRFILE;
}
Function : Unlink
Purpose : Delete the file if the debug level is less than 2
sub Unlink
{
my $delFile = $_[0];
my $force = $_[1];
if ($force || ($context->{"debug"} < 2))
{
unlink ($delFile);
}
}
Function : GetRMANTrace
Purpose : get the trace file name
sub GetRMANTrace
{
my $fileAppend = $_[0];
my $trace = "";
my $traceFile;
if ($context->{"debug"})
{
$traceFile = "$tmp/rmantrc_".GetTimeStamp().
"_".$fileAppend.".trc";
$trace = "debug trace $traceFile";
}
return ($trace, $traceFile);
}
Function : deleteErrFile
Purpose : check if the error file exists and delete
sub deleteErrFile
{
PrintMessage ("Deleting error file");
if (-e $errfile)
{
Unlink ($errfile, 1);
}
}
Function : checkErrFile
Purpose : check if the error file exists
sub checkErrFile
{
if (-e $errfile)
{
{
die "
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Error:
Some failure occurred. Check $errfile for more details
If you have fixed the issue, please delete $errfile and run it
again OR run xttdriver.pl with -L option
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
";
}
}
}
Function : debugprint
Purpose : print if debug value is >= passed debug level
sub debugprint
{
my $message = $_[0];
my $debuglevel = $_[1];
if ($context -> {"debug"} >= $debuglevel)
{
print $_[0] . "\n";
}
}
Function : debug
Purpose : print with level being 1
sub debug
{
debugprint ($_[0], 1);
}
Function : debug3
Purpose : print with level being 3
sub debug3
{
debugprint ($_[0], 3);
}
Function : debug4
Purpose : print with level being 4
sub debug4
{
debugprint ($_[0], 4);
}
Function : Die
Purpose : Print message and exit
sub Die
{
my $message = $_[0];
touchErrFile($message);
die "
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Error:
$message
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
";
}
Function : trim
Purpose : To remove whitespace from the start and end of the string
sub trim
{
my $string = $_[0];
$string =~ s/^s+//;
$string =~ s/\s+$//;
return $string;
}
Function : checkError
Purpose : check if any error was there and bail out
sub checkError
{
my ($errorMessage, $output) = @_;
#No need to check for error when user does not want to
if (defined($context->{"ignoreerrors"}))
{
return;
}
chomp ($output);
$output = trim ($output);
if (($output =~ /ORA[-][0-9]/) || ($output =~ /SP2-.*/))
{
debug $output;
Die("$errorMessage");
}
debug $output;
}
Function : PrintMessage
Purpose : Print the message that is passed to it
Inputs : Any text
Outputs : None
NOTES : None
sub PrintMessage
{
my $message = $_[0];
print "
$message
";
}
Function : parseProperties
Purpose : Parse the properties file xtt.properties
sub parseProperties
{
PrintMessage ("Parsing properties");
# Check if any failure occured and stop exection
checkErrFile();
@properties = qw(tablespaces stageondest storageondest dfcopydir
backupondest backupformat platformid oracle_home_cnvinst
oracle_sid_cnvinst asm_home asm_sid dstlink
srcdir dstdir srclink rollparallel getfileparallel
metatransfer desthost desttmpdir destuser);
open my $in, "$xttprop" or Die "$xttprop not found: $!";
while(<$in>)
{
next if /^#/;
$props{$1}=$2 while m/(\S+)=(.+)/g;
}
close $in;
if ($context -> {"debug"})
{
foreach my $pkey (keys %props)
{
print "Key: $pkey\n";
print "Values: $props{$pkey}\n";
}
}
@tablespaces = split(/,/, $props{'tablespaces'});
PrintMessage ("Done parsing properties");
}
Function : check
Purpose : check if the properties were defined
sub check
{
my $arg = $_[0];
my $key = $_[1];
debug "ARGUMENT $key";
if (!defined ($arg))
{
Die ("Please define xtt.properties:$key");
}
}
Function : checkNoPwdSSHEnabled
Purpose : For putfile option few properties will be not be required, the
same applies for the plan option
sub checkNoPwdSSHEnabled
{
my $outFile = "";
if ($metaTransfer == 0)
{
return;
}
$outFile = "$tmp/transferFile".GetTimeStamp().".log";
unless (system ("ssh $scpParms \"echo host\" 2> $outFile") == 0)
{
my @failArray = ();
open FAIL, "$outFile";
@failArray = <FAIL>;
close FAIL;
Die ("Passwordless SSH not enabled to machine\n@failArray");
}
}
Function : transferFiles
Purpose : For putfile option few properties will be not be required, the
same applies for the plan option
sub transferFiles
{
my @trnsfrFiles = @_;
my $output = 0;
my $files = "";
my $scpParmsL = "";
my $outFile = "$tmp/transferFile".GetTimeStamp().".log";
if ($metaTransfer == 0)
{
return;
}
foreach my $x (@trnsfrFiles)
{
chomp($x);
$files = $files."$x ";
}
$scpParmsL =
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ".
"NumberOfPasswordPrompts=0 ";
unless (system ("scp $scpParmsL $files ".$context -> {"remoteConnect"}.
":".$context->{'desttmp'}." 2> $outFile") == 0)
{
print "scp $scpParmsL $files ".$context -> {"remoteConnect"}.
":".$context->{'desttmp'}." 2> $outFile";
my @failArray = ();
open FAIL, "$outFile";
@failArray = <FAIL>;
close FAIL;
Die ("Unable to transfer files to destination machine\n@failArray");
}
Unlink ($outFile);
}
Function : getParallelProp
Purpose : Find how many files can be fetchde in parallel
sub getParallelProp
{
my $propValue = $_[0];
# We need to find how how many cores are there in the machine and then use
# that number to see if what the user has provided is less than that
# For now we assume that max is 8
# use Sys::Info;
# use Sys::Info::Constants qw( :device_cpu );
# my $info = Sys::Info->new;
# my $cpu = $info->device( CPU => %options );
#
# printf "CPU: %sn", scalar($cpu->identify) || 'N/A';
# printf "CPU speed is %s MHzn", $cpu->speed || 'N/A';
# printf "There are %d CPUsn" , $cpu->count || 1;
# printf "CPU load: %sn" , $cpu->load || 0;
if ($propValue > 8)
{
$propValue = 8;
PrintMessage ("Maximum $propValue files will be fetched in parallel");
}
return $propValue;
}
Function : checkProps
Purpose : For putfile option few properties will be not be required, the
same applies for the plan option
sub checkProps
{
PrintMessage ("Checking properties");
# Check if any failure occured and stop exection
checkErrFile();
# The following are required irrespective of what option is used
check $props{'tablespaces'}, $properties[0];
check $props{'platformid'}, $properties[6];
check $props{'backupformat'}, $properties[5];
check $props{'stageondest'}, $properties[1];
if (!defined($props{'parallel'}) ||
$props{'parallel'} <= 0)
{
$props{'parallel'} = 8;
}
if (defined($props{'metatransfer'}))
{
if (!defined($props{'desthost'}))
{
Die ("Files transfered requested, but remote host not defined");
}
if (!defined($props{'desttmpdir'}))
{
Die ("Files transfered requested, but remote dir not defined");
}
if (defined($props{'destuser'}))
{
$context -> {"remoteConnect"} =
$props{'destuser'}."@".$props{'desthost'};
}
else
{
$context -> {"remoteConnect"} = $props{'desthost'};
}
$context -> {"desttmp"} = $props{'desttmpdir'};
$metaTransfer = 1;
$scpParms =
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ".
"NumberOfPasswordPrompts=0 ".$props{'desthost'};
}
if (!defined($props{'getfileparallel'}) ||
$props{'getfileparallel'} <= 0)
{
$getParallel = 1;
}
else
{
$getParallel = getParallelProp($props{'getfileparallel'});
}
# Depending on option the checks are done
if (defined($context->{"setupgetfile"}) ||
defined($context->{"getfile"}))
{
check $props{'srcdir'}, $properties[12];
check $props{'dstdir'}, $properties[13];
check $props{'srclink'}, $properties[14];
}
if (defined($context->{"prepare"}))
{
check $props{'dfcopydir'}, $properties[3];
}
if (defined($context->{"rollforward"}))
{
check $props{'backupondest'}, $properties[4];
}
if (defined($context->{"srcdir"}) &&
defined($context->{"dstdir"}))
{
my @srcDir = split(/,/, $props{'srcdir'});
my @dstDir = split(/,/, $props{'dstdir'});
my $dstCount = scalar (@dstDir);
my $srcCount = scalar (@srcDir);
if (($dstCount > 1) && ($srcCount != $dstCount))
{
Die ("checkProps: Number of source objects does not match ".
"destination objects");
}
if ($dstCount >= $dirmaxVal)
{
Die ("checkProps: Number of dir objects cannot exceed $dirmaxVal");
}
}
PrintMessage ("Done checking properties");
}
Function : warnMesg
Purpose : Print message and exit
sub warnMesg
{
my $message = $_[0];
print "
Warning:
$message
";
}
Function : checkDbLink
Purpose : To check if the given DB link works
sub checkDbLink
{
my $sqlOutput ;
my $dblink = $_[0];
my $sqlQuery =
"select 'Count=' || count(*) from dual@"."$dblink;";
$sqlOutput = `sqlplus -L -s $connectstring <
$sqlSettings
$sqlQuery
quit;
EOF
`;
chomp ($sqlOutput);
$sqlOutput = trim ($sqlOutput);
if ($sqlOutput =~ m/^Count=(.*)/)
{
if ($1 <= 0)
{
Die ("Database link $props{'dstlink'} is not working");
}
}
else
{
Die ($sqlOutput);
}
}
Function : checkDBState
Purpose : To check state of database
sub checkDBState
{
my $sqlOutput ;
my $dblink = $_[0];
my $sqlQuery =
'select \'STATUS=\' || status FROM v\$instance;';
$sqlOutput = `sqlplus -L -s $connectstring <
$sqlSettings
$sqlQuery
quit;
EOF
`;
chomp ($sqlOutput);
$sqlOutput = trim ($sqlOutput);
if ($sqlOutput =~ m/^STATUS=(.*)/)
{
my $startStatus = trim ($1);
if ($startStatus ne "STARTED")
{
Die ("Open database in nomount mode and try rollforward again");
}
}
else
{
Die ($sqlOutput);
}
}
Function : checkCompat
Purpose : To check state of database
sub checkCompat
{
my $sqlOutput ;
my $sqlQuery =
'select to_number(replace(RPAD(value, 10, \'.0\'), \'.\')) from '.
'v\$parameter WHERE name = \'compatible\';';
$sqlOutput = `sqlplus -s -L $connectstring <
$sqlSettings
$sqlQuery
quit;
EOF
`;
chomp ($sqlOutput);
$sqlOutput = trim ($sqlOutput);
debug "$sqlOutputn";
if ($sqlOutput =~ m/.ORA-./)
{
Die ("Unable to check database compatibility");
}
if ($sqlOutput >= 121000)
{
return VER12;
}
else
{
return VER11;
}
}
Function : getPlatName
Purpose : To get name of source platform
sub getPlatName
{
my $sqlOutput ;
my $platid = $props{'platformid'};
my $sqlQuery =
'select platform_name from v\$transportable_platform '.
'where platform_id = '.$platid.';';
$sqlOutput = `sqlplus -s -L $connectstring <
$sqlSettings
$sqlQuery
quit;
EOF
`;
chomp ($sqlOutput);
$sqlOutput = trim ($sqlOutput);
if ($sqlOutput =~ m/.ORA-./)
{
Die ("Unable to fetch platform name");
}
return $sqlOutput;
}
Function : fetchDirEntry
Purpose : In order to transfer the files we need to define directory
objects, this function will check if they are defined.
sub fetchDirEntry
{
my $dirtoCheck = $_[0];
my $remoteLink = $_[1];
my @sqlOutput ;
my $sqlQuery ;
debug3 "fetchDirEntry: parms $remoteLink, $dirtoCheckn";
if ($remoteLink eq "")
{
debug "fetchDirEntry: remotelink not present\n";
$sqlQuery =
"SELECT 'DIRECTORY_NAME=' || DIRECTORY_NAME,
'DIRECTORY_PATH=' || DIRECTORY_PATH FROM ".
"ALL_DIRECTORIES WHERE DIRECTORY_NAME in ($dirtoCheck);";
}
else
{
$sqlQuery =
"SELECT 'DIRECTORY_NAME=' || DIRECTORY_NAME,
'DIRECTORY_PATH=' || DIRECTORY_PATH FROM ".
"ALL_DIRECTORIES\@".
"$remoteLink WHERE DIRECTORY_NAME in ($dirtoCheck);";
}
@sqlOutput = `sqlplus -L -s $connectstring <
$sqlSettings
$sqlQuery
quit;
EOF
`;
chomp (@sqlOutput);
return @sqlOutput;
}
Function : fetchCheckDirObjectsDST
Purpose : In order to transfer the files we need to define directory
objects, this function will check if they are defined.
sub fetchCheckDirObjectsDST
{
my $dirtoCheck = $_[0];
my $remoteLink = $_[1];
my $dirObjects = $_[2];
my $dirtoCheckLink;
my $dirObjPath;
my @sqlOutput ;
my $sqlQuery ;
my $found ;
my %dstHash = ();
debug "fetchCheckDirObjectsDST: Check dir path $remoteLinkn";
my @srcDir = split(/,/, $props{'srcdir'});
my @dstDir = split(/,/, $props{'dstdir'});
my $tempDir = '';
my $idx = 0;
my $dstCount = scalar (@dstDir);
my $srcCount = scalar (@srcDir);
if (($dstCount > 1) && ($srcCount != $dstCount))
{
Die ("fetchCheckDirObjectsDST: Number of source objects does not match ".
"destination objects");
}
foreach my $var (@dstDir)
{
$var = trim($var);
$tempDir = $tempDir."\'$var\',";
$dstHash{"$var"} = trim($dstDir[$idx]);
debug3 "fetchCheckDirObjectsDST: DestDir: ".$dstHash{"$var"}."\n";
if ($dstCount > 1)
{
$idx = $idx + 1;
}
}
if ($tempDir =~ m/(.*),/)
{
$tempDir = $1;
}
@sqlOutput = fetchDirEntry ($tempDir, $remoteLink);
foreach my $x (@sqlOutput)
{
$x = trim ($x);
if ($x =~ m/^DIRECTORY_NAME=(.*)DIRECTORY_PATH=(.*)/)
{
my $nameTemp = $1;
$dirObjPath = $2;
$nameTemp = trim($nameTemp);
if ($dirObjPath =~ m/(.*)\/$/) # Removes trailing spaces
{
$dirObjPath = $1;
}
$dirObjects->{$nameTemp} = $dirObjPath;
}
}
if ((keys%$dirObjects) == 0)
{
print "fetchCheckDirObjectsDST: Directory ".
"object \"$dirtoCheck\" does not exist\n";
Die (@sqlOutput);
}
return $dirObjPath;
}
Function : fetchCheckDirObjectsSRC
Purpose : In order to transfer the files we need to define directory
objects, this function will check if they are defined.
sub fetchCheckDirObjectsSRC
{
my $dirtoCheck = $_[0];
my $remoteLink = $_[1];
my $dirObjects = $_[2];
my $dirObjPath;
my @sqlOutput ;
my $sqlQuery ;
my $found ;
my %srcHash = ();
debug "fetchCheckDirObjectsSRC: Check dir path $remoteLinkn";
my @srcDir = split(/,/, $props{'srcdir'});
my @dstDir = split(/,/, $props{'dstdir'});
my $tempDir = '';
my $idx = 0;
my $dstCount = scalar (@dstDir);
my $srcCount = scalar (@srcDir);
if (($dstCount > 1) && ($srcCount != $dstCount))
{
Die ("fetchCheckDirObjectsSRC: Number of source objects does not match ".
"destination objects");
}
foreach my $var (@srcDir)
{
$var = trim($var);
$tempDir = $tempDir."\'$var\',";
$srcHash{"$var"} = trim($dstDir[$idx]);
debug3 "fetchCheckDirObjectsSRC: SRCDIR: ".$srcHash{"$var"}."\n";
if ($dstCount > 1)
{
$idx = $idx + 1;
}
}
if ($tempDir =~ m/(.*),/)
{
$tempDir = $1;
}
@sqlOutput = fetchDirEntry ($tempDir, $remoteLink);
foreach my $x (@sqlOutput)
{
$x = trim ($x);
if ($x =~ m/^DIRECTORY_NAME=(.*)DIRECTORY_PATH=(.*)/)
{
my $nameTemp = $1;
$dirObjPath = $2;
$nameTemp = trim($nameTemp);
if ($dirObjPath =~ m/(.*)\/$/)
{
$dirObjPath = $1;
}
# Create a hash with SRC directory and the DST directory object names.
$dirObjects->{$dirObjPath}->{"SRC"} = $nameTemp;
$dirObjects->{$dirObjPath}->{"DST"} = $srcHash{"$nameTemp"};
}
}
if ((keys%$dirObjects) == 0)
{
print "fetchCheckDirObjectsSRC: Directory ".
"object \"$dirtoCheck\" does not exist\n";
Die (@sqlOutput);
}
return;
}
Function : fixXTTNewDatafiles
Purpose : Fix the destination file by replacing the tag DESTDIR with the
actual location
sub fixXTTNewDatafiles
{
my $dirObjects = $_[0];
my $replaceFile = 0;
my @destArray = ();
debug "fixXTTNewDatafiles: Entered";
open(RMANDSTDF, "$rmandstdf") ||
Die "Cant find $rmandstdf";
@destArray = ;
close RMANDSTDF;
foreach my $line (@destArray)
{
if ($line =~ /.*DESTDIR:.*/)
{
$replaceFile = 1;
last;
}
}
if ($replaceFile)
{
open(RMANDSTDF1, ">$rmandstdf"."temp") ||
Die "Cant find $rmandstdf";
foreach my $line (@destArray)
{
if ($line =~ m/(.*),DESTDIR:(.*)\,(.*)/)
{
debug3 "fixXTTNewDatafiles: $1\n";
my $dest = $dirObjects->{$2}->{"DST"};
print RMANDSTDF1 "$1,$dest".":$3\n";
}
else
{
print RMANDSTDF1 "$line";
}
}
close RMANDSTDF1;
Unlink ("$rmandstdf", 1);
system("\\cp $rmandstdf"."temp $rmandstdf");
}
}
Function : verifySrcdirDatafiles
Purpose : Verify if the datafiles are present in the same directory
as defined in the source directory object
sub verifySrcdirDatafiles
{
my $dfPath = "$tmp/xttprepare.cmd";
my @dfList = ();
my @unidfList = ();
my %seen;
my $name;
debug ("verifySrcdirDatafiles: Entered");
open DFLIST, $dfPath ;
@dfList = ;
close DFLIST;
foreach my $df (@dfList)
{
chomp ($df);
debug3 "verifySrcdirDatafiles: $df";
if ($df =~ /^#DNAME:(.*)/)
{
$name = $1;
if (! (grep { $_ eq $name } @unidfList))
{
push (@unidfList, $1);
}
}
}
foreach my $key (keys %dirHash)
{
debug4 "verifySrcdirDatafiles: Hash key".$key."\n";
debug4 "verifySrcdirDatafiles: Hash value".$dirHash{"$key"}."\n";
}
foreach my $df (@unidfList)
{
debug3 "verifySrcdirDatafiles: $df\n";
if (!defined($dirHash{$df}))
{
Die ("Datafile path $df and source directory object ".
"path ".$dirHash{$df}." doest not match");
}
}
}
This routine is called from
Prepare Src
Backup Incr (backincr)
New Plan (newplan)
Getfile
Helper routine to batch the tablespaces sent into control file
queries, this way too many controlfile reads is avoided.
It takes one parameter, to tell who called it
1 => Prepare, 2 => backincr, 3 => newplan, 5 => getfile
sub generate_batch_tsoutput()
{
my $option = $_[0];
my $stageondest = $props{'stageondest'};
my $platform = "'$props{'platform'}'";
my $storageondest = $props{'storageondest'};
my $dfcopydir = $props{'dfcopydir'};
my $backupformat = $props{'backupformat'};
my $script_type = "PREPARE";
my $output;
my $tsbkupmap = "$tmp/tsbkupmap.txt";
my $incrbkups = "$tmp/incrbackups.txt";
my $number_of_tablespaces_per_batch =
int(($#tablespaces + 1)/$props{'parallel'});
# Check if any failure occured and stop exection
checkErrFile();
$number_of_tablespaces_per_batch = ($number_of_tablespaces_per_batch == 0)
? 1
: $number_of_tablespaces_per_batch;
my $total_sets = $number_of_tablespaces_per_batch * $props{'parallel'};
debug3 $connectstring;
debug3 "size of tablespace " . scalar(@tablespaces);
debug3 "No. of tablespaces per batch " . $number_of_tablespaces_per_batch;
my $countno = 0;
my $tablespace_str = "";
my $old_tablespace_str = "";
if ($option == BACKUP)
{
checkMove ($tsbkupmap);
checkMove ($incrbkups);
}
## Generate the rmanconvert.cmd and xttplan.txt from the output
## for Prepare
if (($option == PREPARE) || ($option == BACKUP) || ($option == GETFILE))
{
open(XTTPLAN, ">$xttpath") ||
die 'Cant find xttplan.txt, TMPDIR undefined';
if ($option == PREPARE)
{
open(RMANCONVERT, ">$rmanpath") ||
die 'Cant find rmanconvert.cmd, TMPDIR undefined';
}
# Generate new datafiles for 12c backup
elsif ($option == BACKUP)
{
$script_type = "BACKUP";
open(RMANDSTDF, ">$rmandstdf") ||
die 'Cant find $rmandstdf, TMPDIR undefined';
}
else
{
$script_type = "TRANSFER";
open(RMANDSTDF, ">$rmandstdf") ||
die 'Cant find $rmandstdf, TMPDIR undefined';
}
}
elsif ($option == BCKPINCR)
{
open XTTNEWPLAN, ">$tmp/xttplan.txt.new" or die $!;
$script_type = 'DETNEW';
}
elsif ($option == NEWPLAN)
{
$script_type = 'PREPNEXT';
}
## Based on parallelism, do so many tablespaces at a time.
my $ind = 0;
while ($ind < $total_sets ||
$ind < scalar(@tablespaces))
{
## Remove leading and trailing spaces.
$tablespaces[$ind] =~ s/^\s+//;
$tablespaces[$ind] =~ s/\s+$//;
## grab the rest
while ($ind < scalar(@tablespaces) &&
$ind >= $total_sets)
{
$tablespace_str .= "'" . $tablespaces[$ind] . "'";
if ($ind != $#tablespaces)
{
$tablespace_str .= ",";
}
$ind++;
}
if ($ind < $total_sets)
{
$tablespace_str .= "'" . $tablespaces[$ind] . "'";
if (($ind % $number_of_tablespaces_per_batch) !=
($number_of_tablespaces_per_batch-1))
{
$tablespace_str .= ",";
}
$countno++;
$ind++;
}
if ($countno == $number_of_tablespaces_per_batch ||
$ind == scalar(@tablespaces))
{
debug "TABLESPACE STRING :" . $tablespace_str;
$countno = 0;
## Subst. for tablespace names
open my $in, "xttprep.tmpl" or die $!;
if (($option == PREPARE) || ($option == BACKUP) ||
($option == GETFILE))
{
print "Prepare source for Tablespaces:
$tablespace_str $stageondest\n";
## Open the pareparesrc.sql file and substitute all the parameters
open my $prepout, ">xttpreparesrc.sql" or die $!;
while(<$in>)
{
s/%%stageondest%%/$stageondest/;
s/%%storageondest%%/$storageondest/;
s/%%dfcopydir%%/$dfcopydir/;
s/%%tmp%%/$tmp/;
s/%%parallel%%/$props{'parallel'}/;
s/%%type%%/$script_type/;
s/%%backupformat%%/$backupformat/;
if ($_ =~ /%%TABLESPACES%%/)
{
my @tbs = split(/,/, $tablespace_str);
foreach (@tbs)
{
print $prepout "$_,\n";
}
print $prepout "NULL\n";
}
else
{
print $prepout "$_";
}
}
close $in;
close $prepout;
## timestamp here -- start
print "xttpreparesrc.sql for $tablespace_str started at ".
(localtime) . "\n";
## Reset tablespace string
$old_tablespace_str = $tablespace_str;
$tablespace_str = "";
## Substitution ends here and xttpreparesrc.sql generated
my $output = `sqlplus -L -s $connectstring \@xttpreparesrc.sql`;
## timestamp here -- end
print "xttpreparesrc.sql for $tablespace_str ended at ".
(localtime) . "\n";
checkError ("Error in executing xttpreparesrc.sql", $output);
my @line = split /\n/, $output;
if (($option == BACKUP) || ($option == GETFILE))
{
foreach my $line (@line)
{
if ($line =~ /^#PLAN:/)
{
$line =~ s/^#PLAN://;
print XTTPLAN "$line\n";
if ($line =~ /(\S+)::::(\S+)/)
{
print RMANDSTDF "::$1\n";
}
}
elsif ($line =~ /^#TRANSFER:/)
{
$line =~ s/^#TRANSFER://;
if ($line =~ /.*source_file_name=(.*?),(.*?),(.*)/)
{
my $tsname = $1;
my $fullPath = trim($2);
my $fname = trim($3);
# Remove the trailing "/"
if ($fullPath =~ m/(.*)\/$/)
{
$fullPath = $1;
}
my $fullPath = $fullPath."/".$fname;
push(@{$transfer{$tsname}}, $fullPath);
}
}
elsif ($line =~ /^#NEWDESTDF:/)
{
$line =~ s/^#NEWDESTDF://;
print RMANDSTDF "$line\n";
}
}
# Verify that the files are in proper directory.
&verifySrcdirDatafiles();
}
if (($option == BACKUP) || ($option == PREPARE))
{
foreach my $line (@line)
{
if ($line =~ /^#PLAN:/)
{
$line =~ s/^#PLAN://;
print XTTPLAN "$line\n";
}
elsif ($line =~ /^#CONVERT:/)
{
$line =~ s/^#CONVERT://;
print RMANCONVERT "$line \n";
}
}
## Invoke the backup as copy routine once generated as the last
## step
my $rmancopycmd = "$tmp/xttprepare.cmd";
my ($rmanTrace, $traceFile) = GetRMANTrace("prepare");
$output = `rman target \/ $rmanTrace cmdfile $rmancopycmd`;
if ($output =~ /ERROR MESSAGE/)
{
Die("$output $!");
}
Unlink ($traceFile);
if ($option == BACKUP)
{
# We need to generate tsbkpupmap.txt for
&gentablespace_backupmap( $output ) ;
}
}
}
elsif ($option == BCKPINCR)
{
## Subst. for tablespace names
print "Prepare newscn for Tablespaces: $tablespace_str \n";
## Open the pareparesrc.sql file and substitute all the parameters
open my $prepout, ">xttdetnewfromscnsrc.sql" or die $!;
while(<$in>)
{
s/%%tmp%%/$tmp/;
s/%%type%%/$script_type/;
if ($_ =~ /%%TABLESPACES%%/)
{
my @tbs = split(/,/, $tablespace_str);
foreach (@tbs)
{
print $prepout "$_,\n";
}
print $prepout "NULL\n";
}
else
{
print $prepout "$_";
}
}
close $in;
close $prepout;
## Reset tablespace string
$tablespace_str = "";
## Substitution ends here and xttdetnewfromscnsrc.sql generated
$output = `sqlplus -L -s $connectstring \@xttdetnewfromscnsrc.sql`;
checkError ("Error in executing xttdetnewfromscnsrc.sql", $output);
my @line = split /\n/, $output;
foreach my $line (@line)
{
print XTTNEWPLAN $line . "\n";
}
}
elsif ($option == NEWPLAN)
{
## Subst. for tablespace names
print "Prepare newscn for Tablespaces: $tablespace_str \n";
## Open the pareparesrc.sql file and substitute all the parameters
open my $prepout, ">xttpreparenextiter.sql" or die $!;
while(<$in>)
{
s/%%tmp%%/$tmp/;
s/%%type%%/$script_type/;
if ($_ =~ /%%TABLESPACES%%/)
{
my @tbs = split(/,/, $tablespace_str);
foreach (@tbs)
{
print $prepout "$_,\n";
}
print $prepout "NULL\n";
}
else
{
print $prepout "$_";
}
}
close $in;
close $prepout;
## Reset tablespace string
$tablespace_str = "";
## Substitution ends here and xttpreparenextiter.sql generated
$output = `sqlplus -L -s $connectstring \@xttpreparenextiter.sql`;
checkError ("Error in executing xttpreparenextiter.sql", $output);
}
}
}
if (($option == PREPARE) || ($option == BACKUP) || ($option == GETFILE))
{
close (XTTPLAN);
if ($option == PREPARE)
{
close (RMANCONVERT);
}
elsif ($option == GETFILE)
{
close (RMANDSTDF);
# Replace the paths with the directory destination objects.
fixXTTNewDatafiles (\%dirHash);
# Ver: 1.4.1 We will generate the SQL file in the destination.
my $transferCount = 0;
open GETFILE1, ">$getfile";
foreach my $user (sort keys %transfer)
{
debug "$user: @{$transfer{$user}}\n";
foreach my $file (@{$transfer{$user}})
{
# Bug 17673476: If the destination file is for ASM, it does not
# work with automated filenames. So we convert the names here.
# The automated filenames will be of format "x.y.z". This will
# converted to "x_y_z"
my $srcFile;
my $destFile;
my $srcPath;
my $dstPath;
if ($file =~ m/(.*)\/(.*)/)
{
$srcFile = $2;
$srcPath = $dirHash{"$1"}->{"SRC"};
$dstPath = $dirHash{"$1"}->{"DST"};
}
if ($srcFile =~ m/(.*)\.([0-9]+)\.([0-9]+)/)
{
$destFile = "$1_$2_$3";
}
else
{
$destFile = $srcFile;
}
print GETFILE1
"$transferCount,$srcPath,$srcFile,$dstPath,$destFile\n";
}
$transferCount = $transferCount + 1;
}
close GETFILE1;
}
elsif ($option == BACKUP)
{
close (RMANDSTDF);
}
}
elsif ($option == BCKPINCR)
{
close (XTTNEWPLAN);
}
if ($metaTransfer)
{
my @trnsfrFiles = ();
if ($option == GETFILE)
{
push (@trnsfrFiles, "$tmp/xttnewdatafiles.txt");
push (@trnsfrFiles, "$tmp/getfile.sql");
transferFiles (@trnsfrFiles);
}
if ($option == BCKPINCR)
{
push (@trnsfrFiles, "$tmp/xttplan.txt");
transferFiles (@trnsfrFiles);
}
if ($option == PREPARE)
{
push (@trnsfrFiles, "$tmp/rmanconvert.cmd");
transferFiles (@trnsfrFiles);
@trnsfrFiles = ();
$context->{"desttmp"} = $props{'stageondest'};
push (@trnsfrFiles, $props{'dfcopydir'}."/*");
transferFiles (@trnsfrFiles);
$context -> {"desttmp"} = $props{'desttmpdir'};
}
if ($option == BACKUP)
{
@trnsfrFiles = ();
push (@trnsfrFiles, "$tmp/tsbkupmap.txt");
push (@trnsfrFiles, "$tmp/xttnewdatafiles.txt");
transferFiles (@trnsfrFiles);
open FILE, "$tmp/incrbackups.txt";
@trnsfrFiles = <FILE>;
close FILE;
$context->{"desttmp"} = $props{'stageondest'};
transferFiles (@trnsfrFiles);
$context -> {"desttmp"} = $props{'desttmpdir'};
}
}
}
End generate_batch_tsoutput
Function : assignGlobVars
Purpose : Assign global variables
sub assignGlobVars
{
if (! defined($context->{"xttdir"}))
{
if (defined($ENV{'TMPDIR'}))
{
$tmp = $ENV{'TMPDIR'};
}
else
{
Die ("TMPDIR not defined");
}
}
else
{
$tmp = $context->{"xttdir"};
}
$xttpath = "$tmp/xttplan.txt";
$rmantfrdf = "$tmp/rmantfrdf.sql";
$rmandstdf = "$tmp/xttnewdatafiles.txt";
$xttprep = "$tmp/xttprepare.cmd";
$rmanpath = "$tmp/rmanconvert.cmd";
$errfile = "$tmp/FAILED";
$getfile = "$tmp/getfile.sql";
$connectstring = "/ as sysdba";
$sqlSettings =
"SET TRIMSPOOL OFF LINES 32767 PAGES 0 FEEDBACK OFF ".
"VERIFY OFF DEFINE "&" TERMOUT OFF";
}
Function : checkMove
Purpose : Check if entry is present and move it
sub checkMove
{
my $srcPath = $_[0];
my $dstPath = $_[1];
if (!$dstPath)
{
my $timenow = time;
$dstPath = $srcPath.$timenow;
}
if (-e $srcPath)
{
system("\\mv $srcPath $dstPath");
}
}
Function : fixXTTDestFile
Purpose : Fix the destination file by replacing the tag DESTDIR with the
actual location
sub fixXTTDestFile
{
my $replaceFile = 0;
my @destArray = ();
my %dirHash;
open(RMANDSTDF, "$rmandstdf") ||
Die "Cant find $rmandstdf";
@destArray = ;
close RMANDSTDF;
foreach my $line (@destArray)
{
if (($line !~ m/::.*/) && ($line =~ m/.*\:.*/))
{
$replaceFile = 1;
last;
}
}
if ($replaceFile)
{
&fetchCheckDirObjectsDST($props{'dstdir'}, "", \%dirHash);
open(RMANDSTDF1, ">$rmandstdf"."temp") ||
Die "Cant find $rmandstdf";
foreach my $line (@destArray)
{
if (($line !~ m/::.*/) && ($line =~ /.*,(.*):.*/))
{
my $tempDir = $1;
$line =~ s/$tempDir:/$dirHash{"$tempDir"}/;
}
if ($line =~ m/(.*)\/(.*)\.([0-9]+)\.(.*)/)
{
$line = "$1/$2_$3_$4\n";
}
print RMANDSTDF1 "$line";
}
close RMANDSTDF1;
}
if ($replaceFile)
{
Unlink ("$rmandstdf", 1);
system("\\cp $rmandstdf"."temp $rmandstdf");
}
}
Function : getFilesSource
Purpose : Get files from the source database.
sub getFilesSource
{
PrintMessage ("Getting datafiles from source");
my $output;
my $connectstringcnvinst;
my $pid;
my @getArray = ();
my $getFileTemp;
&checkDbLink($props{'srclink'});
fixXTTDestFile();
$connectstringcnvinst = "/ as sysdba";
open FILE, "$getfile" or Die ("Cannot open $getfile");;
@getArray = ;
close FILE;
foreach my $x (@getArray)
{
chomp ($x);
if ($x =~ m/(.*?),(.*?),(.*?),(.*?),(.*)/)
{
my $sqlQuery =
"BEGIN
DBMS_FILE_TRANSFER.GET_FILE(
source_directory_object => '".$2."',
source_file_name => '".$3."',
source_database => '".$props{'srclink'}."',
destination_directory_object => '".$4."',
destination_file_name => '".$5."');
END;
/
";
$getFileTemp = "getfile"."_$2"."_$3"."_$1".".sql";
$getFileTemp = lc ($getFileTemp);
open FILE, ">$getFileTemp";
print FILE "$sqlQuery\nquit\n";
close FILE;
ChecktoProceed($getParallel);
$pid = fork();
if ($pid == 0)
{
PrintMessage ("Executing getfile for $getFileTemp");
$output = `sqlplus -L -s \"$connectstringcnvinst\" \@$getFileTemp`;
checkError ("Error in executing $getFileTemp", $output);
Unlink ($getFileTemp);
exit (0);
}
else
{
UpdateForkArray ($pid, $getParallel);
}
}
}
while((my $pid = wait()) > 0)
{
#sleep (1);
}
PrintMessage ("Completed getting datafiles from source");
}
Function : prepare
Purpose : Initial preparation for script to run.
sub prepare
{
###
### Prepare starts here
###
PrintMessage ("Starting prepare phase");
# Check if any failure occured and stop exection
checkErrFile();
my $timenow = time;
## Perform cleanup of files by renaming the xttplan.txt, rmanconvert.cmd
## and xttprep.cmd (datafile copy)
checkMove($xttpath, $xttpath.$timenow);
checkMove($rmanpath, $rmanpath.$timenow);
checkMove($xttprep, $xttprep.$timenow);
## For each tablespace, lets generate the scripts
my $stageondest = $props{'stageondest'};
my $platform = "'$props{'platform'}'";
my $storageondest = $props{'storageondest'};
my $dfcopydir = $props{'dfcopydir'};
debug "Parallel:" . $props{'parallel'};
## Remove trailing '/'s
$dfcopydir = $1 if($dfcopydir=~/(.*)\/$/);
## Check if previous ".tf" files present.
##
my @present = glob("$dfcopydir/*.tf");
if (@present)
{
## Try deleting any existing copied datafiles
system("\\rm -f $dfcopydir/*.tf");
sleep 5;
}
if ($context->{"setupgetfile"})
{
&fetchCheckDirObjectsSRC($props{'srcdir'}, "", \%dirHash);
foreach my $keys (keys %dirHash)
{
debug4 "prepare: Key is $keys\n";
debug4 "prepare: Value is ".$dirHash{"$keys"}."\n";
}
&generate_batch_tsoutput(GETFILE);
}
elsif ($context->{"backup"})
{
&generate_batch_tsoutput(BACKUP);
}
else
{
&generate_batch_tsoutput(PREPARE);
}
PrintMessage ("Done with prepare phase");
}
Function : checkExec
Purpose : Check if the rman and sqlplus executables are present in path
sub checkExec
{
if (defined ($context->{"orahome"}))
{
$ENV{'ORACLE_HOME'} = $context->{"orahome"};
}
else
{
if (defined ($ENV{'ORACLE_HOME'}))
{
$context->{"orahome"} = $ENV{'ORACLE_HOME'};
}
else
{
Die ("Niether ORACLE_HOME defined or orahome passed to script") ;
}
}
if (defined ($context->{"orasid"}))
{
$ENV{'ORACLE_SID'} = $context->{"orasid"};
}
else
{
if (defined ($ENV{'ORACLE_SID'}))
{
$context->{"orasid"} = $ENV{'ORACLE_SID'};
}
else
{
Die ("Niether ORACLE_SID defined or orasid passed to script") ;
}
}
debug "ORACLE_SID : $context->{"orasid"}";
debug "ORACLE_HOME : $context->{"orahome"}";
$ENV{'PATH'} = "$context->{"orahome"}/bin".":".$ENV{'PATH'};
$context->{'sqlexec'} = "$context->{"orahome"}/bin/sqlplus";
$context->{'rmanexec'} = "$context->{"orahome"}/bin/rman";
if (! -x $context->{"rmanexec"})
{
Die ("RMAN executable not found in path");
}
if (! -x $context->{"sqlexec"})
{
Die ("SQLPLUS executable not found in path");
}
}
Function : checkArg
Purpose : check if given arguments are fine
sub checkArg
{
if ($context->{"backup"} || $context->{"bkpincr"} ||
$context->{"bkpexport"} || $context->{"restore"} ||
$context->{"recover"} || $context->{"resincrdmp"})
{
my $ver = checkCompat();
if ($ver < VER12)
{
Die ("Unable to use given option with this version of DB. Min ver".
" required is 12.1.0.0");
}
}
}
Function : Main
Purpose : Main entry point for the program
sub Main
{
parseArg();
assignGlobVars();
checkArg();
# User wanted to clear file, do it so here
if ($context->{"clearerrorfile"})
{
Unlink ($errfile, 1);
}
parseProperties();
checkProps();
checkExec();
$context->{"platname"} = getPlatName();
#If we want specify tablespaces to be rolled forward, then store them in
#a hash
if ($context->{"rolltbs"})
{
@rolltbsArray = split (',', $context->{"rolltbs"});
foreach my $x (@rolltbsArray)
{
$tbsHash{"$x"}= 1;
}
}
if (($context->{"incremental"}) ||
($context->{"bkpincr"}) ||
($context->{"bkpexport"}))
{
backincr();
}
elsif ($context->{"rollforward"})
{
fixXTTDestFile();
rollforward();
}
elsif ($context->{"restore"})
{
fixXTTDestFile();
resrecBkp(RESTORE);
}
elsif (($context->{"recover"}) ||
($context->{"resincrdmp"}))
{
fixXTTDestFile();
resrecBkp(RECOVER);
if ($context->{"resincrdmp"})
{
createDumpFile();
plugin(LOCALFILE);
}
}
elsif ($context->{"determinescn"})
{
newplan();
}
elsif ($context->{"convert"})
{
#If the transfer is across the same endian then platformid will be
#set as 0.
if ($props{'platformid'} == 0)
{
genConvertDFNames();
}
else
{
convert();
}
}
elsif ($context->{"generate"})
{
plugin(LINK);
}
elsif (($context->{"prepare"}) || ($context->{"backup"}) ||
($context->{"setupgetfile"}))
{
checkNoPwdSSHEnabled ();
prepare();
}
elsif ($context->{"getfile"})
{
getFilesSource();
}
else
{
usage();
}
}
Incremental Starts here
Generates /tsbkupmap.txt
that maps tablespace to backup
sub gentablespace_backupmap
{
my @line = split /n/, $_[0];
my $tsbkupmap = "$tmp/tsbkupmap.txt";
my $incrbkups = "$tmp/incrbackups.txt";
my $backupformat = $props{'backupformat'};
my $i = 0;
my @fnums = ();
my @fnumorder = ();
my $chan;
my %bpfn;
my %bpts;
my %chfn;
my $tsname;
my $fno;
my $order = 0;
my $dmpFile = 0;
open(TSBKMAP, ">>$tsbkupmap") ||
Die("Cant open tablespace backup map file $!");
open(INCRBKUPS, ">>$incrbkups") || Die("Cant open incr backups file $!");
while ($i <= $#line)
{
my $str = $line[$i];
if ($str =~ /^ts::(.*)$/)
{
$tsname = $1;
$order = 0;
}
elsif ($str =~ /channel (.*): specifying datafile/)
{
$chan = $1;
}
elsif ($str =~ /.*input Data Pump dump file.*/)
{ ## piece name is on the next line
$i++;
$str = $line[$i];
$chan = $1;
$dmpFile = 1;
}
## In HP, an issue was reported where in the RMAN output was
## shown as 'input datafile fno' instead of 'input datafile file number'.
## So parse for both here.
elsif (($str =~ /input datafile file number=(\d+) name/) ||
($str =~ /input datafile fno=(\d+) name/))
{
$fno = $1 + 0;
push(@fnums, $fno);
$i++;
next;
}
elsif (defined($chan) && $#fnums >= 0 )
{
my @new = @fnums;
$chfn{$chan} -> {"fnumarray"} = \@new;
@fnums = ();
}
elsif ($str =~ /channel (.*): finished piece/)
{
## piece name is on the next line
$i++;
$str = $line[$i];
$chan = $1;
if ($str =~ /piece handle=(.+)[\/](\S+) tag/)
{
if ($dmpFile)
{
my $piece = $2;
debug "backup piece:" . $piece . "\n" ;
debug "TSNAME:" . $tsname;
print TSBKMAP "DMPEXP::$piece\n";
print INCRBKUPS $backupformat."/$piece \n";
}
else
{
$bpfn{$2} -> {"fnumarray"} = $chfn{$chan} -> {"fnumarray"};
$order = $order + 1;
$bpfn{$2} -> {"fnumorder"} = $order;
debug "TSNAME:" . $tsname;
$bpts{$2} = $tsname;
}
}
}
elsif ($str =~ /including current control file in backup set/)
{
do
{
$i++;
$str = $line[$i];
}
while ($str !~ /Finished backup/);
}
$i++;
}
foreach my $pkey (keys %bpfn)
{
print INCRBKUPS $backupformat . "/$pkey\n";
my @fns = @{$bpfn{$pkey} -> {"fnumarray"}};
my $sizefns = @fns;
print TSBKMAP $bpts{$pkey} . "::";
my $orderx = $bpfn{$pkey} -> {"fnumorder"};
foreach my $v (@fns)
{
$sizefns--;
print TSBKMAP $v;
print TSBKMAP "," if ($sizefns > 0);
}
print TSBKMAP ":::". $orderx;
print TSBKMAP "=" . $pkey;
print TSBKMAP "\n";
}
close(TSBKMAP);
close(INCRBKUPS);
}
sub backincr()
{
PrintMessage ("Backup incremental");
checkErrFile();
## Move out any existing xttplan.txt.new
my $xttpathnew = "$tmp/xttplan.txt.new";
my @trnsfrFiles = ();
my $tsbkupmap = "$tmp/tsbkupmap.txt";
my $incrbkups = "$tmp/incrbackups.txt";
if ( -e $xttpathnew )
{
my $timenow = time;
system("\\mv $xttpathnew $xttpath" . $timenow);
}
## Generate the next round xttplan.txt
&generate_batch_tsoutput(BCKPINCR);
## At this point, we must have a new xttplan.txt.new
## populated with required from_scn's for next incremental backup.
debug "Start backup incremental" ;
# Clean up any rmanincr.cmd from TMPDIR area.
my $rmanincrpath = "$tmp/rmanincr.cmd";
my $timenow = time;
if ( -e $rmanincrpath )
{
system("\\mv $rmanincrpath $rmanincrpath" . $timenow);
}
debug "Crossed mv " ;
my $backupformat = $props{'backupformat'};
debug "Crossed mv $backupformat" ;
## Remove trailing '/'s
$backupformat = $1 if($backupformat=~/(.*)\/$/);
if ($context->{"bkpexport"})
{
open(XTTPLAN, $xttpath) || die 'Cant find xttplan.txt, TMPDIR undefined';
my $rman_str1 = "set nocfau;";
my $rman_str;
my @scnArray = ();
my @scnSortedArray = ();
my $scn;
my $rmCmd;
while (<XTTPLAN>)
{
if (/(\S+)::::(\S+)/)
{
push (@scnArray, $2);
}
}
close XTTPLAN;
if (!@scnArray)
{
ErrorMessage ("BACKUPINCR: Could not find lowest SCN");
}
@scnSortedArray = sort (@scnArray);
$scn = $scnSortedArray[0];
$rmCmd = "BACKUP FOR TRANSPORT INCREMENTAL from scn $scn TABLESPACE ".
" $props{'tablespaces'}".
" FORMAT '$backupformat/%U'".
" DATAPUMP FORMAT '$backupformat/%U';";
open(RMANINCR, ">$tmp/rmanincr.cmd") || Die("Cant open rmanincr.cmd $!");
print RMANINCR "$rmCmd";
close RMANINCR;
}
else
{
## Generate $tmp/rmanincr.cmd
debug "Generate $tmp/rmanincr.cmd";
open(RMANINCR, ">$tmp/rmanincr.cmd") || Die("Cant open rmanincr.cmd $!");
## Parse out xttplan.txt and build the rman command
open(XTTPLAN, $xttpath) || die 'Cant find xttplan.txt, TMPDIR undefined';
my $rman_str1 = "set nocfau;";
my $rman_str;
while (<XTTPLAN>)
{
if (/(\S+)::::(\S+)/)
{
my $tablespace = $1;
my $scn = $2;
my $rman_str;
print RMANINCR $rman_str1 . "\n";
$rman_str = "host 'echo ts::$tablespace';";
print RMANINCR $rman_str . "\n";
if ($context->{"bkpincr"})
{
$rman_str = "backup for transport allow INCONSISTENT ".
"incremental from scn $scn ";
}
else
{
$rman_str = "backup incremental from scn $scn ";
}
print RMANINCR $rman_str . "\n";
if ($context->{"incremental"})
{
$rman_str = " tag tts_incr_update tablespace '$tablespace' ".
" format";
print RMANINCR $rman_str . "\n";
}
else
{
$rman_str = " tablespace '$tablespace' format";
print RMANINCR $rman_str . "\n";
}
$rman_str = " '$backupformat/%U';";
print RMANINCR $rman_str . "\n";
}
}
close(XTTPLAN);
close(RMANINCR);
}
## Execute the rman command here
my ($rmanTrace, $traceFile) = GetRMANTrace("incrbackup");
my $output_str = "rman target \/ $rmanTrace cmdfile $rmanincrpath";
print $output_str . "n";
my $output = rman target \/ $rmanTrace cmdfile $rmanincrpath
;
print $output . "n";
checkMove ($tsbkupmap);
checkMove ($incrbkups);
&gentablespace_backupmap( $output ) ;
if ($metaTransfer)
{
@trnsfrFiles = ();
push (@trnsfrFiles, "$tmp/tsbkupmap.txt");
transferFiles (@trnsfrFiles);
open FILE, "$tmp/incrbackups.txt";
@trnsfrFiles = <FILE>;
close FILE;
$context->{"desttmp"} = $props{'stageondest'};
transferFiles (@trnsfrFiles);
$context -> {"desttmp"} = $props{'desttmpdir'};
}
Unlink ($traceFile);
PrintMessage ("Done backing up incrementals");
exit;
}
Incremental Ends here
Check if element exists in array.
sub checkInArray
{
my $element = $_[0];
if (@rolltbsArray)
{
if ($tbsHash{"$element"})
{
return 1;
}
else
{
return 0;
}
}
else
{
return 1;
}
}
Rollforward Starts here - on destination
New changes in v1.1
xttrollforwarddest.sql is now a generated file.
All file processing is handled by perl itself.
xttrollforwarddest.sql will be called for each tablespace
The destination database will be started up in nomount
until all the tablespaces are rollforwarded.
sub rmconvertedincr
{
# Check if any failure occured and stop exection
checkErrFile();
my $backupondest = $props{'backupondest'};
my $asmhome = $props{'asm_home'};
my $asmsid = $props{'asm_sid'};
my $rmcmd = "";
if (defined $props{'asm_home'})
{
## Lets first delete the incremental backup if any
## this will happen through asmcmd
my $oh_saved = $ENV{'ORACLE_HOME'};
my $ohsid_saved = $ENV{'ORACLE_SID'};
my $asmcmd = 0 ;
my $asmcmd_str;
$ENV{'ORACLE_HOME'} = $asmhome;
$ENV{'ORACLE_SID'} = $asmsid;
# Remove trailing spaces
$backupondest =~ s/\s+$//;
$asmsid =~ s/\s+$//;
$asmcmd_str = "asmcmd rm $backupondest/$xtts_incr_backup";
debug $asmcmd_str . " $asmhome .. $asmsid \n" ;
$asmcmd = `asmcmd rm $backupondest/$xtts_incr_backup`;
debug "ASMCMD: $asmcmd\n";
$ENV{'ORACLE_HOME'} = $oh_saved;
$ENV{'ORACLE_SID'} = $ohsid_saved;
if ($asmcmd == 0)
{
Unlink ("$backupondest/$xtts_incr_backup", 1);
}
}
else
{
Unlink ("$backupondest/$xtts_incr_backup", 1);
}
}
Function : ChecktoProceed
Purpose : When running the roll forward in parallel, we will check if we can
fork any more jobs
sub ChecktoProceed
{
my $parallel = $_[0];
# Check if any failure occured and stop exection
checkErrFile();
my $running = 0;
if ($#forkArray <= 0)
{
return;
}
do
{
$running = 0;
foreach my $index (0 .. $#forkArray)
{
my $x = $forkArray[$index];
if ($x <= 0)
{
next;
}
my $kid = waitpid($x, WNOHANG);
if ($kid >= 0)
{
$running = $running + 1;
}
else
{
$forkArray[$index] = 0;
}
}
} while ($running >= $parallel);
return;
}
sub UpdateForkArray
{
my ($pid, $parallel) = @_;
if ($#forkArray < $parallel)
{
push (@forkArray, $pid);
}
else
{
foreach my $index (0 .. $#forkArray)
{
my $x = $forkArray[$index];
if ($x == 0)
{
$forkArray[$index] = $pid;
last;
}
}
}
}
Function : FixCnvScripts
Purpose : Fix the scripts that has details about incremental backup
sub FixCnvScripts
{
my $toFixSql = $_[0];
my $inpSql = $_[1];
my $fixedStr = $_[2];
my $platformid = $props{'platformid'};
my $fixedStrL = "";
my $len = 0;
open XTTS_INCR_BACKUP, "<$inpSql";
open XTTS_INCR_BACKUP1, ">$toFixSql";
my @arrayXttincr = ;
# Bug 20192155: Use xib instead of "xtts_incr_backup" to prevent
# ORA error ORA-15126 with ASM
$fixedStrL = substr($fixedStr, 0, 42);
$xtts_incr_backup = "xib"."_".$fixedStrL;
$len = length ($xtts_incr_backup);
if ($len > 50)
{
ErrorMessage ("Length of backupiece exceeds 50 chars $xtts_incr_backup");
}
foreach my $x (@arrayXttincr)
{
chomp($x);
if ($x =~ m/(.*)xtts_incr_backup(.*)\,/)
{
print XTTS_INCR_BACKUP1 "$1$xtts_incr_backup$2,\n";
}
elsif ($x =~ m/(.*)xtts_incr_backup(.*)/)
{
print XTTS_INCR_BACKUP1 "$1$xtts_incr_backup$2\n";
}
elsif (!(($platformid == 0) && ($x =~ m/(.*)pltfrmfr(.*)/)))
{
print XTTS_INCR_BACKUP1 "$x\n";
}
else
{
print XTTS_INCR_BACKUP1 "$x\n";
}
}
close XTTS_INCR_BACKUP1;
close XTTS_INCR_BACKUP;
}
Function : sortArrayOrder
Purpose : Sort the Array based on piece
sub sortArrayOrder
{
return sort {
(($a =~ /::(.*):::.*/)[0] <=> ($b =~ /::(.*):::.*/)[0] ||
($a =~ /:::(.*?)=/)[0] <=> ($b =~ /:::(.*?)=/)[0]
)
} @_;
}
UTIL functions
Following functions are useful for conversion, rollforward etc
sub ConvertBackup
{
my $backup = $_[0];
my $dfno = $_[1];
my $fixCnvSql;
my $outputCnvrt;
$fixCnvSql = "$tmp/xxttconv_$backup"."_$dfno.sql";
FixCnvScripts ($fixCnvSql, $cnvrSql, $backup."_".$dfno);
Unlink ("$backupondest/$xtts_incr_backup", 1);
my $outputCnvrt =
`sqlplus -L -s \"$connectstringcnvinst\" \@$fixCnvSql $stageondest/$backup $backupondest $platformid`;
checkError ("$fixCnvSql execution failed", $outputCnvrt);
Unlink ($fixCnvSql);
}
sub RollPiece
{
my $sqlfile = $_[0];
my $outputCnvrt;
## now call generated rollforward
$outputCnvrt =
`sqlplus -L -s \"/ as sysdba\" \@$sqlfile`;
checkError ("$sqlfile execution failed", $outputCnvrt);
}
sub ConvertRoll
{
my ($fixedStr, $oldre, $rb, $rend, $bkupList, $rmList) = @_;
my $rollFile;
$rollFile = "$tmp/xxttroll_$fixedStr.sql";
open (XTTROLL, ">$rollFile") or Die $!;
print XTTROLL $rb;
foreach my $y (@{$rmList})
{
print XTTROLL $y;
}
foreach my $backup (@{$bkupList})
{
my $re = $oldre;
ConvertBackup ($backup, $fixedStr);
$re =~ s/##xtts_incr_backup/$xtts_incr_backup/;
print XTTROLL $re;
}
print XTTROLL $rend;
close XTTROLL;
RollPiece ($rollFile);
rmconvertedincr();
}
sub RestoreRecover
{
my ($fixedStr, $finalRes) = @_;
my $cmdfile = "$tmp/xttresrec_$fixedStr.cmd";
open RMANRE, ">$cmdfile";
print RMANRE "$finalResn";
close RMANRE;
my ($rmanTrace, $traceFile) = GetRMANTrace("resrec");
my $output = rman target \/ $rmanTrace cmdfile $cmdfile
;
debug ($output);
if ($output =~ /ERROR MESSAGE/)
{
Die("$output $!");
}
Unlink ($traceFile);
}
End of UTIL functions
Following functions are useful for conversion, rollforward etc
Function : clearUpRollfwd
Purpose : Clear the scripts and converted incrementals after rollfwd
sub clearUpRollfwd
{
Unlink ($fixCnvSql);
Unlink ($xttrollforwardp);
Unlink ($fixRollSql);
rmconvertedincr();
}
sub rollforward()
{
PrintMessage ("Start rollforward");
# Check if any failure occured and stop exection
checkErrFile();
my $connectstringdest = "/ as sysdba";
my $ohcnv = $props{'cnvinst_home'};
my $ohcnvsid = $props{'cnvinst_sid'};
my $tsbkupmappath = "$tmp/tsbkupmap.txt";
my $tsn;
my $fixedSql;
my $pid = 0;
my $i = 0;
my $parent = 0;
my $count = 0;
my $newrm;
my $oldre;
my $rb = q(
set serveroutput on;
DECLARE
outhandle varchar2(512) ;
outtag varchar2(30) ;
done boolean ;
failover boolean ;
devtype VARCHAR2(512);
BEGIN
DBMS_OUTPUT.put_line('Entering RollForward');
-- Now the rolling forward.
devtype := sys.dbms_backup_restore.deviceAllocate;
sys.dbms_backup_restore.applySetDatafile(
check_logical => FALSE, cleanup => FALSE) ;
DBMS_OUTPUT.put_line('After applySetDataFile');
);
my $rm = q(
sys.dbms_backup_restore.applyDatafileTo(
dfnumber => ##fno,
toname => ##fname,
fuzziness_hint => 0, max_corrupt => 0, islevel0 => 0,
recid => 0, stamp => 0);
);
my $rmend = q(
DBMS_OUTPUT.put_line('Done: applyDataFileTo');
);
my $re = q(
DBMS_OUTPUT.put_line('Done: applyDataFileTo');
-- Restore Set Piece
sys.dbms_backup_restore.restoreSetPiece(
handle => '##backupondest/##xtts_incr_backup',
tag => null, fromdisk => true, recid => 0, stamp => 0) ;
DBMS_OUTPUT.put_line('Done: RestoreSetPiece');
-- Restore Backup Piece
sys.dbms_backup_restore.restoreBackupPiece(
done => done, params => null, outhandle => outhandle,
outtag => outtag, failover => failover);
DBMS_OUTPUT.put_line('Done: RestoreBackupPiece');
);
my $rend = q(
sys.dbms_backup_restore.restoreCancel(TRUE);
sys.dbms_backup_restore.deviceDeallocate;
END;
/
exit
);
$stageondest = $props{'stageondest'};
$backupondest = $props{'backupondest'};
$platformid = $props{'platformid'};
$re =~ s/##backupondest/$backupondest/;
$oldre = $re;
if (!defined($ohcnv))
{
$connectstringcnvinst = "/ as sysdba";
}
else
{
debug "convert instance: $ohcnv \n";
debug "convert instance: $ohcnvsid \n";
$connectstringcnvinst =
"/\@(DESCRIPTION=(ADDRESS=(PROTOCOL=BEQ)(PROGRAM=$ohcnv/bin/oracle)".
"(ARGV0=oracle$ohcnvsid)(ARGS='(DESCRIPTION=(LOCAL=YES)".
"(ADDRESS=(PROTOCOL=BEQ)))')".
"(ENVS='ORACLE_HOME=$ohcnv,ORACLE_SID=$ohcnvsid'))".
"(CONNECT_DATA=(SID=$ohcnvsid))) as sysdba";
}
my %tsbkupmap;
my @tsArray = ();
open my $in, $tsbkupmappath or Die ("$tsbkupmappath $!");
@tsArray = <$in>;
close $in;
foreach my $x (sortArrayOrder @tsArray)
{
$_ = $x;
next if /^#/;
if (m/(\S+):::.*?=(\S+)/g)
{
push (@{$tsbkupmap{$1}}, $2);
}
}
##
## Putting database on target in nomount
## as it could cause the following error.
## ORA-00600: internal error code, arguments: [2130], [33], [32], [4]
## The reason for this is :
##
## The code is trying to access KCCDEDBF (datafile) record# 33 when there
## is only record# 32. It looks when you try to apply the incremental
## backup on linux box, you are expecting the datafile# 33 to exists in the
## target database. However, that can't be always true.
##
## This is worked around by starting the target database iin nomount.
## The convert call/script should work in nomount state of database too.
##
if (defined($context->{"rolltbs"}))
{
checkDBState();
}
debug3 ("ROLLFORWARD: Starting DB in nomount mode");
my $outputstart = sqlplus -L -s \"/ as sysdba\" \@xttstartupnomount.sql
;
checkError ("Error in executing xttstartupnomount.sql", $outputstart);
## Generate xttrollforwarddest.sql that is the script
## that will rollforward all the converted datafiles.
my $tsbkcount = scalar keys %tsbkupmap;
if ($tsbkcount == 0)
{
Die ("No tablepsace entries found");
}
my @sortedkeys = sort keys %tsbkupmap;
foreach $tsn (@sortedkeys)
{
my ($ts, $rdfno, $order) = split(/::/, $tsn);
my $fixedStr;
if ($context->{"rolltbs"})
{
my $checkArray = checkInArray($ts);
if ($checkArray == 0)
{
next;
}
}
debug "rdfno " . $rdfno . "\n";
my @rdfnos = split /,/, $rdfno;
open (ROLLPLAN, "$tmp/xttnewdatafiles.txt") or Die $!;
my $scrape = 0;
# Check if any failure occured and stop exection
checkErrFile();
debug "BEFORE ROLLPLAN\n";
my @rmList = ();
while (<ROLLPLAN>)
{
if ($scrape == 1 && $_ !~ /::/)
{
chop;
my $oldrm = $rm;
my $dfstr = $_;
my ($dfno, $dfname) = split /,/, $dfstr;
my $ind;
## include the dfno only if present in the tsbkupmap.txt list
## of dfnos.
my $found = 0;
for ($ind = 0; $ind <= $#rdfnos ; $ind++)
{
if ($dfno == $rdfnos[$ind])
{
$found = 1;
last;
}
}
if ($found == 1)
{
debug "datafile number : $dfno \n";
debug "datafile name : $dfname\n";
$newrm = $rm;
$newrm =~ s/##fno/$dfno/;
$newrm =~ s/##fname/'$dfname'/;
push (@rmList, $newrm);
}
}
if (/^::$ts$/)
{
$scrape = 1;
}
elsif (/^::/)
{
$scrape = 0;
}
}
push (@rmList, $rmend);
close(ROLLPLAN);
debug "AFTER ROLLPLAN\n";
$rollParallel = $props{'rollparallel'};
$fixedStr = $rdfno;
$fixedStr =~ tr/,/_/;
# Bug 20192155: Use xib instead of "xtts_incr_backup" to prevent
# ORA error ORA-15126 with ASM
my $fixedStrL = substr($fixedStr, 0, 25);
if ($rollParallel)
{
ChecktoProceed($rollParallel);
$pid = fork();
if ($pid == 0)
{
ConvertRoll ($fixedStrL, $oldre, $rb ,$rend, \@{$tsbkupmap{$tsn}},
\@rmList);
exit (0);
}
else
{
UpdateForkArray ($pid, $rollParallel);
}
}
else
{
ConvertRoll ($fixedStrL, $oldre, $rb ,$rend, \@{$tsbkupmap{$tsn}},
\@rmList);
}
}
while((my $pid = wait()) > 0)
{
#sleep (1);
}
# Check if any failure occured and stop exection
checkErrFile();
my $outputstart = sqlplus -L -s \"/ as sysdba\" \@xttdbopen.sql
;
PrintMessage ("End of rollforward phase");
exit;
}
Function : resrecBkp
Purpose : Restore recover backup piece
sub resrecBkp
{
my $option = $_[0];
PrintMessage ("Start restore/recover");
# Check if any failure occured and stop exection
checkErrFile();
my $tsbkupmappath = "$tmp/tsbkupmap.txt";
my $tsn;
my $fixedSql;
my $pid = 0;
my $i = 0;
my $parent = 0;
my $count = 0;
my $newrm;
my $oldre;
my $platName = $context->{"platname"};
$stageondest = $props{'stageondest'};
$platformid = $props{'platformid'};
my %tsbkupmap;
my @tsArray = ();
open my $in, $tsbkupmappath or Die ("$tsbkupmappath $!");
@tsArray = <$in>;
close $in;
foreach my $x (sortArrayOrder @tsArray)
{
$_ = $x;
next if /^#/;
if (m/(\S+):::.*?=(\S+)/g)
{
push (@{$tsbkupmap{$1}}, $2);
}
}
my $tsbkcount = scalar keys %tsbkupmap;
if ($tsbkcount == 0)
{
Die ("No tablepsace entries found");
}
my @sortedkeys = sort keys %tsbkupmap;
my $res = '';
my $finalRes = '';
foreach $tsn (@sortedkeys)
{
my ($ts, $rdfno, $order) = split(/::/, $tsn);
my $fixedStr;
debug "rdfno " . $rdfno . "\n";
my @rdfnos = split /,/, $rdfno;
open (ROLLPLAN, "$tmp/xttnewdatafiles.txt") or Die $!;
my $scrape = 0;
# Check if any failure occured and stop exection
checkErrFile();
debug "BEFORE ROLLPLAN\n";
my @rmList = ();
my $dfnofinal = 0;
$res = '';
while (<ROLLPLAN>)
{
if ($_ !~ /::/)
{
chop;
my $dfstr = $_;
my ($dfno, $dfname) = split /,/, $dfstr;
my $ind;
## include the dfno only if present in the tsbkupmap.txt list
## of dfnos.
my $found = 0;
for ($ind = 0; $ind <= $#rdfnos ; $ind++)
{
if ($dfno == $rdfnos[$ind])
{
$found = 1;
if (($option == RESTORE) || ($option == RECOVER))
{
if ($res ne '')
{
$res = $res.",";
}
if ($option == RECOVER)
{
$res = $res."'$dfname'";
}
else
{
$res = $res."$dfno format '$dfname'";
}
}
}
}
}
}
if ($option == RESTORE)
{
$finalRes =
"restore from platform '$platName' FOREIGN DATAFILE ".
$res.
" from backupset '$stageondest/@{$tsbkupmap{$tsn}}';\n";
}
elsif ($option == RECOVER)
{
$finalRes =
" recover from platform '$platName' FOREIGN DATAFILECOPY ".
$res.
" from backupset '$stageondest/@{$tsbkupmap{$tsn}}';\n";
}
close(ROLLPLAN);
debug "AFTER ROLLPLAN\n";
$rollParallel = $props{'rollparallel'};
$fixedStr = $rdfno;
$fixedStr =~ tr/,/_/;
if ($rollParallel)
{
ChecktoProceed($rollParallel);
$pid = fork();
if ($pid == 0)
{
RestoreRecover ($fixedStr, $finalRes);
exit (0);
}
else
{
UpdateForkArray ($pid, $rollParallel);
}
}
else
{
RestoreRecover ($fixedStr, $finalRes);
}
}
while((my $pid = wait()) > 0)
{
#sleep (1);
}
# Check if any failure occured and stop exection
checkErrFile();
PrintMessage ("End of restore/recover phase");
}
sub createDumpFile
{
my $option = $_[0];
my $finalRes = '';
my $bkPiece = '';
my $platName = $context->{"platname"};
PrintMessage ("Start creating dumpfile");
# Check if any failure occured and stop exection
checkErrFile();
my $tsbkupmappath = "$tmp/tsbkupmap.txt";
my %tsbkupmap;
my @tsArray = ();
open my $in, $tsbkupmappath or Die ("$tsbkupmappath $!");
@tsArray = <$in>;
close $in;
foreach my $x (sortArrayOrder @tsArray)
{
$_ = $x;
next if /^#/;
if (m/.*DMPEXP::(.*)/)
{
$bkPiece = $1;
last;
}
}
my $dmpFile = "impdp".GetTimeStamp().".dmp";
my $dumpDir;
$context->{"dmpfile"} = $dmpFile;
if (defined($props{'dumpdir'}))
{
$dumpDir = $props{'dumpdir'};
if (!-e $dumpDir)
{
$dumpDir = $tmp;
}
}
else
{
$dumpDir = $tmp;
}
$finalRes =
" restore from platform '$platName' ".
" dump file '$dmpFile' ".
" datapump destination '$dumpDir' ".
" from backupset '$stageondest/$bkPiece';\n";
debug "$finalResn";
open RMANRE, ">rman_createdmp.cmd";
print RMANRE "$finalResn";
close RMANRE;
my ($rmanTrace, $traceFile) = GetRMANTrace("convert");
my $output = rman target \/ $rmanTrace cmdfile rman_createdmp.cmd
;
debug ($output);
if ($output =~ /ERROR MESSAGE/)
{
Die("$output $!");
}
Unlink ($traceFile);
# Check if any failure occured and stop exection
checkErrFile();
PrintMessage ("End of creating dumpfile");
}
Rollforward Ends here - on destination
xttplan.txt with new fromscn -- STARTS
sub newplan()
{
# Check if any failure occured and stop exection
checkErrFile();
## Move the current xttplan.txt.new as xttplan.txt
## The xttplan.txt.new gets generated during backincr()
my $xttplanpath = "$tmp/xttplan.txt";
my $xttplanpathnew = "$tmp/xttplan.txt.new";
if ( -e $xttplanpath )
{
my $timenow = time;
system("\\mv $xttplanpath $xttplanpath" . $timenow);
system("\\mv $xttplanpathnew $xttplanpath");
}
## Checks that no tablespace went read only
## or datafiles offline
&generate_batch_tsoutput(NEWPLAN);
print "New $tmp/xttplan.txt with FROM SCN's generatedn";
exit;
}
xttplan.txt with new fromscn -- ENDS
Convert Starts here - on destination
sub convert()
{
PrintMessage ("Performing convert");
# Check if any failure occured and stop exection
checkErrFile();
my ($rmanTrace, $traceFile) = GetRMANTrace("convert");
my $output = rman target \/ $rmanTrace cmdfile $rmanpath
;
if ($output =~ /ERROR MESSAGE/)
{
Die("$output $!");
}
Unlink ($traceFile);
my @lines = split /n/, $output;
my $xttnewdata = "$tmp/xttnewdatafiles.txt";
if ( -e $xttnewdata )
{
my $timenow = time;
system("\\mv $xttnewdata $xttnewdata" . $timenow);
}
open(XTTNEW, ">$xttnewdata") || Die("Cant open xttnewdatafiles.txt");
foreach my $line (@lines)
{
my $tsname;
my $filno;
$_ = $line;
if (/converted datafile=(.+)\/(\w+)[_](\d+)\.xtf/)
{
print XTTNEW "$3,";
$line =~ s/.*converted datafile=//;
print XTTNEW "$line\n";
}
elsif(/^ts(\S+)/)
{
print XTTNEW $1 . "\n";
$tsname = $1;
}
}
close(XTTNEW);
PrintMessage ("Converted datafiles listed in: $xttnewdata");
exit;
}
Function : genConvertDFNames
Purpose : When script is used to transport tablespaces between same endian
this function will be called. Here we create xttnewdatafiles.txt
sub genConvertDFNames
{
my @convertArray = ();
# Check if any failure occured and stop exection
checkErrFile();
open(RMANCONVERT, "$rmanpath") ||
die 'Cant find rmanconvert.cmd, TMPDIR undefined';
@convertArray = ;
close RMANCONVERT;
open(RMANDSTDF, ">$rmandstdf") ||
Die "Cant find $rmandstdf";
foreach my $x (@convertArray)
{
chomp($x);
$x = trim($x);
if ($x =~ /'(.*)\.tf'/)
{
$x = $1.".tf";
if ($x =~ /.*(.+)\/(\w+)[_](\d+)\.tf/)
{
print RMANDSTDF "::$2\n";
print RMANDSTDF "$3,$x\n";
}
}
}
close RMANDSTDF;
exit;
}
Convert Ends here - on destination
Generate plugin/impdp command template as xttplugin.txt
- Starts here, on destination
sub plugin()
{
my $option = $_[0];
PrintMessage ("Generating plugin");
# Check if any failure occured and stop exection
checkErrFile();
my $xttplugin = "$tmp/xttplugin.txt";
my $tts = "transport_tablespaces=";
my $tdf = "transport_datafiles=";
my $comma = 0;
my $xttnewdata = "$tmp/xttnewdatafiles.txt";
my $command_str;
my $dmpFile = $context->{"dmpfile"};
if ( -e $xttplugin )
{
my $timenow = time;
system("\\mv $xttplugin $xttplugin" . $timenow);
}
open(XTTPLUG, ">$xttplugin") || Die("Unable to open file $xttplugin");
if ($option == LINK)
{
$command_str =
"impdp directory=<DATA_PUMP_DIR> logfile=<tts_imp.log> \\" . "\n" .
"network_link=<ttslink> transport_full_check=no \\" . "\n" ;
}
else
{
$command_str =
"impdp directory=<DATA_PUMP_DIR> logfile=<tts_imp.log> \\" . "\n" .
"dumpfile=$dmpFile \\" . "\n" ;
}
print XTTPLUG $command_str;
if ($option == LINK)
{
print XTTPLUG $tts;
open(XTTPLAN, $xttpath) || Die("Cant find xttplan.txt\n $!");
while (<XTTPLAN>)
{
if ($comma == 1 && /::::/)
{
print XTTPLUG ",";
$comma = 0;
}
if (/(\S+)::::(\S+)/)
{
print XTTPLUG $1;
$comma = 1;
}
}
close(XTTPLAN);
print XTTPLUG " \\\n";
}
print XTTPLUG $tdf;
$comma = 0;
open(XTTNEWDATA, $xttnewdata) || Die("Cant find xttnewdatafiles.txt\n $!");
while ()
{
if ($comma == 1)
{
print XTTPLUG ",";
$comma = 0;
}
if (! /^::\S+/)
{
chop;
my ($dfno, $dfname) = split /,/, $_;
print XTTPLUG "'" . $dfname . "'";
$comma = 1;
}
}
print XTTPLUG "n";
close(XTTNEWDATA);
close(XTTPLUG);
PrintMessage ("Done generating plugin file $xttplugin");
exit;
}
Generate plugin/impdp command - Ends here, on destination
Function : usage
Purpose : Message about this program and how to use it
sub usage
{
print STDERR << "EOF";
This program prepares, backsup and rollsforward tablespaces
for cross-platform transportable tablespaces.
usage: $0
{[--backup|-b] || [--bkpincr|-B] || [--bkpexport/E]
[--resincrdmp|M]
[--convert/-c] || [--generate|-e] || [--incremental|-i] ||
[[--prepare|-p] || [--getfile|-G]] ||
[--restore|R] || [--recover|X]
[--rollforward|-r [--rolltbs|-T <TBS1[,TBS2]>] ||
[--determinescn|-s] ||
[--orasid/O] || [--orahome|-o]]
[--help|-h]}
Additional options
------------------
[--debug|d] [--clearerrorfile|-C] [--xttdir|Dir <tmpdir>]
[-F/--propfile] [-I/--propdir]
-b : For 12c and above, generate transportable backups
-B : For 12c and above, generate level 1 transportable backups
-c : conversion of datafiles
-M : create the dump file from the generated backup
-e : generate impdp script: export over new link
-i : incremental backup
-p : prepare
-G : get datafiles from source database using get_file, should not
be used together with -p
-r : roll forward datafiles
-s : new from_scn values into xttplan.txt
-R : For 12c restore the datafiles from the backups
-X : For 12c recover the datafiles from the backups
-T : roll forward specific tablespace(s)
-h : this (help) message (Default)
-d : provides more debug information, also rman is called with debug
option so that tracing is better.
-L : delete the ERROR FILE and proceed with the execution
-D : Instead of defining environement variable, user can pass tmpdir
through xttdir
-O : Use this option to pass ORACLE_SID to override the environment
variable
-o : Use this option to pass ORACLE_HOME to override the environment
variable
-I : Use this option to mention the location from where the script
will pick the properties file etc
-F : Use this option to mention the location from where the script
will pick the properties file.
example: $0 -p
$0 -i
$0 -r
$0 -s
EOF
exit;
}
Function : GetTimeStamp
Purpose : Get the time stamp
Inputs : None
Outputs : Timestamp
NOTES : None
sub GetTimeStamp
{
my $timeStamp = $$."_".int(rand(1000));
return ($timeStamp);
}
Call main function
Main();