#!/usr/bin/perl

# ------------------------------------------------------------------------
# coldarch -- cold backup for Microsoft
#             G. Patterson, June 2002

# WARNING  ** This script SHUTS DOWN THE DATABASE! ********
#             This is the only warning!

# Set the common parameters (contained in common.pl)
use File::Basename;
# extract the path from $0
$MyPath = dirname($0);
# This file contains important site definitions
require "$MyPath\\common.pl";
# This determines how the database will be re-opened
$OPEN_TYPE = 0;		# 0 = Don't open, 1 = oradim, 2 = svrmgrl, 3 = both

# ------------------------------------------------------------------------

# Write a log message to the logfile
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];
}

# ------------------------------------------------------------------------

# We have a problem ... cleanup and exit
sub problem{
	LogMsg "ERROR: $_[0]";
	unlink "$lockfile";
	unlink "$stopfile";
	# hopefully we can start the database
	opendb();
	die "$_[0]\n";
}

# ------------------------------------------------------------------------

# Run the given SQL and spool to temp.LST, because we
# cannot redirect output from version 7 sqlplus in NT
sub run_sql {
	# first,  write the given SQL to a temp file
	open SQL,">$sqltmp" || die "cannot open $sqltmp";
	print SQL "set term off\n";
	print SQL "set echo off\n";
	print SQL "set trimspool on\n";
	print SQL "set pages 0\n";
	print SQL "set feedback off\n";
	print SQL "set verify off\n";
	print SQL "set lines 1024\n";
	print SQL "spool $tmpdir\\temp\n";
	print SQL "$_[0]\n";
	print SQL "exit\n";
	close SQL || die "$!";
	# now run it with the SQLPLUS utility
	system "$sqlplus -s $userid/$passwd\@$orasid \@$sqltmp";
	# read back the spool file and return the contents in list context
	open (TMP,"$tmpdir\\temp.LST") || problem "opening temp.LST";
	my @t = (<TMP>);
	close TMP || problem "closing temp.LST";
	return( @t);
}

# ------------------------------------------------------------------------

# Run the given SQL in svrmgrl
sub run_svrmgr {
	# construct an SQL file
	open SQL,">$sqltmp" || die "cannot open $sqltmp";
	print SQL "connect internal/$passwd\n";
	print SQL "$_[0]\n";
	print SQL "exit\n";
	close SQL || die "$!";
	# EXEC as a backticks block and return the output in list context
	my @t = `$svrmgrl \@$sqltmp`;
	return( @t);
}

# ------------------------------------------------------------------------

# get list of redo logs from v_$logfile
sub get_redo_info{
	# this should return a list of the redo logs
	my @t = run_sql "select member from v_\$logfile;";
	foreach $x (@t){
		# remove any crud from the end of the lines
		$x =~ s/\s+$//;
		push (@REDOLIST,$x) if (length($x) )
	}
}

# ------------------------------------------------------------------------

# shutdown the database and get a copy of SQL to create control file
sub shutdb{
	my $t; my $f; my $x; my $a;
	# run a BACKUP TO TRACE command
	my $backup_ctrl = "$coldarc\\" . uc($orasid) . "_CTRL.FILE.trc";
	my @t = run_svrmgr "alter system switch logfile;
		alter database backup controlfile to trace resetlogs;";
	@t = run_svrmgr "shutdown immediate;";
	LogMsg $t = `$oradim -shutdown -sid $orasid -usrpwd $passwd -shuttype inst,srvc -shutmode i`;
	# examine the files in USER_DUMP_DEST
	opendir (TRACE,$ORAPARM{user_dump_dest}) ||
		problem "Cannot open $ORAPARM{user_dump_dest}";
	$a = 99;
	# we want the most recent trace file ...
	while ($t = readdir TRACE){
		$x = $ORAPARM{user_dump_dest} . "\\" . $t;	
		next unless ( -f $x);
		next unless ( (-M $x ) < $a);
		$f = $x;
		$a = ( -M $x )
	}
	# save the trace file for later (the coldclone procedure)
	rename $f,$backup_ctrl || problem "Cannot move $f to $backup_ctrl";
}

# ------------------------------------------------------------------------

# retrieve parameter values from v$parameter
sub get_oraparm{
	# extract the parameters from v$parameter
	my @t = run_sql "col name format a40 trunc\ncol value format a512 trunc
		select name,value from v\$parameter;";
	for my $i ( 0 .. $#t){
		$t[$i] =~ s/\s+$//;
		# parse them into %ORAPARM (using parameter as a hash key)
		if ( $t[$i] =~ /^(\w+)/){
			$ORAPARM{$1} = $';
			$ORAPARM{$1} =~ s/^\s+//;
		}
	}
}

# ------------------------------------------------------------------------

# Get the list of datafiles to be backed up
sub get_datafiles{
	my @t = run_sql "select tablespace_name, file_name, blocks+1
		from dba_data_files where status = 'AVAILABLE'
		order by tablespace_name, file_name;";
	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;
}

# ------------------------------------------------------------------------

# Create a header file.
# This is a text file that contains information about the cold archive
# which can be used by scripts that restore from the cold archive.
sub write_header{
	my $x;
	# gather all the necessary information
	get_redo_info();
	get_datafiles();
	@CTRLFILES=split /, /,$ORAPARM{control_files};
	# clobber previous backup
	unlink glob "$coldarc\\*";
	open (HDR,">$coldarc\\0header") || die "Cannot open $coldarc\\0header\n";
	# store the information in the header file
	print HDR "$MyName started at $start_str
 ORACLE_SID: $orasid
ORACLE_HOME: $ENV{ORACLE_HOME}
 BLOCK_SIZE: $ORAPARM{db_block_size}
 PARAM_FILE: $pfile{$orasid}\n";
	if ($ORAPARM{ifile}){
		print HDR "    I FILES:\n";
		foreach $x(split (/, /,$ORAPARM{ifile})){
			print HDR "\t$x\n";
		}
	}
	print HDR " CTRL FILES:\n";
	foreach $x (@CTRLFILES){
		print HDR "\t$x\n";
	}
}

# ------------------------------------------------------------------------

# create SQL for a backup control (backup to trace)
sub backup_control_file{
	my $backup_ctrl = "$coldarc\\" . uc($orasid) . "_CTRL.FILE";
	# clobber any old versions of this file
	unlink "$backup_ctrl";
	my @t = run_sql "set feedback on
		alter database backup controlfile to '$backup_ctrl';";
	problem "Cannot backup controlfile"
		unless ( grep(/Database altered$/i,@t) );
}


# ------------------------------------------------------------------------

# use the $zipview command to verify that the backup is successful
# this reads through the contents of the zip archive and checks
# that the sizes match specified system size.
# NB: This may vary from site to site. If you have actually created
# a data file that is larger than the allocated size in the database
# this check should be disabled.
sub verify_backup{
	$blksz = $ORAPARM{db_block_size};
	for my $i ( 0 .. $#DATAFILE){
		my @t = `$zipview $coldarc\\$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);
			}
		}
	}
}

# ------------------------------------------------------------------------

#make the output from the zipadd more pretty
sub pretty {
	my $t = $_[0];
	$t =~ s/Deflating     .*%\), done./Deflated/;
	return($t);
}

# ------------------------------------------------------------------------

# backup the control_file, redo logs, pfiles and ifiles
sub backup_config(){
	my $x; my $b;
	my $dest = "$coldarc\\config.$orasid.zip";
	# first copy the pfile to the archive directory
	my $t = `copy $pfile{$orasid} $coldarc`;
	$t =~ s/\s+$//;
	problem "pfile: $t" unless ($t =~ /1 file\(s\) copied/);
	# add the control_file to the zip archive
	LogMsg pretty $t = `$zipadd $dest $CTRLFILES[0]`;
	foreach my $x (@REDOLIST){
		LogMsg pretty $t = `$zipadd $dest $x`;	
	}
	# copy each of the ifiles to the archive directory
	foreach $x(split (/, /,$ORAPARM{ifile})){
		$b = basename( $x);
		problem "Duplicate ifile: $b" if ( -f "$coldarc\\$b");
		$t = `copy $x $coldarc`;
		$t =~ s/\s+$//;
		problem "ifile: $t" unless ($t =~ /1 file\(s\) copied/);
	}
}

# ------------------------------------------------------------------------

# reopen database -- called from problem(), so we must not call problem()
sub opendb{
	LogMsg "WARNING: Database is shutdown!" unless $OPEN_TYPE;
	LogMsg $t =
	`$oradim -startup -sid $orasid -usrpwd $passwd -starttype srvc,inst`
		if ( $OPEN_TYPE & 1);
	run_svrmgr "startup" if ( $OPEN_TYPE & 2);
}

# ------------------------------------------------------------------------

# Main ... start here:
# which program am I?
$MyName = basename $0,"\.pl";
# sanity check for command line
die "usage: $MyName intrnl_passwd db_name\n" unless (@ARGV == 2);
$userid = "sys";
$passwd = $ARGV[0];
$orasid = $ARGV[1];
# check the environment
chk_env();
# init strings for logfile
@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\\coldarch.$orasid.lock";
$stopfile = "$logdir\\coldarch.$orasid.stop";
die "$MyName is already running\n" if ((-f $lockfile) && (-M $lockfile < 1));
# create the lockfile
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_oraparm();
problem "$ORAPARM{db_name} does not match $orasid"
	unless (uc($ORAPARM{db_name}) eq uc($orasid));
$coldarc = "$coldarc\\$ORAPARM{db_name}";
# setup the header
write_header();
# shutdown the database, backup control_file, pfile, ifiles
shutdb();
# write information about datafiles to the HEADER
print HDR "\n DATA FILES:\n";
for my $i ( 0 .. $#DATAFILE){
	# loop through the datafiles
	problem "$MyName Stopped!" if ( -f $stopfile);
	sleep(10);
	LogMsg "copying $DATAFILE[$i][1], $DATAFILE[$i][2] blocks";
	print HDR "\t$DATAFILE[$i][1] $DATAFILE[$i][2] blocks\n";
	LogMsg pretty $t = `$zipadd $coldarc\\$DATAFILE[$i][3].zip $DATAFILE[$i][1]`;
}
backup_config();
opendb();
LogMsg "$MyName completed normal";
unlink "$lockfile";
