ColdCore-3.1/
ColdCore-3.1/bin/
ColdCore-3.1/dbbin/
ColdCore-3.1/root/
ColdCore-3.1/root/html/
ColdCore-3.1/root/html/jlogin/
ColdCore-3.1/root/logs/
#!/usr/bin/perl

$debug = 0;

########################################################################
#
# This is a complete rewrite of the original backup script provided
# for cold.  It is much friendlier for filesystem backups (the previous
# one was very unfriendly with incremental FS backups)--and it also
# provides an option for storing the textdb instead of binarydb --
# this is much safer and portable, but also more CPU intensive on the
# system.
#
# Configurable variables:
#

##
## who to whine at when there are problems, and 'mailx'
## (make sure to backslash-escape the at-sight)
##
#$admin_email = "admin\@domain.com";
$mailx_exe =   "/usr/bin/Mail";

##
## The base directories--SET THIS!!
##
$base_dir   = "/cold/current";
$logs_dir   = "$base_dir/logs";
$backup_dir = "$base_dir/backups";

##
## The retention of backups
##
$hourly_set  = 24;  # how many in the hourly set?  default: 1 day's worth
$daily_set   = 30;  # how many in the daily set?   default: 1 month's worth
$monthly_set = 12;  # how many in the monthly set? default: 1 year's worth

##
## Clean the set 1=yes 0=no (setting to no will mean it does not cleanup
## after the set, and you must do it manually)
##
$clean_hourly  = 1;    # hourly backups?
$clean_daily   = 1;    # daily backups?
$clean_monthly = 1;    # monthly backups?

##
## Store binary or text backups?  0=binary, 1=text
##
## if you set text, make sure $coldcc_exec is correct (fully qualified),
## and make sure $mkfifo_exec (or its equivalent) is set properly.
## if you set binary, make sure $tar_exec is correct (fully qualified)
##
$store_text = 1;

##
## compress output (binary or text)? 0=no 1=yes
##
$compress_output = 1;

##
## what to compress the text file with?
##
                   # bzip is the best, I see about 85% compression
$compress_exec   = "/usr/local/bin/bzip2 -9f";
$compress_suffix = "bz2";

                   # alternative (gzip), I see about 80% compression
#$compress_exec   = "/usr/bin/gzip -9f";
#$compress_suffix = "gz";

                   # alternative legacy compress, I see about 68% compression
#$compress_exec   = "/usr/bin/compress";
#$compress_suffix = "Z";

##
## more utilities required, make sure the path is correct
##
$coldcc_exec = "$base_dir/bin/coldcc";
$tar_exec = "/usr/bin/tar";

$mkfifo_exec = "/usr/bin/mkfifo";
# $mkfifo_exec = "/usr/bin/mknod p"; # SYSV way is something like this

##
## What permission level should the backups be set to?
##
## Options:
##
##    0600     read/write by owner, and nobody else
##    0640     read/write by owner, read by group, and nobody else
##    0644     read/write by owner, read by anybody else
##
$backup_perms = 0600;

########################################################################
## No configurable variables are beyond this point
########################################################################

$ENV{PATH} = '';

# just in case it wasn't called correctly...
if (!$debug) {
    if (fork()) {
        exit();
    }
}

# so we dont have to load a module for it ** DO NOT CHANGE THESE **
$LOCK_SH = 1;  $LOCK_EX = 2;  $LOCK_NB = 4;  $LOCK_UN = 8;
$SEEK_SET = 0; $SEEK_CUR = 1; $SEEK_END = 1;

# anybody have the time?
($l_sec,$l_min,$l_hour,$l_mday,$l_mon,$l_year,$l_wday,$l_yday) = localtime();
$time = time();
$l_year+= 1900; # no y2k issues here

#@days = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat");
#@months = ("Jan","Feb","Mar","Apr","May","Jun",
#           "Jul","Aug","Sep","Oct","Nov","Dec");
#
#
#$d_mn = "$months[$l_mon]$l_year";
#$d_da = sprintf("%02d$d_mn", $l_mday);
#$d_hr = sprintf("${d_da}:%02d", $l_hour);

$d_mn = sprintf("$l_year%02d", $l_mon+1);
$d_da = sprintf("$d_mn%02d", $l_mday);
$d_hr = sprintf("$d_da%02d", $l_hour);

$lockfile = "$base_dir/L.$d_hr";
$textdb   = "textdb-${d_hr}";
$binarydb = "binary-${d_hr}";

lopen(LOCK, ">$lockfile") || die("open(>$lockfile): $!\n");
print LOCK "$$\n";

@nix_files = ($lockfile);
$SIG{INT} = 'abort';
$SIG{TERM} = 'abort';
$SIG{QUIT} = 'abort';

##
## bundle up the current backup
##
chdir($base_dir);
rename("binary.bak", $binarydb) ||
   die("rename(binary.bak => $binarydb): $!\n");

if ($store_text) {
    $bundle = $td_out = "$backup_dir/$textdb";
    if ($compress_output) {
        $td_out = "textdump_fifo";
        push(@nix_files, $td_out);
        &cmd("$mkfifo_exec $td_out.out");
        $zout = "$backup_dir/_$textdb.$compress_suffix";
        &cmd("$compress_exec < $td_out.out > $zout &");
        $bundle .= ".$compress_suffix";
    }

    $rc=0xffff & &cmd("$coldcc_exec -d -o -b $binarydb -t $td_out 2>/dev/null");
    &abort("ColdCC decompile failed!!\n") if ($rc != 0);

    if ($compress_output) {
        unlink($td_out);

        ## all of this mess is incase coldcc dies, we dont hose a good db
        $zout1 = "$backup_dir/_$textdb.$compress_suffix";
        $zout2 = "$backup_dir/$textdb.$compress_suffix";
        rename($zout1, $zout2) || &abort("rename($zout1=>$zout2: $!\n");
    }
} else {
    &cmd("$tar_exec -cf $backup_dir/$binarydb.tar $binarydb");
    $bundle = "$backup_dir/$binarydb.tar";
    if ($compress_output) {
        &cmd("$compress_exec $bundle");
        $bundle .= ".$compress_suffix"; 
    }
}

## remove it
opendir(BINARY, $binarydb) || &abort("opendir($binarydb): $!\n");
@list = grep(!/^\.{1,2}$/, readdir(BINARY));
for $f (@list) {
    $debug && print "unlink(\"$binarydb/$f\")";
    if (unlink("$binarydb/$f")) {
        print "\n";
    } else {
        print ": $!\n";
    }
}
closedir(BINARY);
rmdir($binarydb) || print "rmdir(\"$binarydb\"): $!\n";

##
## now shuffle the existing backup sets
##
opendir(BACKUPS, "$backup_dir") ||
    &abort("Unable to open backup directory ($backup_dir): $!\n");
%hourly  = ();
%daily   = ();
%monthly = ();
for $f (readdir(BACKUPS)) {
    if ($f =~ /^(binary|textdb)-([^.]+)\./) {
        $date = $2;
        if (length($date) == 10) {
            $hourly{$date} = "textdb-$date.bz2";
        } elsif (length($date) == 8) {
            $daily{$date} = "textdb-$date.bz2";
        } elsif (length($date) == 6) {
            $monthly{$date} = "textdb-$date.bz2";
        }
    }
}
closedir(BACKUPS);

$debug && print "HOURLY\n";
&shift_set($clean_hourly, $hourly_set, \%hourly,
    sub { return $d =~ /^\d{8}00$/ },
    sub { $f2 =~ s/(\d{8})00/$1/ });

$debug && print "DAILY\n";
&shift_set($clean_daily, $daily_set, \%daily,
    sub { return $d =~ /^\d{6}01$/ },
    sub { $f2 =~ s/(\d{6})01/$1/ });

$debug && print "MONTHLY\n";
&shift_set($clean_monthly, $monthly_set, \%monthly,
    sub { return $d =~ /^\d{4}12$/ },
    sub { $f2 =~ s/(\d{4})12/$1/ });

##
## let go of the lock
##
unlink(@nix_files);
lclose(LOCK);

########################################################################

sub lopen {
    my ($fd, $fname) = @_;
    my $retry;

    if (!open($fd, $fname)) {
        print(STDERR "open($fd, \"$fname\"): $!\n");
        return 0;
    }
    $retry = $lopen_retry || 5;

    while (!flock($fd, $LOCK_EX|$LOCK_NB)) {
        if (!$retry) {
            print STDERR "Cannot lock file: $fname\n";
            return 0;
        }
        $retry--;
        sleep(1);
    }

    return $fd;
}

sub lclose {
    my ($file) = $_[0];
    
    close($file);
    flock($file, $LOCK_UN);
}

sub abort {
    unlink(@nix_files);
    lclose(LOCK);
    if (length($admin_email) && length($mailx_exe)) {
        system($mailx_exe, "-s", "Genesis Backup ERROR: $_[0]", $admin_email);
    }
    die(@_);
}

sub cmd {
    $debug && print("EXEC: " . join(" ", @_) . "\n");
    return system(@_);
}

sub shift_set {
    my ($clean_it, $set_max, $dict, $keep, $change) = @_;

    $debug && print "shift($clean_it, $set_max, $dict, $keep, $change)\n";

    if ($clean_it) {
        my @set = sort(keys(%$dict));
        $debug && print "CLEAN: $#set\n";
        while (($#set+1) > $set_max) {
            $d = shift(@set);
            $f1 = $$dict{$d};
            if (&$keep) {
                $f2 = $f1;
                &$change;
                $debug && print "rename($f1, $f2)\n";
                rename("$backup_dir/$f1", "$backup_dir/$f2") ||
                    &abort("rename($f1 => $f2): $!\n");
            } else {
                $debug && print "unlink($f1)\n";
                unlink("$backup_dir/$f1") || print STDERR "unlink($f1): $!\n";
            }
        }
    } else {
        $f1 = $f2 = $bundle;
        &$change;
        $debug && print "rename($f1, $f2)\n";
        rename("$backup_dir/$f1", "$backup_dir/$f2") ||
            &abort("rename($f1 => $f2): $!\n");
    }
}