#!/usr/bin/perl -w ######################################################################## # # # rbackup # # by Michael "Serpedon" Walz # # # # The official rbackup website is located at # # http://serpedon.de/Download/rbackup/ # # # # Copyright (C) 2007, 2008, 2011 Michael Walz # # # # rbackup comes with ABSOLUTELY NO WARRANTY. This is free software, # # and you may copy, distribute and/or modify it under the terms of # # the GNU GPL (version 2 or at your option any later version). # # See the GNU General Public License for details. # # # ######################################################################## use strict; # version of rbackup my $VERSION = '0.4'; my $SNAPSHOT_ROOT = undef; my $MOUNT_POINT = undef; my @INTERVAL_NAMES; my %INTERVAL_LENGTHS; my %INTERVAL_AMOUNTS; my @BACKUPS_NEEDED; my $FIRST_ARGUMENT = shift; my $FORCE = 0; my $CHECK = 0; my $LOCAL = 0; if (defined $FIRST_ARGUMENT ) { if ($FIRST_ARGUMENT eq "help") { help(); exit 0; } elsif ( $FIRST_ARGUMENT eq "version") { version(); exit 0; } elsif ( length($FIRST_ARGUMENT) >= 2 && substr($FIRST_ARGUMENT,0,1) eq "-" ) { for ( my $i = 1; $i < length($FIRST_ARGUMENT); $i++ ) { if ( substr($FIRST_ARGUMENT,$i,1) eq "f" ) { $FORCE = 1; }elsif ( substr($FIRST_ARGUMENT,$i,1) eq "c" ) { $CHECK = 1; }elsif ( substr($FIRST_ARGUMENT,$i,1) eq "l" ) { $LOCAL = 1; } else { println("ERROR: -" . substr($FIRST_ARGUMENT,$i,1) . " is not a valid argument"); println(""); help(); exit 1; } } } elsif ( $FIRST_ARGUMENT ne "" ) { println("ERROR: $FIRST_ARGUMENT is not a valid argument"); println(""); help(); exit 1; } } disclaimer(); if ( ! $LOCAL ) { if ( ! check("Checking for root", check_root()) ) { println("You are not root. Exiting..."); exit 1; } } if ( ! check("Reading configuration file", read_config()) ) { println("The configuration file is mad. Exiting..."); exit 1; } if ( defined $MOUNT_POINT ) { if ( ! check ("Mounting $MOUNT_POINT", mount($MOUNT_POINT) ) ) { println("Cannot mount $MOUNT_POINT. Exiting..."); exit 1; } } my $interval_one_freq = ""; foreach my $interval(@INTERVAL_NAMES) { if ( check("Checking interval '$interval'", need_backup($interval, $interval_one_freq) ) ) { unshift(@BACKUPS_NEEDED, $interval); } else { println("The interval '$interval' needs no backup."); last; } $interval_one_freq = $interval; } my $result = 0; foreach my $interval(@BACKUPS_NEEDED) { println("rsnapshot $interval"); my $res; if ( $LOCAL ) { if ( $CHECK ) { $res = system("rsnapshot -c ./rbackup-check.conf $interval"); } else { $res = system("rsnapshot -c ./rsnapshot.conf $interval"); } } else { if ( $CHECK ) { $res = system("rsnapshot -c /etc/rbackup-check.conf $interval"); } else { $res = system("rsnapshot $interval"); } } if ($res == 2 ) { $result = 2; } elsif ( $res != 0 ) { println ("ERROR: rsnapshot wasn't successful...\nExit code: $res\n"); exit 1; } } if ( defined $MOUNT_POINT ) { if ( ! check ("Unmounting $MOUNT_POINT", umount($MOUNT_POINT) ) ) { println("Cannot unmount $MOUNT_POINT. Exiting..."); exit 2; } } exit $result; ######################################## ### FUNCTIONS ### ######################################## # mount 'mount_point' # args: mount_point # result: none sub mount { my $mount_point = shift(@_); system ( "mount $mount_point" ); return 0 == system ( "mount|grep $mount_point>/dev/null" ); } # umount 'mount_point' # args: mount_point # result: none sub umount { my $mount_point = shift(@_); return 0 == system ( "umount $mount_point" ); } # shows the help page # args: none # result: none sub help { println("rsnapshot $VERSION"); println("Usage: rbackup [options]"); println("Type \"perldoc rbackup\" for more information."); println(""); println("rbackup is a wrapper script for rsnapshot. It can take incremental"); println("snapshots of local and remote filesystems for any number of machines."); println(""); disclaimer(""); } # show the GPL disclaimer # args: none # result: none sub disclaimer { println("rbackup comes with ABSOLUTELY NO WARRANTY. This is free software,"); println("and you are welcome to redistribute it under certain conditions."); println("See the GNU General Public License for details."); } # shows the version # args: none # result: none sub version { println("rsnapshot $VERSION"); } # checks if 'interval' needs to be backuped # args: interval, interval_one_freq # result: true if backup is needed sub need_backup { my $interval = shift(@_); my $interval_one_freq = shift(@_); if ( $interval_one_freq eq "" ) { # First is the firstbackup # If the switch -f was set, this has to be done. if ( $FORCE ) { return 1; } if ( -d "$SNAPSHOT_ROOT$interval.0" ) { return ( ( ( -M "$SNAPSHOT_ROOT$interval.0" ) *24*3600 ) > $INTERVAL_LENGTHS{$interval} ); } else { return 1; } } else { my $backup_number_one_freq = $INTERVAL_AMOUNTS{$interval_one_freq} - 1; if ( -d "$SNAPSHOT_ROOT$interval_one_freq.$backup_number_one_freq" ) { if ( -d "$SNAPSHOT_ROOT$interval.0" ) { return ( ( ( (-M "$SNAPSHOT_ROOT$interval.0") - (-M "$SNAPSHOT_ROOT$interval_one_freq.$backup_number_one_freq" ) ) *24*3600 ) >= $INTERVAL_LENGTHS{$interval} ); } else { return 1; } } else { return 0; } } } # Print 'text' in an line # args: String text # result: none sub println { my $text = shift(@_); print " $text\n"; } # Prints 'text' and uses 'success' to decide whether 'text' was successful or not # args: String text, boolean success # result: 'success' sub check { my $text = shift(@_); my $success = shift(@_); print "* $text... "; if ( $success ) { print "done\n"; } else { print "FAILED\n"; } return $success; } # checks if current user is root # args: none # result: true if user is root sub check_root { # $<: id of current user # $>: id of calculated user return ($< == 0 && $> == 0 ); } # read the config file # args: none # result: true if success sub read_config { if ( $LOCAL ) { open(FROM, "./rsnapshot.conf") or println("Error: cannot open ./rsnapshot.conf") and return 0; } else { open(FROM, "/etc/rsnapshot.conf") or println("Error: cannot open /etc/rsnapshot.conf") and return 0; } my $buf; my $config; while (read FROM, $buf, 4096) { $config = $config . $buf; } close FROM; # replace all newlines $config =~ s/[\r\n|\r|\n]/\\n/g; my @lines = split(/\\n/, $config); foreach my $line (@lines){ if ( $line =~ /^snapshot_root\s+([^\s]+)$/ ) { $SNAPSHOT_ROOT = $1; } elsif ( $line =~ /^interval\s+([^\s]+)\s+([^\s]+)$/ ) { push(@INTERVAL_NAMES, $1); $INTERVAL_AMOUNTS{$1}=$2; } elsif ( $line =~ /^#rbackupconf:intervallen\s+([^\s]+)\s+([^\s]+)$/ ) { $INTERVAL_LENGTHS{$1}=$2; } elsif ( $line =~ /^#rbackupconf:mount_point\s+([^\s]+)$/ ) { $MOUNT_POINT = $1; } } # Check if all interval lengths werde found and if they are sorted upwards my $curIntLen = 0; foreach my $int(@INTERVAL_NAMES) { if ( ! exists($INTERVAL_LENGTHS{$int}) ) { println("The interval '$int' has no length."); return 0; } else { if ( $curIntLen >= $INTERVAL_LENGTHS{$int} ) { println("The intervals have to be sorted upwards in the configuration file" ); return 0; } $curIntLen = $INTERVAL_LENGTHS{$int}; } } return 1; } ######################################## ### PERLDOC / POD ### ######################################## =pod =head1 NAME rbackup - remote filesystem backup utility [uses rsnapshot] =head1 SYNOPSIS B [options] =head1 DESCRIPTION B is only a wrapper-tool for rsnapshot that uses mainly rsync (see B and B) The only advantage of B is that you can use it if you cannot run a cron job to run B regularly. If you can use cron jobs, use them and B use B. B will read the configfile of B and will examine the backups to get the dates the backups were created. If there needs to be created more backups (according to the configfile), B will call B with the according interval name as argument. So, B will typically be invoked as root by hand at some random time. (e.g. when the backup disk is connected) Most options are specified in the configuration file of B, which is located at B. The command line options are as follows: =over 4 B Shows some short help information. B Shows the version. B<-???> Command line switches that consist of only one letter. =over 4 B Will start rsnapshot with the alternate configfile B. Normally the content of this file is as follow: =over 8 1. rsync will be instructed to check the files using checksums 2. The normal configfile B will be included. =back B Forces a backup. The first interval will be started whatever the interval check should result. B Acts locally. That means to the config files used will be searched in the currect directory instead of B. In addition, the check if rbackup runs as root is skipped. =back =back =head1 CONFIGURATION B is the default configuration file of B. B will lookup the following parameters: =over 4 B Local filesystem path to save all snapshots B [name] [number] Additional B searches lines that have the following sheme: =over 4 #rbackupconf:intervallen [name] [length] =over 4 B<[name]> is the name of the interval that length should be specified. B<[length]> is the length of the interval in seconds. (The #-char is needed because B would complain otherwise.) B =over 4 interval hourly 24 #rbackupconf:intervallen hourly 3600 interval daily 7 #rbackupconf:intervallen daily 86400 interval weekly 4 #rbackupconf:intervallen weekly 604800 =back =back #rbackupconf:mount_point [mount_point] =over 4 This option is optional. If set, B will mount B<[mount_point]> at the beginning and umount at the end. (simply per "mount [mount_point]"; so you have to write something in B) The B<[mount_point]> should not end with a slash. =back =back (Important: B won't support B. All needed parameters have to be found directly in B.) =back =head1 USAGE B can only be used by root. Otherwise B will complain. Using the example of above, B will behave as follows: =over 4 =item 1. B will check if it's run by root. Otherwise it will exit. =item 2. B will read the configuration file B and will complain about wrong/incomplete values. =item 3. B will check if there needs to be created a new backup for the first interval. If not, B will exit immeadiatly. Example: B will check the date of B and will exit if this backup isn't older than 3600 seconds. =item 4. B will check if the higher backups need to be created already. Example: B will check the date of B and decides that it needs to be backuped if the difference to hourly.4 is greater than 86400 seconds. Important note: Be sure, that you adjust the interval length. If you want to do a backup once an hour an you run rbackup every full hour, an interval length of 3600 seconds is a bad idea because the backup will be finished some minutes after the full hour an so the age of this backup will be something about 3500 seconds (or less) during the next run. So there won't be done a new backup. =over 4 This will be done recursivly through all interval names. =back =item 5. B will call B<"rsnapshot intervalname"> several times for each interval needed. The commands will be executed downwards (according to interval length). Example: B will call B<"rsnapshot daily"> and afterwards B<"rsnapshot hourly">. =back =head1 EXIT VALUES =over 4 B<0> All operations completed successfully B<1> A fatal error occurred B<2> Some warnings occurred, but the backup still finished =back =head1 FILES /etc/rsnapshot.conf =head1 SEE ALSO rsnapshot(1) (really important) =head1 BUGS Please report bugs (and other comments) in English or German (preferred) to the rbackup-homepage B or per Mail to B B B. =head1 AUTHORS Michael "Serpedon" Walz B =head1 COPYRIGHT Copyright (C) 2007,2008,2011 Michael "Serpedon" Walz This man page is distributed under the same license as rbackup: the GPL (see below). This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA =cut