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