From 6bc31caaed8fdcd2c1ce738b5b672c80e3cd7a09 Mon Sep 17 00:00:00 2001 From: Andrew Ruthven Date: Sat, 16 Feb 2008 22:51:51 +1300 Subject: [PATCH] Add in more functionality. --- mirror | 303 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 220 insertions(+), 83 deletions(-) diff --git a/mirror b/mirror index c971635..45fea35 100644 --- a/mirror +++ b/mirror @@ -38,7 +38,7 @@ # limited testing under their belt. # # Instructions: -# +# # The first thing you need to do is go down to the end of this header section # and set all the user defined variables in the user settings section. # They have a brief description of what they do and how they should be set. @@ -53,18 +53,18 @@ # Once that is done, you can run the program. # # mirror supports one command line switches ( -s ). -# -s = run sync mode only +# -s = run sync mode only # -# Sync mode should be ran the first time you setup mirror. Otherwise, use +# Sync mode should be ran the first time you setup mirror. Otherwise, use # no switch. # Once you have run mirror for the first time, you will find that it has setup # a directory hierarchy in your backup root directory: # -# // ( host root ) -# ///working ( working directory ) -# ///hourly ( hourly saves ) -# ///daily ( daily saves ) -# ///weekly ( weekly saves ) +# // ( host root ) +# ///working ( working directory ) +# ///hourly ( hourly saves ) +# ///daily ( daily saves ) +# ///weekly ( weekly saves ) # # The directories are named 'image----' # @@ -92,133 +92,270 @@ use YAML::Syck; use POSIX qw/strftime/; +use LockFile::Simple; +use Sys::Hostname; -use GetOptions::Long; +use Getopt::Long; my $config_file = "mirror.yaml"; my $sync = undef; +my $lock_file = "/var/lock/mirror-" . hostname; GetOptions("config|c=s" => $config_file, - "sync|s" => $sync); + "sync|s" => $sync); my @errors = (); my @warnings = (); my $c = load_config(); +# Try and obtain a lock. +my $lockmgr = LockFile::Simple->make(-stale => 1, + -autoclean => 1, + -hold => 60 * 60 * 3); +if (! $lockmgr->lock($lock_file)) { + push @errors, "Failed to obtain lockfile"; + die_gracefully(); +} + +dodir(); +# do work + +$lockmgr->unlock($lock_file); + + sub load_config { - my $c = LoadFile($config_file); + my $c = eval { LoadFile($config_file) }; + die_gracefully("Failed to load $config_file: $@") + if $@; - for my $required ('backuproot', 'hostid') { - die_gracefully("No $required set") - unless defined $c->{$required} || $c->{required} ne ''; - } + for my $required ('backuproot', 'hostid') { + die_gracefully("No $required set") + unless defined $c->{$required} && $c->{$required} ne ''; + } $c->{'cp'} ||= "cp -alf"; - $c->{'weekdir'} = "$c->{'backuproot'}/$c->{'hostid'}/weekly"; + $c->{'weekdir'} = "$c->{'backuproot'}/$c->{'hostid'}/weekly"; $c->{'daydir'} = "$c->{'backuproot'}/$c->{'hostid'}/daily"; - $c->{'hourdir'} = "$c->{'backuproot'}/$c->{'hostid'}/hourly"; - $c->{'working'} = "$c->{'backuproot'}/$c->{'hostid'}/working"; - $c->{'lockfile'} = "$c->{'backuproot'}/$c->{'hostid'}/syncing-now"; + $c->{'hourdir'} = "$c->{'backuproot'}/$c->{'hostid'}/hourly"; + $c->{'working'} = "$c->{'backuproot'}/$c->{'hostid'}/working"; + $c->{'lockfile'} = "$c->{'backuproot'}/$c->{'hostid'}/syncing-now"; - $c->{'datedir'} = strftime("image-%Y-%m-%d-%H"); - $c->{'dow'} = strftime("%w"); - $c->{'chour'} = strftime("%H"); - $c->{'lday'} = strftime("image-%Y-%m-%d-%H", localtime() - 3600 * 24); - $c->{'1week'} = strftime("image-%Y-%m-%d-%H", localtime() - 3600 * 24 * 7); + $c->{'datedir'} = strftime("image-%Y-%m-%d-%H"); + $c->{'dow'} = strftime("%w"); + $c->{'chour'} = strftime("%H"); + $c->{'lday'} = strftime("image-%Y-%m-%d-%H", localtime() - 3600 * 24); + $c->{'1week'} = strftime("image-%Y-%m-%d-%H", localtime() - 3600 * 24 * 7); - return \%c; + return $c; }; sub dodir { - for my dir (qw'weekdir daydir hourdir working') { - if (test("! -d $c->{$dir}")) { - run_and_check("mkdir -p $c->{$dir}"); - } - } + for my $dir (qw'weekdir daydir hourdir working') { + if (test("! -d $c->{$dir}")) { + run_and_check("mkdir -p $c->{$dir}"); + } + } } -sub doweek { - return - if $c->{'saveweeks'} == 0; +sub doweek { + return + if defined $c->{'save'}{'weeks'} && $c->{'save'}{'weeks'} == 0; - if (test("-d $c->{'daydir'}/$c->{'lweek'}")) { - run_and_check("mv $c->{'daydir'}/$c->{'lweek'} $c->{'weekdir'}") - } else { - push @warnings, "I can't find a daily snapshot that is a week old..."; - } + if (test("-d $c->{'daydir'}/$c->{'lweek'}")) { + run_and_check("mv $c->{'daydir'}/$c->{'lweek'} $c->{'weekdir'}") + } else { + push @warnings, "I can't find a daily snapshot that is a week old..."; + } } sub doday { - return - if $c->{'savedays'} == 0; - - if (test("-d $c->{'hourdir'}/$c->{'lday'}")) { - run_and_check("mv $c->{'hourdir'}/$c->{'lday'} $c->{'daydir'}"); - } else { - push @warnings, "I can't find an hourly snapshot that is 24hrs old..." - } + return + if defined $c->{'save'}{'days'} && $c->{'save'}{'days'} == 0; + + if (test("-d $c->{'hourdir'}/$c->{'lday'}")) { + run_and_check("mv $c->{'hourdir'}/$c->{'lday'} $c->{'daydir'}"); + } else { + push @warnings, "I can't find an hourly snapshot that is 24hrs old..." + } } sub dohour { - run_and_check("$c->{'cp'} $c->{'working'} $c->{'hourdir'}/$c->{'datedir'}"); - run_and_check("touch $c->{'hourdir'}/$c->{'datedir'}"); + run_and_check("$c->{'cp'} $c->{'working'} $c->{'hourdir'}/$c->{'datedir'}"); + run_and_check("touch $c->{'hourdir'}/$c->{'datedir'}"); } sub dosync { - if (defined $c->{'autosrcdir'}) { - allsrcdir=`ssh ${backedhost} "ls -d /*"` - } - - for srcdir in ${allsrcdir}; do - if [ "${backedhost}" != "" ]; then - if [ ${advexcludes} -gt 0 ]; then - run_and_check "${rsync} ${EXCLUDES} ${backedhost}:${srcdir} ${working}" - else - run_and_check "${rsync} ${backedhost}:${srcdir} ${working}" - fi - else - if [ ${advexcludes} -gt 0 ]; then - run_and_check "${rsync} ${EXCLUDES} ${srcdir} ${working}" - else - run_and_check "${rsync} ${srcdir} ${working}" - fi - fi -done + # See if we should backup everything... + my $srcdirs; + if (defined $c->{'autosrcdir'}) { + if (defined $c->{'backedhost'}) { + push @{ $srcdirs }, split("\n", `ssh $c->{'backedhost'} "ls -d /*"`); + } else { + push @{ $srcdirs }, split("\n", `ls -d /*`); + } + } else { + $srcdirs = $c->{'allsrcdir'}; + } + + my $dest = (defined $c->{'rmthost'} ? "$c->{'rmtlogin'}@$c->{'rmthost'}:" : '') + . $c->{'working'}; + + for my $dir (@{ $srcdirs }) { + my $srcdir = (defined $c->{'backedhost'} ? "$c->{backedhost}:" : '') + . $dir; + + if (defined $c->{advexcludes} && $c->{advexcludes} == 1) { + run_and_check("$c->{'rsync'} $EXCLUDES $srcdir $dest"); + } else { + run_and_check("$c->{'rsync'} $srcdir $dest"); + } + } } +sub docleanup { + $c->{'save'}{'hours'} ||= 1; + + my @hours = split("\n", run_and_return("ls -t $c->{'hourdir'}")); + + my $count = 0; + for my $hour (@hours) { + $count++; + + if ($count > $c->{'save'}{'hours'}) { + run_and_check("rm -Rf $c->{'hourdir'}/$hour"); + } + } + + if ($c->{'save'}{'weeks'} > 0 && $c->{'save'}{'days'} < 7) { + $c->{'save'}{'days'} = 7; + } + + my @days = split("\n", run_and_return("ls -t $c->{'daydir'}")); + + $count = 0; + for my $day (@days) { + $count++; + + if ($count > $c->{'save'}{'days'}) { + run_and_check("rm -Rf $c->{'daydir'}/$day"); + } + } + + my @weeks = split("\n", run_and_return("ls -t $c->{'weekdir'}")); + + $count = 0; + for my $week (@weeks) { + $count++; + + if ($count > $c->{'save'}{'weeks'}) { + run_and_check("rm -Rf $c->{'weekdir'}/$week"); + } + } +} + + # Perform a test, either via ssh or using a local perl check sub test { - my ($test) = shift; - - if (defined $c->{rmtssh}) { - system("$c->{rmtssh} [ $test ]"); - - return ($? >> 8); - } else { - return $test; - } + my ($test) = shift; + + if (defined $c->{rmtssh}) { + system("$c->{rmtssh} [ $test ]"); + + return ($? >> 8); + } else { + return $test; + } } - + # Run a command and check its return code. sub run_and_check { my ($cmd, $die) = shift; - $cmd = "$c->{$rmtssh} $cmd" - if defined $c->{rmtssh}; + $cmd = "$c->{$rmtssh} \"$cmd\"" + if defined $c->{rmtssh}; system($cmd); if ($? >> 8 != 0) { - push @errors "Command \"$cmd\" failed with return code " . ($? >> 8) . "\n"; + push @errors, "Command \"$cmd\" failed with return code " . ($? >> 8); + + die_gracefully() + if defined $die; + + return 0; + } +} + +# Run a command and return its output. +sub run_and_return { + my ($cmd, $die) = shift; + + $cmd = "$c->{$rmtssh} \"$cmd\"" + if defined $c->{rmtssh}; + + my $output = `$cmd`; + if ($? >> 8 != 0) { + push @errors, "Command \"$cmd\" failed with return code " . ($? >> 8); + + die_gracefully() + if defined $die; + } + + return $output; +} + + +sub die_gracefully { + my $message = shift; + + if ($#errors > 0 || $#warnings > 0 || defined $message) { + handle_output($message); + } + + print "$message\n" + if defined $message; + + exit 0; +} - die_gracefully() - if defined $die; +# Either print the status out, or email it. +sub handle_output { + my $output = shift; + $output .= "\n" + if defined $output && $output ne ""; - return 0; + my $title = "mirror report from " . hostname; + my $suffix = undef; + + if ($#errors > 0) { + $output .= "Errors:\n"; + $output .= join("\n", @errors); + $output .= "\n"; + $suffix = "ERRORS"; + } + + if ($#warnings > 0) { + $output .= "Warnings:\n"; + $output .= join("\n", @warnings); + $suffix ||= "WARNINGS"; + } + + if (scalar(@{ $c->{'emails'} }) == 0) { + print "\n$output"; + } else { + my $mail = MIME::Entity->build( + To => \@{ $c->{emails} }, + Subject => $title . (defined $suffix ? " - $suffix" : ''), + Data => $output + ); + + $mail->send('sendmail'); } } + + -- 2.30.2