#!/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";