#!/usr/bin/perl -w use strict; # All the same scheduling and checking if the drive is mounted, # but uses rsync instead of dar! =pod testing my $src_dir = "/home/pjungwir/src/flashcards/"; my $dest_dir = "/home/pjungwir/src/test/rsync/bak2"; my $excludes_file = "$dest_dir/excludes.txt"; my $current_dir = "current"; my $incremental_dir = "inc"; my $last_backup_file = "$dest_dir/last-backup.txt"; my $one_day = 60*60*24; # one day in seconds my $today_in_seconds = time(); my $lock = "/tmp/rsync-backup"; my $max_incrementals = 3; my $previous_backup_date; =cut # my $src_dir = "/home/pjungwir"; my $src_dir = "/"; my $dest_dir = "/media/Elements/bak/shiny"; my $excludes_file = "$dest_dir/excludes.txt"; my $current_dir = "current"; my $incremental_dir = "inc"; my $last_backup_file = "$dest_dir/last-backup.txt"; my $one_day = 60*60*24; # one day in seconds my $today_in_seconds = time(); my $lock = "/tmp/rsync-backup"; my $max_incrementals = 3; my $previous_backup_date; my $LS = "/bin/ls"; my $DATE = "/bin/date"; my $RSYNC = "/usr/bin/rsync"; my $RM = "/bin/rm"; print_time("starting"); check_for_disk($dest_dir); try_lock_file($lock); handle_signals(); write_lock_file($lock); remove_old_incrementals($dest_dir, $max_incrementals); $previous_backup_date = get_last_backup_date($last_backup_file); run_rsync($src_dir, $dest_dir, $excludes_file, $incremental_dir, $previous_backup_date); write_last_backup_date($last_backup_file, $today_in_seconds); clean_lock_file($lock); print_time("finished"); sub print_time { my $msg = shift; print "$msg " . localtime() . "\n"; } # Don't bother doing a backup if the external hard drive isn't connected: sub check_for_disk { my $disk_dir = shift; (-e $disk_dir) or die "No disk connected at $disk_dir\n"; } # remove all but the last n incremental backups. # TODO: make this smarter so it removes however many are required # to provide adequate space for the backup sub remove_old_incrementals { my ($dest, $n) = @_; my @dirs = split /\n/, run("$LS -t1 $dest/inc-* 2>/dev/null"); for my $dir (@dirs[$n .. scalar @dirs]) { next unless $dir; # run("echo $RM -rf $dir"); run("$RM -rf $dir"); } } sub write_last_backup_date { my $filename = shift; my $today = shift; # TODO: error check date command my $date = run("$DATE +%Y-%m-%d"); chomp $date; open DFILE, ">$filename" or die "Can't open $filename for writing: $!\n"; print DFILE "$date\n"; close DFILE; } sub get_last_backup_date { my $filename = shift; my $success = open DFILE, "<$filename"; if ($success) { my $date = ; chomp $date; close DFILE; return $date; } else { print STDOUT "No 'last backup' file found at $filename\n"; return 0; } } sub run { my $command = shift; print "$command\n"; my $result = `$command`; return $result; } sub run_rsync { my ($src, $dest, $excludes, $inc_dir, $prev_backup_date) = @_; my $opts = ''; if ($excludes) { $opts .= " --exclude-from='$excludes'"; } if ($prev_backup_date) { $opts .= " --backup --backup-dir='../$inc_dir-$prev_backup_date'"; } my $command = "$RSYNC -avz $opts '$src' '$dest/current'"; print run($command); my $err = $? >> 8; return $err; } sub handle_signals { $SIG{'INT'} = 'clean_lock_file'; $SIG{'__DIE__'} = 'clean_lock_file'; } sub is_running { my $pid = shift; # TODO: use ps? return 0; } sub clean_lock_file { my $lock_file = shift || $lock; unlink $lock_file; } sub write_lock_file { # TODO: fork rsync and write its pid, too. my $lock_file = shift; open LF, ">$lock_file" or die "Can't open $lock_file for writing: $!\n"; print LF "$$\n"; close LF; } sub try_lock_file { my $lock_file = shift; if (-e $lock_file) { my @stats = stat($lock_file) or die "Can't stat $lock_file: $!\n"; my $file_age = $today_in_seconds - $stats[9]; if ($file_age >= $one_day) { open LF, "<$lock_file" or die "Can't open $lock_file for reading: $!\n"; my $pid = ; system("kill -9 $pid"); if (is_running($pid)) { die "Can't kill $pid\n"; } close LF; print STDERR "WARN: stale process found\n"; # don't exit so we can go ahead an run the backup. } else { die "process already running\n"; } } else { # no lock file means we can run; do nothing here. } }