#!/usr/bin/perl -w


# Copyright (c) 2001-2006, Kungliga Tekniska Högskolan
# (Royal Institute of Technology, Stockholm Sweden)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the university nor the names of its contributors
#    may be used to endorse or promote products derived from this software
#    without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


my $dsmpath = "/opt/tivoli/tsm/client/ba/bin";
my $vos = "/usr/sbin/vos";
my $dsmc = "$dsmpath/dsmc";
my $scratch = "/var/tsm/afsbackup";

#$ENV{DSM_CONFIG} = "$dsmpath/dsm-afsbackup.opt";

my $debug = 0;

use Date::Manip;
use strict;

sub print_buplist {
    my $listp = shift;
    my $vol = shift;
    my $i = 0;
    while (exists $listp->[$i]) {
	print LOGFILE "$vol, $listp->[$i]->[0], $listp->[$i]->[1] \n";
	$i++
    }
}

sub sortbup {
    my $date1 = ParseDate($a->[1]);
    my $date2 = ParseDate($b->[1]);
    return Date_Cmp($date2, $date1);
}

sub sortdate {
    my $date1;
    my $date2;
    $date1 = ParseDate($a);
    $date2 = ParseDate($b);
    return (Date_Cmp($date1, $date2));
}

sub last_incrbackup {
    my $incrbupsptr = shift;
    my $vol = shift;
    my $datelastefullbackup = shift;
    my @dates;
    my $i = 0;
    while (exists $incrbupsptr->{$vol}->[$i]) {
	if ($incrbupsptr->{$vol}->[$i]->[0] eq $datelastefullbackup) {
	    push @dates, $incrbupsptr->{$vol}->[$i]->[1];
	    #last ?
	}
	$i++;
    }
    return shift @dates;
}

sub last_changed {
    my $vol = shift;
    my $datum;
    open VOSEXA, "$vos exa -noauth $vol|";
    while (<VOSEXA>) {
	chomp;
	if (/Last Update (.+)$/) {
	    $datum = $1;
	    $datum = UnixDate($datum, "%Y%m%d");
	    last;
	}
    }
    close VOSEXA;
    return $datum;
}


sub last_fullbackup {
    my $fullbupsptr = shift;
    my $vol = shift;
    return $fullbupsptr->{$vol}->[0]->[1];
}

sub makefullbackup {
    my $vol = shift;
    my $date = UnixDate("now","%Q");

    my @vosexa = `$vos exa -noauth $vol.backup`;
    if($#vosexa < 0) {
	print LOGFILE "FAILED: Error for $vol\n";
    }
    my @size = split($vosexa[0]);
    my $siz = $size[3];
    print LOGFILE "Making a full backup of $vol\n";

    if ($debug) {
	return;
    }
    doarchive($vol,"00000000", $date, $siz);
    return;
}

 
sub makeincrementalbackup {
    my $vol = shift;
    my $lastfull = shift;
    if ($debug) {
	print LOGFILE "Making an incremental backup of $vol\n";
	return;
    }
    my $date1 = UnixDate("now", "%Q"); # lie 19950428
    my @vosexa = `$vos exa -noauth $vol.backup`;
    doarchive($vol, $lastfull, $date1);
}

sub doarchive {
    my $vol = shift;	
    my $fulldate = shift;
    my $incrdate = shift;
    my $ret;

    my $full = $fulldate eq "00000000" ? 1 : 0;
    my $message = $fulldate eq "00000000" ? "Full backup" : "Incremental backup";
    my $fname = "$scratch/$vol.backup.$fulldate.$incrdate";
    my $dumpcmd;
    my $archcmd = "$dsmc archive $fname";
    if ($full) {
	$dumpcmd = "$vos dump $vol.backup -localauth -file $fname";
    } else {
	my $vosdate = UnixDate($fulldate,"%D"); # like 04/28/95
	$dumpcmd = "$vos dump $vol.backup -localauth -file $fname -time $vosdate";
    }

    print LOGFILE "Trying to dump $vol.backup.$fulldate.$incrdate \n";
    $ret = system($dumpcmd);
    if($ret != 0) { 
	print LOGFILE "FAILED: Dump of $vol.backup.$fulldate.$incrdate failed with errorcode $ret\n";
	goto out;
    }

    print LOGFILE "Trying to archive $vol.backup.$fulldate.$incrdate \n";
    $ret = system($archcmd);
    if($ret != 0) { 
	print LOGFILE "FAILED: Arch of $vol.backup.$fulldate.$incrdate failed with errorcode $ret\n";
	goto out;
    }

  out:
    print LOGFILE "Trying to remove $vol.backup.$fulldate.$incrdate \n";
    my $rmcmd = "rm $fname";
    $ret = system($rmcmd);
    if($ret != 0) {
	print LOGFILE "FAILED: Removal of $fname failed\n";
    }
    
    return;
}

sub create_volumelist {
    my $volume;
    my %volumes;

    my %skipvols;

    open SKIPVOLS, "/opt/tivoli/volumes-to-skip.lst" or die "Can't open skipvols";
    while (<SKIPVOLS>) {
	chomp;
	/^([^# ][^ ]*)/;
	$skipvols{$1} = 1;
    }
    close SKIPVOLS;

    if ($debug) {
	open VLDB, "/tmp/vldb.txt";
    }
    else {
	open(VLDB , "$vos listvldb -noauth|") or die "Can't run vos listvldb";
    }
    while (<VLDB>) {
	chomp;
	if (/^Total entries/ || /^VLDB entries for all servers/) {
	    next;
	}

	if (/(^[a-zA-Z0-9\._\-]+)/) {
	    $volume=$1;

	    if($volume =~ /.*\.nobup$/ || $volume =~ /.*\.nobackup/) {
		print LOGFILE "Skipping volume $volume because of extension\n";
		next;
	    }
	    if(defined $skipvols{$volume}) {
		print LOGFILE "Skipping volume $volume because it's on the skiplist\n";
		next;
	    }

	    $volumes{$volume} = "$volume";
	}
    }
    close VLDB;

    return \%volumes;
}

sub do_backupsys {
    if ($debug) {
	print LOGFILE "Doing a backupsys\n";
	return;
    } else {
	my $cmd = "$vos backupsys -local -verbose";
	my $res = system($cmd);
	if($res != 0) {
	    print LOGFILE "FAILED: vos backupsys ret"; 
	}
	print LOGFILE "vos backupsys returned $res\n";
    }
}

sub read_and_sort_archive {
    my %fullbups;
    my %incrbups;
    my %fullsizes;
    my %incrsizes;

    print LOGFILE "Opening archive\n";
    if ($debug) {
	open ARCHIVE, "/tmp/bupset";
    }
    else {
	open(ARCHIVE, "$dsmc query archive \"$scratch/*\"| ") or die "Can't run dsmc query";
    }
    while (<ARCHIVE>) {
	if (m;^\s*([\d,]+)\s+.*$scratch/(.+)\.backup\.(\d{8})\.(\d{8});) {
	    my $size = $1;
	    my $vol = $2;
	    my @dates = ($3, $4);
	    $size =~ s/,//g;
	    if ($dates[0] eq "00000000") {
		push @{$fullbups{$vol}}, \@dates;
		if(!defined $fullsizes{$vol}) {
		    $fullsizes{$vol} = {};
		}
		$fullsizes{$vol}{$dates[1]} = $size;
	    } else {
		push @{$incrbups{$vol}}, \@dates;
		if(!defined $incrsizes{$vol}) {
		    $incrsizes{$vol} = {};
		}
		$incrsizes{$vol}{$dates[0]} += $size;
	    }
	}
    }
    close ARCHIVE;

    print LOGFILE "Finished reading archive\n";
    print LOGFILE "Sorting fullbups\n";
    for my $vol (keys %fullbups) {
	@{$fullbups{$vol}} = sort sortbup @{$fullbups{$vol}};
    }
    print LOGFILE "Sorting incrbups\n";
    for my $vol (keys %incrbups) {
	@{$incrbups{$vol}} = sort sortbup @{$incrbups{$vol}};
    }
    return (\%fullbups, \%incrbups, \%fullsizes, \%incrsizes);
}

				       
    
#main
#exit $debug if $debug;

Date_Init();

my $nowdate = UnixDate("now", "%Q");


my $logname;
if($debug) {
    $logname = "/var/log/afs-tsm/backuplog.debug.$nowdate";
} else {
    $logname = "/var/log/afs-tsm/backuplog.$nowdate";
}

open LOGFILE, ">", $logname or die "Can't open logfile";

open STDOUT, ">&LOGFILE" or die "Can't dup LOGFILE: $!";
open STDERR, ">&LOGFILE" or die "Can't dup LOGFILE: $!";


LOGFILE->autoflush(1);

print LOGFILE scalar localtime(time)," Starting $0\n";
my $volumesp = create_volumelist();
print LOGFILE "Created volumelist\n";
my ($fullbupsptr, $incrbupsptr, $fullsizes, $incrsizes) = read_and_sort_archive();
print LOGFILE "Read archive\n";
do_backupsys();
foreach my $volume (keys %{$volumesp}) {
    print LOGFILE "\n";

    if ($debug) {
	print LOGFILE "FULL BACKUPS:\n";
	print_buplist( \@{$ {$fullbupsptr}{$volume}}, $volume);
	print LOGFILE "INCREMENTAL BACKUPS\n";
	print_buplist( \@{$ {$incrbupsptr}{$volume}}, $volume);
    }
#   $volume is a candidate for backup find out when it was last changed
    my $datelastchanged = last_changed($volume);
    if ($debug) {
	print LOGFILE "Last change $volume $datelastchanged\n";
    }
#   Now find out when was the last fullbackup of the volume 
    my $datelastfullbackup = last_fullbackup($fullbupsptr, $volume);
    if (not $datelastfullbackup) {
	makefullbackup($volume);
	next;
    }
#   datelastfullbackup is defined if we get here!
    if ($debug) {
	print LOGFILE "Last full $volume: $datelastfullbackup\n";
    }
#   and when was the last incremental backup done.
    my $datelastincrbackup = last_incrbackup($incrbupsptr, $volume, $datelastfullbackup);
    if (not defined $datelastincrbackup) {
	$datelastincrbackup = $datelastfullbackup;
    }
    my $dalach = ParseDate($datelastchanged);
    my $dalafu = ParseDate($datelastfullbackup);
    my $dalain = ParseDate($datelastincrbackup);
    my $now = ParseDate("now");

    my $diff = DateCalc($dalafu, $now); # now - dalafu
    my $diffindays = Delta_Format($diff, 1, "%dt");

    if ((Date_Cmp($dalach, $dalafu) < 0)) {
	print LOGFILE "No backup of volume $volume needed. No change since last full.\n";
	next;
    }

# If there was more than 60 days since last full, take a new full
    if ($diffindays >= 60) {
	makefullbackup($volume);
	next;
    }

    if (Date_Cmp($dalach, $dalain) < 0) {
	print LOGFILE "No backup of volume $volume needed. No change since last incremental.\n";
	next;
    }

    if (int(rand(59) + 1) == 17) {
	makefullbackup($volume);}
    else {
	if($$incrsizes{$volume}{$datelastfullbackup} > 
	   $$fullsizes{$volume}{$datelastfullbackup}) {
	    print LOGFILE "Making full backup, since size of incrementals are greater than last full.\n";
	    if($debug) { print LOGFILE "sum(incr) = $$incrsizes{$volume}{$datelastfullbackup} sum(full) = $$fullsizes{$volume}{$datelastfullbackup}\n"; }
	    makefullbackup($volume);
	} else {

	    makeincrementalbackup($volume, $datelastfullbackup);
	}
    }
}

print LOGFILE scalar localtime(time)," Ending $0\n";

close LOGFILE;
