#!/usr/bin/perl

# ------------------------------------------------------------------------
# hotarch -- hotarchive backup for Microsoft
#            copied from ye olde shell script
#            G. Patterson, Nov 2001

# Set the common parameters (contained in common.pl)
use File::Basename;
$MyPath = dirname($0);
require "$MyPath\\common.pl";

# ------------------------------------------------------------------------

sub LogMsg{
	my @t = localtime(time);
	printf LOG "%04d-%02d-%02d %02d:%02d %s\n",
		$t[5]+1900,$t[4]+1,$t[3],$t[2],$t[1],$_[0];
}

# ------------------------------------------------------------------------

sub stop_backup{
	# my $sqltmp = "$tmpdir\\stop_backup.sql";
	open SQL,">$sqltmp";
	print SQL "set verify off\nset trimspool on\nset pages 0
	set feedback off
	select distinct 'alter tablespace ' || TABLESPACE_NAME || ' end backup;'
	from dba_data_files where FILE_ID in (
		select file# from v_\$backup where status = 'ACTIVE'
		);\nexit\n";
	my $t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	open SQL,">$sqltmp";
	print SQL "$t\nexit\n";
	$t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
}

# ------------------------------------------------------------------------

sub problem{
# Err Houston? ... Houston ... We have a problem ...
	LogMsg "ERROR: $_[0]";
	# make a last ditch effort to end backup
	stop_backup();
	unlink "$lockfile";
	unlink "$stopfile";
	die "$_[0]\n";
}

# ------------------------------------------------------------------------

sub get_redo_info{
# get list of redo logs from v_$logfile
	# my $sqltmp = "$tmpdir\\get_redo_info.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "set verify off\nset trimspool on\nset pages 0\nset feedback off
	select member from v_\$logfile;\nexit\n";
	my @t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	foreach $x (@t){
		$x =~ s/\s+$//;
		push (@REDOLIST,$x) if (length($x) )
	}
}

# ------------------------------------------------------------------------

sub get_log_info{
# issue "archive log list" command from svrmgrl -- use sys as sysdba
	# my $sqltmp = "$tmpdir\\get_log_info.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "connect $userid/$passwd\@$orasid as sysdba
	archive log list\nexit\n";
	my @t = `svrmgrl \@$sqltmp`;
	# this is why we use sys as sysdba -- internal connects even if shutdown
	die grep (/ORA-/,@t) if ( grep /ORA-/,@t);
	for my $i ( 0 .. $#t){
		if ( $t[$i] =~ /^Connected\./){
			for ( $i++;$i<=$#t-1;$i++){
				chomp( $t[$i]);
				if ( $t[$i] =~ /\s\s+/){
					$log_list{$`} = $';
				}
			}
			return;
		}
	}
	problem "cannot retrieve archive log details";
}

# ------------------------------------------------------------------------

sub get_oraparm{
# retrieve parameter values from v$parameter
	# my $sqltmp = "$tmpdir\\get_oraparm.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "set echo off\nset lines 600\nset trimspool on\nset pages 0\nset feedback off\ncol name format a40 trunc\ncol value format a512 trunc
	select name,value from v\$parameter;\nexit\n";
	my @t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	for my $i ( 0 .. $#t){
		$t[$i] =~ s/\s+$//;
		if ( $t[$i] =~ /^(\w+)/){
			$ORAPARM{$1} = $';
			$ORAPARM{$1} =~ s/^\s+//;
		}
	}
}

# ------------------------------------------------------------------------

sub backup_mode{
# alter tablespace begin/end backup
	# my $sqltmp = "$tmpdir\\backup_mode.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "alter tablespace $_[0] $_[1] backup;\nexit\n";
	my $t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	$t =~ s/\s+$//;
	$t =~ s/\.$//;
	problem "cannot alter tablespace: $_[0]" unless
		($t =~ /Tablespace altered$/i);
}

# ------------------------------------------------------------------------

sub get_datafiles{
# Get the list of datafiles to be backed up
	# my $sqltmp = "$tmpdir\\get_datafiles.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "set pages 0\nset trimspool on\nset feedback off\nset lines 1024
	select tablespace_name, file_name, blocks+1
	from dba_data_files where status = 'AVAILABLE'
	order by tablespace_name, file_name;\nexit\n";
	my @t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	my $j = 0;
	for my $i ( 0 .. $#t){
		chomp($t[$i]);
		my @s = split ' ',$t[$i];
		if (@s == 3){
			$DATAFILE[$j] = \@s;
			my @b = split /\\/,$DATAFILE[$j][1];
			$DATAFILE[$j++][3] = $b[$#b];
		}
		elsif (@s > 3){
			# spit the dummy if filename contains embedded spaces
			problem "bad file: $s[1] $s[2]";
		}
	}
	problem "no data files available" unless (@DATAFILE > 0);
	LogMsg sprintf "$MyName found %d data files to backup",@DATAFILE+0;
}

# ------------------------------------------------------------------------

sub write_header{
	get_redo_info();
	get_datafiles();
	@CTRLFILES=split /, /,$ORAPARM{control_files};
	open (HDR,">$arcdir\\0header") || die "Cannot open $arcdir\\0header\n";
	print HDR "$MyName started at $start_str
 ORACLE_SID: $orasid
ORACLE_HOME: $ENV{ORACLE_HOME}
 BLOCK_SIZE: $ORAPARM{db_block_size}
START_SEQNO: " . $log_list{"Current log sequence"}; print HDR "\n
 CTRL FILES:\n";
	foreach $x (@CTRLFILES){
		print HDR "\t$x\n";
	}
}

# ------------------------------------------------------------------------

sub backup_control_file{
# create a backup copy of the control file
	my $backup_ctrl = "$arcdir\\" . uc($orasid) . "_CTRL.FILE";
	unlink "$backup_ctrl";
	# my $sqltmp = "$tmpdir\\backup_control_file.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "alter database backup controlfile to '$backup_ctrl';\nexit\n";
	my $t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	$t =~ s/\s+$//;
	$t =~ s/\.$//;
	problem "Cannot backup controlfile" unless ($t =~ /Database altered$/i);
}


# ------------------------------------------------------------------------

sub switch_log{
# switch_log files ... then wait for the archiver to catch up
	# my $sqltmp = "$tmpdir\\switch_log.sql";
	open SQL,">$sqltmp" || die "cannot open $sqltmp\n";
	print SQL "alter system switch logfile;\nexit\n";
	my $t = `sqlplus -s $userid/$passwd\@$orasid \@$sqltmp`;
	$t =~ s/\s+$//;
	$t =~ s/\.$//;
	problem "Cannot switch log file" unless ($t =~ /System altered$/i);
	for ( get_log_info(),$c = $log_list{"Current log sequence"};
		 $log_list{"Next log sequence to archive"} < $c;
		 get_log_info()){
		 sleep 20;
	}
}

# ------------------------------------------------------------------------

sub copy_log_files{
	my $i;
	my $next_log = $log_list{"Next log sequence to archive"};
	my $arczip = "$arcdir\\archivelog";
	unlink "$arczip";
	print HDR "\n  LOG FILES:\n";
	for ($i = $start_log_seq; $i < $next_log; $i++){
		@copy_log = glob sprintf $arch_fmt_str,$i;
		problem "Too many files: @copy_log\n" unless (@copy_log == 1);
		problem "No file: $copy_log[0]" unless ( -s $copy_log[0]);
		problem "$MyName Stopped!" if ( -f $stopfile);
		print HDR "\t$copy_log[0]\n";
		$t = `$zipadd $arczip $copy_log[0]`;
	}
}

# ------------------------------------------------------------------------

sub verify_backup{
	$blksz = $ORAPARM{db_block_size};
	for my $i ( 0 .. $#DATAFILE){
		my @t = `$zipview $arcdir\\$DATAFILE[$i][3].zip`;
		foreach my $x (@t){
			$x =~ s/\s+$//;
			if ($x =~ /$DATAFILE[$i][3]$/){
				$size = (split ' ',$x)[0];
				problem "suspect backup $DATAFILE[$i][3].zip"
				unless ($size == $DATAFILE[$i][2] * $blksz);
			}
		}
	}
}

# ------------------------------------------------------------------------

# Main ... start here:
$MyName = basename $0,"\.pl";
die "usage: $MyName sys_passwd db_name\n" unless (@ARGV == 2);
$userid = "sys";
$passwd = $ARGV[0];
$orasid = $ARGV[1];
@start = localtime(time);
$start_str = sprintf "%04d-%02d-%02d %02d:%02d:%02d",$start[5]+1900,
		$start[4]+1,$start[3],$start[2],$start[1],$start[0];
$logfile = sprintf "%s\\%s_%s_%02d%02d%02d.log",$logdir,$orasid,$MyName,
		$start[5]%100,$start[4]+1,$start[3];
$lockfile = "$logdir\\hotarch.$orasid.lock";
$stopfile = "$logdir\\hotarch.$orasid.stop";
die "$MyName is already running\n" if ((-f $lockfile) && (-M $lockfile < 1);
open (LOCK,">$lockfile") || die "Cannot write to $lockfile";
print LOCK "$MyName started $start_str\n";
close (LOCK) || die "$!";
open(LOG,">>$logfile") || die "error opening $logfile\n";
get_log_info();
get_oraparm();
problem "$ORAPARM{db_name} does not match $orasid"
	unless (uc($ORAPARM{db_name}) eq uc($orasid));
$arcdir = "$arcdir\\$ORAPARM{db_name}";
problem "No Archive Mode"
	unless ($log_list{"Database log mode"} eq "Archive Mode");
problem "Archive disabled"
	unless ($log_list{"Automatic archival"} eq "Enabled");
problem "No archive log destination"
	unless ($ORAPARM{log_archive_dest});
problem "No init.ora file"
	unless (-s $ORAPARM{ifile});
problem "Cannot fathom format: $ORAPARM{log_archive_format}"
	unless ($ORAPARM{log_archive_format} =~ /^\%\%ORACLE_SID\%\%/ &&
	        $ORAPARM{log_archive_format} =~ /\%S\.(\w+)$/i);
$arch_fmt_str = "$ORAPARM{log_archive_dest}\\" . uc($orasid) . '*' . '%05d.' . $1;
$start_log_seq = $log_list{"Current log sequence"};
stop_backup(); # just in case previous hotarch died an untidy death
write_header();
print HDR "\n DATA FILES:\n";
for my $i ( 0 .. $#DATAFILE){
	problem "$MyName Stopped!" if ( -f $stopfile);
	LogMsg "copying $DATAFILE[$i][1], $DATAFILE[$i][2] blocks";
	print HDR "\t$DATAFILE[$i][1] $DATAFILE[$i][2] blocks\n";
	backup_mode "$DATAFILE[$i][0]","BEGIN";
	$t = `ocopy $DATAFILE[$i][1] $tmpdir`;
	backup_mode "$DATAFILE[$i][0]","END";
	problem "$MyName Stopped!" if ( -f $stopfile);
	$t = `$zipmv $arcdir\\$DATAFILE[$i][3].zip $tmpdir\\$DATAFILE[$i][3]`;
}
backup_control_file();
$t = `copy $ORAPARM{ifile} $arcdir`;
$t =~ s/\s+$//;
problem $t unless ($t =~ /1 file\(s\) copied/);
switch_log();
copy_log_files();
verify_backup();
LogMsg "$MyName completed normal";
unlink "$lockfile";
