~ubuntu-branches/ubuntu/edgy/rsnapshot/edgy

« back to all changes in this revision

Viewing changes to rsnapshot-program.pl

  • Committer: Bazaar Package Importer
  • Author(s): Simon Boulet
  • Date: 2006-06-05 22:03:45 UTC
  • mfrom: (3.1.1 etch)
  • Revision ID: james.westby@ubuntu.com-20060605220345-byz0jaecexc3h2m4
Tags: 1.2.9-1
* New upstream release
* Updated Free Software Foundation address in debian/copyright
* Added rsnapshot-diff man page from CVS
* Switched autotools-dev and rsync back to Build-Depends-Indep

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
#                                                                      #
5
5
# rsnapshot                                                            #
6
6
# by Nathan Rosenquist                                                 #
7
 
#                                                                      #
8
 
# Based on code originally by Mike Rubel                               #
9
 
# http://www.mikerubel.org/computers/rsync_snapshots/                  #
 
7
# now  maintained by David Cantrell                                    #
10
8
#                                                                      #
11
9
# The official rsnapshot website is located at                         #
12
10
# http://www.rsnapshot.org/                                            #
13
11
#                                                                      #
 
12
# Copyright (C) 2003-2005 Nathan Rosenquist                            #
 
13
#                                                                      #
 
14
# Portions Copyright (C) 2002-2006 Mike Rubel, Carl Wilhelm Soderstrom,#
 
15
# Ted Zlatanov, Carl Boe, Shane Liebling, Bharat Mediratta,            #
 
16
# Peter Palfrader, Nicolas Kaiser, David Cantrell, Chris Petersen,     #
 
17
# Robert Jackson, Justin Grote, David Keegel                           #
 
18
#                                                                      #
14
19
# rsnapshot comes with ABSOLUTELY NO WARRANTY.  This is free software, #
15
 
# and you are welcome to redistribute it under certain conditions.     #
16
 
# See the GNU General Public License for details.                      #
 
20
# and you may copy, distribute and/or modify it under the terms of     #
 
21
# the GNU GPL (version 2 or at your option any later version).         #
 
22
# See the GNU General Public License (in file: COPYING) for details.   #
 
23
#                                                                      #
 
24
# Based on code originally by Mike Rubel                               #
 
25
# http://www.mikerubel.org/computers/rsync_snapshots/                  #
17
26
#                                                                      #
18
27
########################################################################
19
28
 
 
29
# $Id: rsnapshot-program.pl,v 1.339 2006/05/18 10:12:37 djk20 Exp $
 
30
 
20
31
# tabstops are set to 4 spaces
21
32
# in vi, do: set ts=4 sw=4
22
33
 
26
37
 
27
38
require 5.004;
28
39
use strict;
29
 
use DirHandle;
 
40
use DirHandle;                  # DirHandle()
30
41
use Cwd;                                # cwd()
31
42
use Getopt::Std;                # getopts()
32
43
use File::Path;                 # mkpath(), rmtree()
33
44
use File::stat;                 # stat(), lstat()
34
45
use POSIX qw(locale_h); # setlocale()
 
46
use Fcntl;                              # sysopen()
 
47
 
 
48
########################################
 
49
###           CPAN MODULES           ###
 
50
########################################
 
51
 
 
52
# keep track of whether we have access to the Lchown module
 
53
my $have_lchown = 0;
 
54
 
 
55
# use_lchown() is called later, so we can log the results
35
56
 
36
57
########################################
37
58
###     DECLARE GLOBAL VARIABLES     ###
41
62
$| = 1;
42
63
 
43
64
# version of rsnapshot
44
 
my $VERSION = '1.2.1';
 
65
my $VERSION = '1.2.9';
45
66
 
46
67
# command or interval to execute (first cmd line arg)
47
68
my $cmd;
57
78
my @backup_points;
58
79
 
59
80
# array of backup points to rollback, in the event of failure
60
 
# (when using link_dest)
61
81
my @rollback_points;
62
82
 
63
 
# "intervals" are user defined time periods (i.e. hourly, daily)
 
83
# "intervals" are user defined time periods (e.g., hourly, daily)
64
84
# this array holds hash_refs containing the name of the interval,
65
85
# and the number of snapshots to keep of it
66
86
my @intervals;
75
95
        archive
76
96
        check-config-version
77
97
        configtest
 
98
        diff
78
99
        delete
79
100
        du
 
101
        get-latest-snapshot
80
102
        help
81
103
        history
82
104
        list
83
105
        restore
84
106
        rollback
 
107
        sync
85
108
        upgrade-config-file
86
109
        version
87
110
        version-only
115
138
my $default_verbose             = 2;
116
139
my $default_loglevel    = 3;
117
140
 
118
 
# assume the config file is valid
 
141
# assume the config file is valid until we find an error
119
142
my $config_perfect = 1;
120
143
 
121
144
# exit code for rsnapshot
123
146
 
124
147
# global defaults for external programs
125
148
my $default_rsync_short_args    = '-a';
126
 
my $default_rsync_long_args_pre12               = '--delete --numeric-ids --delete-excluded';
 
149
my $default_rsync_long_args_pre12       = '--delete --numeric-ids --delete-excluded';
127
150
my $default_rsync_long_args             = '--delete --numeric-ids --relative --delete-excluded';
128
151
my $default_ssh_args                    = undef;
129
152
my $default_du_args                             = '-csh';
130
153
 
 
154
# set default for use_lazy_deletes
 
155
my $use_lazy_deletes = 0;       # do not delete the oldest archive until after backup
 
156
 
131
157
# exactly how the program was called, with all arguments
132
158
# this is set before getopts() modifies @ARGV
133
159
my $run_string = "$0 " . join(' ', @ARGV);
192
218
        exit_no_config_file();
193
219
}
194
220
 
 
221
# attempt to load the Lchown module: http://search.cpan.org/dist/Lchown/
 
222
use_lchown();
 
223
 
195
224
# if we're just doing a configtest, exit here with the results
196
225
if (1 == $do_configtest) {
197
226
        exit_configtest();
198
227
}
199
228
 
200
 
# if we're just using "du" to check the disk space, do it now (and exit)
201
 
# this is down here because it needs to know the contents of the config file
 
229
# if we're just using "du" or "rsnapshot-diff" to check the disk space, do it now (and exit)
 
230
# these commands are down here because they needs to know the contents of the config file
202
231
if ($cmd eq 'du') {
203
232
        show_disk_usage();
 
233
} elsif ($cmd eq 'diff') {
 
234
        show_rsnapshot_diff();
 
235
} elsif ($cmd eq 'get-latest-snapshot') {
 
236
        show_latest_snapshot();
204
237
}
205
238
 
206
239
#
207
240
# IF WE GOT THIS FAR, PREPARE TO RUN A BACKUP
208
241
#
209
242
 
210
 
# figure out which interval we're working on
211
 
# $cmd should store the name of the interval we'll run against
212
 
$interval_data_ref = get_interval_data( $cmd );
213
 
 
214
243
# log the beginning of this run
215
244
log_startup();
216
245
 
224
253
create_snapshot_root();
225
254
 
226
255
# actually run the backup job
227
 
handle_interval( $interval_data_ref );
 
256
# $cmd should store the name of the interval we'll run against
 
257
handle_interval( $cmd );
228
258
 
229
259
# if we have a lockfile, remove it
230
260
remove_lockfile();
242
272
# runs when rsnapshot is called with no arguments
243
273
# exits with an error condition
244
274
sub show_usage {
245
 
        print "rsnapshot $VERSION\n";
246
 
        print "Usage: rsnapshot [-vtxqVD] [-c cfgfile] <interval>|configtest|du|help|version\n";
247
 
        print "Type \"rsnapshot help\" or \"man rsnapshot\" for more information.\n";
 
275
        print<<HERE;
 
276
rsnapshot $VERSION
 
277
Usage: rsnapshot [-vtxqVD] [-c cfgfile] [command] [args]
 
278
Type \"rsnapshot help\" or \"man rsnapshot\" for more information.
 
279
HERE
248
280
        
249
281
        exit(1);
250
282
}
255
287
sub show_help {
256
288
        print<<HERE;
257
289
rsnapshot $VERSION
258
 
Usage: rsnapshot [-vtxqVD] [-c cfgfile] <interval>|configtest|du|help|version
 
290
Usage: rsnapshot [-vtxqVD] [-c cfgfile] [command] [args]
259
291
Type "man rsnapshot" for more information.
260
292
 
261
293
rsnapshot is a filesystem snapshot utility. It can take incremental
266
298
See the GNU General Public License for details.
267
299
 
268
300
Options:
269
 
    -v verbose       - show equivalent shell commands being executed
270
 
    -t test          - show verbose output, but don't touch anything
271
 
    -c [file]        - specify alternate config file (-c /path/to/file)
272
 
    -x one_fs        - don't cross filesystems (same as -x option to rsync)
273
 
    -q quiet         - suppress non-fatal warnings
274
 
    -V extra verbose - same as -v, but with more detail
275
 
    -D debug         - a firehose of diagnostic information
 
301
    -v verbose       - Show equivalent shell commands being executed.
 
302
    -t test          - Show verbose output, but don't touch anything.
 
303
    -c [file]        - Specify alternate config file (-c /path/to/file)
 
304
                       This will be similar, but not always exactly the same
 
305
                       as the real output from a live run.
 
306
    -q quiet         - Suppress non-fatal warnings.
 
307
    -V extra verbose - The same as -v, but with more detail.
 
308
    -D debug         - A firehose of diagnostic information.
 
309
    -x one_fs        - Don't cross filesystems (same as -x option to rsync).
 
310
 
 
311
Commands:
 
312
    [interval]       - An interval as defined in rsnapshot.conf.
 
313
    configtest       - Syntax check the config file.
 
314
    sync [dest]      - Sync files, without rotating. "sync_first" must be
 
315
                       enabled for this to work. If a full backup point
 
316
                       destination is given as an optional argument, only
 
317
                       those files will be synced.
 
318
    diff             - Front-end interface to the rsnapshot-diff program.
 
319
                       Accepts two optional arguments which can be either
 
320
                       filesystem paths or interval directories within the
 
321
                       snapshot_root (e.g., /etc/ daily.0/etc/). The default
 
322
                       is to compare the two most recent snapshots.
 
323
    du               - Show disk usage in the snapshot_root.
 
324
                       Accepts an optional destination path for comparison
 
325
                       across snapshots (e.g., localhost/home/user/foo).
 
326
    version          - Show the version number for rsnapshot.
 
327
    help             - Show this help message.
276
328
HERE
277
329
        
278
330
        exit(0);
365
417
        $cmd = $ARGV[0];
366
418
        
367
419
        # check for extra bogus arguments that getopts() didn't catch
368
 
        if (defined($cmd) && ('du' ne $cmd)) {
 
420
        if (defined($cmd) && ('du' ne $cmd) && ('diff' ne $cmd) && ('sync' ne $cmd)) {
369
421
                if (scalar(@ARGV) > 1) {
370
422
                        for (my $i=1; $i<scalar(@ARGV); $i++) {
371
423
                                print STDERR "Unknown option: $ARGV[$i]\n";
372
424
                                print STDERR "Please make sure all switches come before commands\n";
373
 
                                print STDERR "(i.e. 'rsnapshot -v hourly', not 'rsnapshot hourly -v')\n";
 
425
                                print STDERR "(e.g., 'rsnapshot -v hourly', not 'rsnapshot hourly -v')\n";
374
426
                                exit(1);
375
427
                        }
376
428
                        
403
455
        if (defined($opts{'x'}))        { $one_fs = 1; }
404
456
}
405
457
 
406
 
# accepts no arguments
 
458
# accepts an optional argument - no arg means to parse the default file,
 
459
#   if an arg is present parse that file instead
407
460
# returns no value
408
461
# this subroutine parses the config file (rsnapshot.conf)
409
462
#
416
469
        my $rsync_include_file_args     = undef;
417
470
        
418
471
        # open the config file
 
472
        my $config_file = shift() || $config_file;
419
473
        open(CONFIG, $config_file)
420
474
                or bail("Could not open config file \"$config_file\"\nAre you sure you have permission?");
421
475
        
445
499
                        next;
446
500
                }
447
501
                
 
502
                # INCLUDEs
 
503
                if($var eq 'include_conf') {
 
504
                        if(defined($value) && -f $value && -r $value) {
 
505
                                $line_syntax_ok = 1;
 
506
                                parse_config_file($value);
 
507
                        } else {
 
508
                                config_err($file_line_num, "$line - can't find file '$value'");
 
509
                                next;
 
510
                        }
 
511
                }
 
512
 
448
513
                # CONFIG_VERSION
449
514
                if ($var eq 'config_version') {
450
515
                        if (defined($value)) {
461
526
                                config_err($file_line_num, "$line - config_version not defined!");
462
527
                                next;
463
528
                        }
464
 
                } 
 
529
                }
465
530
                
466
531
                # SNAPSHOT_ROOT
467
532
                if ($var eq 'snapshot_root') {
468
533
                        # make sure this is a full path
469
534
                        if (0 == is_valid_local_abs_path($value)) {
470
 
                                config_err($file_line_num, "$line - snapshot_root must be a full path");
 
535
                                if (is_ssh_path($value) || is_anon_rsync_path($value) || is_cwrsync_path($value)) {
 
536
                                        config_err($file_line_num, "$line - snapshot_root must be a local path - you cannot have a remote snapshot_root");
 
537
                                } else {
 
538
                                        config_err($file_line_num, "$line - snapshot_root must be a full path");
 
539
                                }
471
540
                                next;
472
541
                        # if the snapshot root already exists:
473
542
                        } elsif ( -e "$value" ) {
496
565
                        next;
497
566
                }
498
567
                
 
568
                # SYNC_FIRST
 
569
                # if this is enabled, rsnapshot syncs data to a staging directory with the "rsnapshot sync" command,
 
570
                # and all "interval" runs will simply rotate files. this changes the behaviour of the lowest interval.
 
571
                # when a sync occurs, no directories are rotated. the sync directory is kind of like a staging area for data transfers.
 
572
                # the files in the sync directory will be hard linked with the others in the other snapshot directories.
 
573
                # the sync directory lives at: /<snapshot_root>/.sync/
 
574
                #
 
575
                if ($var eq 'sync_first') {
 
576
                        if (defined($value)) {
 
577
                                if ('1' eq $value) {
 
578
                                        $config_vars{'sync_first'} = 1;
 
579
                                        $line_syntax_ok = 1;
 
580
                                        next;
 
581
                                } elsif ('0' eq $value) {
 
582
                                        $config_vars{'sync_first'} = 0;
 
583
                                        $line_syntax_ok = 1;
 
584
                                        next;
 
585
                                } else {
 
586
                                        config_err($file_line_num, "$line - sync_first must be set to either 1 or 0");
 
587
                                        next;
 
588
                                }
 
589
                        }
 
590
                }
 
591
                
499
592
                # NO_CREATE_ROOT
500
593
                if ($var eq 'no_create_root') {
501
594
                        if (defined($value)) {
586
679
                        }
587
680
                }
588
681
                
 
682
                # CHECK FOR cmd_preexec (optional)
 
683
                if ($var eq 'cmd_preexec') {
 
684
                        my $full_script = $value;       # backup script to run (including args)
 
685
                        my $script;                                     # script file (no args)
 
686
                        my @script_argv;                        # all script arguments
 
687
                        
 
688
                        # get the base name of the script, not counting any arguments to it
 
689
                        @script_argv = split(/\s+/, $full_script);
 
690
                        $script = $script_argv[0];
 
691
                        
 
692
                        # make sure script exists and is executable
 
693
                        if (((! -f "$script") or (! -x "$script")) or !is_real_local_abs_path($script)) {
 
694
                                config_err($file_line_num, "$line - cmd_preexec \"$script\" is not executable or does not exist");
 
695
                                next;
 
696
                        }
 
697
                        
 
698
                        $config_vars{'cmd_preexec'} = $full_script;
 
699
                        
 
700
                        $line_syntax_ok = 1;
 
701
                        next;
 
702
                }
 
703
                
 
704
                # CHECK FOR cmd_postexec (optional)
 
705
                if ($var eq 'cmd_postexec') {
 
706
                        my $full_script = $value;       # backup script to run (including args)
 
707
                        my $script;                                     # script file (no args)
 
708
                        my @script_argv;                        # all script arguments
 
709
                        
 
710
                        # get the base name of the script, not counting any arguments to it
 
711
                        @script_argv = split(/\s+/, $full_script);
 
712
                        $script = $script_argv[0];
 
713
                        
 
714
                        # make sure script exists and is executable
 
715
                        if (((! -f "$script") or (! -x "$script")) or !is_real_local_abs_path($script)) {
 
716
                                config_err($file_line_num, "$line - cmd_postexec \"$script\" is not executable or does not exist");
 
717
                                next;
 
718
                        }
 
719
                        
 
720
                        $config_vars{'cmd_postexec'} = $full_script;
 
721
                        
 
722
                        $line_syntax_ok = 1;
 
723
                        next;
 
724
                }
 
725
                
 
726
                # CHECK FOR rsnapshot-diff (optional)
 
727
                if ($var eq 'cmd_rsnapshot_diff') {
 
728
                        if ((-f "$value") && (-x "$value") && (1 == is_real_local_abs_path($value))) {
 
729
                                $config_vars{'cmd_rsnapshot_diff'} = $value;
 
730
                                $line_syntax_ok = 1;
 
731
                                next;
 
732
                        } else {
 
733
                                config_err($file_line_num, "$line - $value is not executable");
 
734
                                next;
 
735
                        }
 
736
                }
 
737
                
589
738
                # INTERVALS
590
739
                if ($var eq 'interval') {
591
740
                        # check if interval is blank
691
840
                        } elsif ( is_anon_rsync_path($src) ) {
692
841
                                $line_syntax_ok = 1;
693
842
                                
 
843
                        # check for cwrsync
 
844
                        } elsif ( is_cwrsync_path($src) ) {
 
845
                                $line_syntax_ok = 1;
 
846
                                
694
847
                        # fear the unknown
695
848
                        } else {
696
849
                                config_err($file_line_num, "$line - Source directory \"$src\" doesn't exist");
749
902
                                        while (my $node = readdir(SRC)) {
750
903
                                                next if ($node =~ m/^\.\.?$/o); # skip '.' and '..'
751
904
                                                
 
905
                                                # avoid double slashes from root filesystem
 
906
                                                if ($src eq '/') {
 
907
                                                        $src = '';
 
908
                                                }
 
909
                                                
 
910
                                                # if this directory is in the snapshot_root, skip it
 
911
                                                # otherwise, back it up
 
912
                                                #
752
913
                                                if ("$config_vars{'snapshot_root'}" !~ m/^$src\/$node/) {
753
914
                                                        my %hash;
754
915
                                                        
755
 
                                                        # avoid double slashes from root filesystem
756
 
                                                        if ($src eq '/')    {
757
 
                                                                $hash{'src'}    = "/$node";
758
 
                                                        } else  {
759
 
                                                                $hash{'src'}    = "$src/$node";
760
 
                                                        }
761
 
                                                        
 
916
                                                        $hash{'src'}    = "$src/$node";
762
917
                                                        $hash{'dest'}   = "$dest/$node";
763
918
                                                        
764
919
                                                        if (defined($opts_ref)) {
823
978
                        my $dest                = $value2;      # dest directory
824
979
                        my %hash;                                       # tmp hash to stick in the backup points array
825
980
                        my $script;                                     # script file (no args)
826
 
                        my @script_argv;                        # tmp spot to help us separate the script from the args
 
981
                        my @script_argv;                        # tmp array to help us separate the script from the args
827
982
                        
828
983
                        if ( !defined($config_vars{'snapshot_root'}) ) {
829
984
                                config_err($file_line_num, "$line - snapshot_root needs to be defined before backup scripts");
830
985
                                next;
831
986
                        }
832
987
                        
 
988
                        if (!defined($dest)) {
 
989
                                config_err($file_line_num, "$line - no destination path specified");
 
990
                                next;
 
991
                        }
 
992
                        
833
993
                        # get the base name of the script, not counting any arguments to it
834
994
                        @script_argv = split(/\s+/, $full_script);
835
995
                        $script = $script_argv[0];
836
996
                        
837
 
                        # make sure the script is a full path
 
997
                        # make sure the destination is a full path
838
998
                        if (1 == is_valid_local_abs_path($dest)) {
839
999
                                config_err($file_line_num, "$line - Backup destination $dest must be a local, relative path");
840
1000
                                next;
846
1006
                                next;
847
1007
                        }
848
1008
                        
849
 
                        # validate destination path
850
 
                        if ( is_valid_local_abs_path($dest) ) {
851
 
                                config_err($file_line_num, "$line - Full paths not allowed for backup destinations");
852
 
                                next;
853
 
                        }
854
 
                        
855
1009
                        # make sure script exists and is executable
856
 
                        if ((! -f "$script") or (! -x "$script") && is_real_local_abs_path($script)) {
 
1010
                        if (((! -f "$script") or (! -x "$script")) or !is_real_local_abs_path($script)) {
857
1011
                                config_err($file_line_num, "$line - Backup script \"$script\" is not executable or does not exist");
858
1012
                                next;
859
1013
                        }
1059
1213
                                next;
1060
1214
                        }
1061
1215
                }
1062
 
                
 
1216
                # USE LAZY DELETES
 
1217
                if ($var eq 'use_lazy_deletes') {
 
1218
                        if (!defined($value)) {
 
1219
                                config_err($file_line_num, "$line - use_lazy_deletes can not be blank");
 
1220
                                next;
 
1221
                        }
 
1222
                        if (!is_boolean($value)) {
 
1223
                                config_err(
 
1224
                                        $file_line_num, "$line - \"$value\" is not a legal value for use_lazy_deletes, must be 0 or 1 only"
 
1225
                                );
 
1226
                                next;
 
1227
                        }
 
1228
                        
 
1229
                        if (1 == $value) { $use_lazy_deletes = 1; }
 
1230
                        $line_syntax_ok = 1;
 
1231
                        next;
 
1232
                }
 
1233
                                
1063
1234
                # make sure we understood this line
1064
1235
                # if not, warn the user, and prevent the program from executing
1065
1236
                # however, don't bother if the user has already been notified
1070
1241
                        }
1071
1242
                }
1072
1243
        }
1073
 
        close(CONFIG) or print_warn("Could not close $config_file", 2);
 
1244
        # close(CONFIG) or print_warn("Could not close $config_file", 2);
1074
1245
        
1075
1246
        ####################################################################
1076
1247
        # SET SOME SENSIBLE DEFAULTS FOR VALUES THAT MAY NOT HAVE BEEN SET #
1131
1302
        # (things that should be in the config file that aren't)
1132
1303
        #
1133
1304
        # make sure config_version was set
1134
 
        #if (!defined($config_vars{'config_version'})) {
1135
 
        #       print_err ("config_version was not defined. rsnapshot can not continue.", 1);
1136
 
        #       syslog_err("config_version was not defined. rsnapshot can not continue.");
1137
 
        #       exit(1);
1138
 
        #}
 
1305
        if (!defined($config_vars{'config_version'})) {
 
1306
                print_err ("config_version was not defined. rsnapshot can not continue.", 1);
 
1307
                syslog_err("config_version was not defined. rsnapshot can not continue.");
 
1308
                exit(1);
 
1309
        }
1139
1310
        # make sure rsync was defined
1140
1311
        if (!defined($config_vars{'cmd_rsync'})) {
1141
1312
                print_err ("cmd_rsync was not defined.", 1);
1179
1350
        if (defined($config_vars{'no_create_root'})) {
1180
1351
                if (1 == $config_vars{'no_create_root'}) {
1181
1352
                        if ( ! -d "$config_vars{'snapshot_root'}" ) {
 
1353
                                if ( -e "$config_vars{'snapshot_root'}" ) {
 
1354
                                        print_err ("$config_vars{'snapshot_root'} is not a directory.", 1);
 
1355
                                } else {
 
1356
                                        print_err ("$config_vars{'snapshot_root'} does not exist.", 1);
 
1357
                                }
1182
1358
                                print_err ("rsnapshot refuses to create snapshot_root when no_create_root is enabled", 1);
1183
1359
                                syslog_err("rsnapshot refuses to create snapshot_root when no_create_root is enabled");
1184
1360
                                exit(1);
1185
1361
                        }
1186
1362
                }
1187
1363
        }
 
1364
        # make sure that the user didn't call "sync" if sync_first isn't enabled
 
1365
        if (($cmd eq 'sync') && (! $config_vars{'sync_first'})) {
 
1366
                print_err ("\"sync_first\" must be enabled for \"sync\" to work", 1);
 
1367
                syslog_err("\"sync_first\" must be enabled for \"sync\" to work");
 
1368
                exit(1);
 
1369
        }
1188
1370
        # make sure that the backup_script destination paths don't nuke data copied over for backup points
1189
1371
        {
1190
1372
                my @backup_dest                 = ();
1222
1404
                                        my $tmp_bs = $bs_dest;
1223
1405
                                        
1224
1406
                                        # add trailing slashes back in so similarly named directories don't match
1225
 
                                        # i.e. localhost/abc/ and localhost/ab/
 
1407
                                        # e.g., localhost/abc/ and localhost/ab/
1226
1408
                                        $tmp_b  .= '/';
1227
1409
                                        $tmp_bs .= '/';
1228
1410
                                        
1252
1434
                                        my $path2 = $backup_script_dest[$j];
1253
1435
                                        
1254
1436
                                        # add trailing slashes back in so similarly named directories don't match
1255
 
                                        # i.e. localhost/abc/ and localhost/ab/
 
1437
                                        # e.g., localhost/abc/ and localhost/ab/
1256
1438
                                        $path1 .= '/';
1257
1439
                                        $path2 .= '/';
1258
1440
                                        
1525
1707
        log_msg($str, 3);
1526
1708
        
1527
1709
        if (!defined($verbose) or ($verbose >= 3)) {
1528
 
                print wrap_cmd($str, 76, 4), "\n";
 
1710
                print wrap_cmd($str), "\n";
1529
1711
        }
1530
1712
}
1531
1713
 
1663
1845
                if ((!defined($verbose)) or ($level <= $verbose)) {
1664
1846
                        print STDERR "----------------------------------------------------------------------------\n";
1665
1847
                        print STDERR "rsnapshot encountered an error! The program was invoked with these options:\n";
1666
 
                        print STDERR wrap_cmd($run_string, 76, 4), "\n";
 
1848
                        print STDERR wrap_cmd($run_string), "\n";
1667
1849
                        print STDERR "----------------------------------------------------------------------------\n";
1668
1850
                }
1669
1851
                
1768
1950
        
1769
1951
        if (!defined($msg))                     { return (undef); }
1770
1952
        if (!defined($facility))        { $facility     = 'user'; }
1771
 
        if (!defined($level))           { $level        = 'notice'; }
 
1953
        if (!defined($level))           { $level        = 'info'; }
1772
1954
        
1773
1955
        if (defined($config_vars{'cmd_logger'})) {
1774
1956
                # print out our call to syslog
1910
2092
        print_cmd("echo $$ > $lockfile");
1911
2093
        
1912
2094
        if (0 == $test) {
1913
 
                my $result = open(LOCKFILE, "> $lockfile");
 
2095
                # sysopen() is atomic, whereas open() is not
 
2096
                my $result = sysopen(LOCKFILE, $lockfile, O_WRONLY | O_EXCL | O_CREAT);
1914
2097
                if (!defined($result)) {
1915
2098
                        print_err ("Could not write lockfile $lockfile", 1);
1916
2099
                        syslog_err("Could not write lockfile $lockfile");
2026
2209
        my $interval_max;
2027
2210
        
2028
2211
        # this is the name of the previous interval, in relation to the one we're
2029
 
        # working on. i.e. if we're operating on weekly, this should be "daily"
 
2212
        # working on. e.g., if we're operating on weekly, this should be "daily"
2030
2213
        my $prev_interval;
2031
2214
        
2032
2215
        # same as $interval_max, except for the previous interval.
2033
2216
        # this is used to determine which of the previous snapshots to pull from
2034
 
        # i.e. cp -al hourly.$prev_interval_max/ daily.0/
 
2217
        # e.g., cp -al hourly.$prev_interval_max/ daily.0/
2035
2218
        my $prev_interval_max;
2036
2219
        
2037
2220
        # FIGURE OUT WHICH INTERVAL WE'RE RUNNING, AND HOW IT RELATES TO THE OTHERS
2049
2232
                        
2050
2233
                        # how many of these intervals should we keep?
2051
2234
                        # we start counting from 0, so subtract one
2052
 
                        # i.e. 6 intervals == interval.0 .. interval.5
 
2235
                        # e.g., 6 intervals == interval.0 .. interval.5
2053
2236
                        $interval_max = $$i_ref{'number'} - 1;
2054
2237
                        
2055
2238
                        # we found our interval, exit the foreach loop
2066
2249
                
2067
2250
                # which of the previous interval's numbered directories should we pull from
2068
2251
                # for the interval we're currently set to run?
2069
 
                # i.e. daily.0/ might get pulled from hourly.6/
 
2252
                # e.g., daily.0/ might get pulled from hourly.6/
2070
2253
                #
2071
2254
                $prev_interval_max = $$i_ref{'number'} - 1;
2072
2255
                
2074
2257
        }
2075
2258
        
2076
2259
        # make sure we got something that makes sense
2077
 
        if (!defined($interval_num)) { bail("Interval \"$cur_interval\" unknown, check $config_file"); }
 
2260
        if ($cur_interval ne 'sync') {
 
2261
                if (!defined($interval_num)) { bail("Interval \"$cur_interval\" unknown, check $config_file"); }
 
2262
        }
2078
2263
        
2079
2264
        # populate our hash
2080
2265
        $hash{'interval'}                       = $cur_interval;
2087
2272
        return (\%hash);
2088
2273
}
2089
2274
 
 
2275
# accepts no arguments
 
2276
# prints the most recent snapshot directory and exits
 
2277
# this is for use with the get-latest-snapshot command line argument
 
2278
sub show_latest_snapshot {
 
2279
        # this should only be called after parse_config_file(), but just in case...
 
2280
        if (! @intervals)       { bail("Error! intervals not defined in show_latest_snapshot()"); }
 
2281
        if (! %config_vars) { bail("Error! config_vars not defined in show_latest_snapshot()"); }
 
2282
        
 
2283
        # regardless of .sync, this is the latest "real" snapshot
 
2284
        print $config_vars{'snapshot_root'} . '/' . $intervals[0]->{'interval'} . '.0/' . "\n";
 
2285
        
 
2286
        exit(0);
 
2287
}
 
2288
 
2090
2289
# accepts no args
2091
2290
# prints out status to the logs, then exits the program with the current exit code
2092
2291
sub exit_with_status {
2116
2315
# accepts no arguments
2117
2316
# returns nothing
2118
2317
#
2119
 
# exits the program with the status of the config file (i.e. Syntax OK).
 
2318
# exits the program with the status of the config file (e.g., Syntax OK).
2120
2319
# the exit code is 0 for success, 1 for failure (although failure should never happen)
2121
2320
sub exit_configtest {
2122
2321
        # if we're just doing a configtest, exit here with the results
2144
2343
        }
2145
2344
        
2146
2345
        # if we have the default config from the install, remind the user to create the real config
2147
 
        if (-e "$config_file.default") {
 
2346
        if ((-e "$config_file.default") && (! -e "$config_file")) {
2148
2347
                print STDERR "Did you copy $config_file.default to $config_file yet?\n";
2149
2348
        }
2150
2349
        
2209
2408
# returns 1 if it's a valid ssh absolute path
2210
2409
# returns 0 otherwise
2211
2410
sub is_ssh_path {
2212
 
        my $path        = shift(@_);
 
2411
        my $path = shift(@_);
2213
2412
        
2214
2413
        if (!defined($path))                            { return (undef); }
2215
2414
        
2217
2416
        if ($path =~ m/^\s/)                            { return (undef); }
2218
2417
        if ($path =~ m/\s$/)                            { return (undef); }
2219
2418
        
2220
 
        # must have user@host:/path syntax
2221
 
        if ($path =~ m/^.*?\@.*?:\/.*$/)        { return (1); }
 
2419
        # must have user@host:[~]/path syntax for ssh
 
2420
        if ($path =~ m/^.*?\@.*?:~?\/.*$/)      { return (1); }
 
2421
        
 
2422
        return (0);
 
2423
}
 
2424
 
 
2425
# accepts path
 
2426
# returns 1 if it's a valid cwrsync server path (user@host::sharename)
 
2427
# return 0 otherwise
 
2428
sub is_cwrsync_path {
 
2429
        my $path = shift(@_);
 
2430
        if (!defined($path))            { return (undef); }
 
2431
        if ($path =~ m/^[^\/]+::/)      { return (1); }
2222
2432
        
2223
2433
        return (0);
2224
2434
}
2227
2437
# returns 1 if it's a syntactically valid anonymous rsync path
2228
2438
# returns 0 otherwise
2229
2439
sub is_anon_rsync_path {
2230
 
        my $path        = shift(@_);
 
2440
        my $path = shift(@_);
2231
2441
        
2232
2442
        if (!defined($path))                    { return (undef); }
2233
2443
        if ($path =~ m/^rsync:\/\/.*$/) { return (1); }
2339
2549
        
2340
2550
        # it's not a trailing slash if it's the root filesystem
2341
2551
        if ($str eq '/') { return ($str); }
 
2552
        # it's not a trailing slash if it's a remote root filesystem
 
2553
        if ($str =~ m%:/$% ) { return ($str); }
2342
2554
        
2343
2555
        $str =~ s/\/+$//;
2344
2556
        
2345
2557
        return ($str);
2346
2558
}
2347
2559
 
2348
 
# accepts an interval_data_ref
 
2560
# accepts string
 
2561
# returns /. if passed /, returns input otherwise
 
2562
# this is to work around a bug in some versions of rsync
 
2563
sub add_slashdot_if_root {
 
2564
        my $str = shift(@_);
 
2565
        
 
2566
        if ($str eq '/') {
 
2567
                return '/.';
 
2568
        }
 
2569
        
 
2570
        return ($str);
 
2571
}
 
2572
 
 
2573
# accepts the interval (cmd) to run against
2349
2574
# returns nothing
2350
2575
# calls the appropriate subroutine, depending on whether this is the lowest interval or a higher one
 
2576
# also calls preexec/postexec scripts if we're working on the lowest interval
2351
2577
#
2352
2578
sub handle_interval {
2353
 
        my $id_ref = shift(@_);
2354
 
        
2355
 
        if (!defined($id_ref)) { bail('id_ref not defined in handle_interval()'); }
2356
 
        
2357
 
        if (0 == $$id_ref{'interval_num'}) {
2358
 
                # if this is the most frequent interval, actually do the backups here
2359
 
                backup_lowest_interval( $id_ref );
2360
 
                
 
2579
        my $cmd = shift(@_);
 
2580
        
 
2581
        if (!defined($cmd)) { bail('cmd not defined in handle_interval()'); }
 
2582
        
 
2583
        my $id_ref = get_interval_data( $cmd );
 
2584
        
 
2585
        my $result = 0;
 
2586
        
 
2587
        # make sure we don't have any leftover interval.delete directories
 
2588
        # if so, loop through and delete them
 
2589
        foreach my $interval_ref (@intervals) {
 
2590
                my $interval = $$interval_ref{'interval'};
 
2591
                
 
2592
                my $is_file = 0;
 
2593
                my $exists = 0;
 
2594
                
 
2595
                # double check that the node and snapshot_root are not the same directory
 
2596
                # it would be very bad to accidentally delete the snapshot root!
 
2597
                # first test for symlinks (should never be here)
 
2598
                if ( -l "$config_vars{'snapshot_root'}/$interval.delete" ) {
 
2599
                        $exists = 1;
 
2600
                        $is_file = 1;
 
2601
                        
 
2602
                # file (should never be here)
 
2603
                } elsif ( -f "$config_vars{'snapshot_root'}/$interval.delete" ) {
 
2604
                        $exists = 1;
 
2605
                        $is_file = 1;
 
2606
                        
 
2607
                # directory (this is what we're expecting)
 
2608
                } elsif ( -d "$config_vars{'snapshot_root'}/$interval.delete" ) {
 
2609
                        $exists = 1;
 
2610
                        $is_file = 0;
 
2611
                        
 
2612
                # exists, but is something else
 
2613
                } elsif ( -e "$config_vars{'snapshot_root'}/$interval.delete" ) {
 
2614
                        bail("Invalid file type for \"$config_vars{'snapshot_root'}/$interval.delete\" in handle_interval()\n");
 
2615
                }
 
2616
                
 
2617
                # we don't use if (-e $dir), because that fails for invalid symlinks
 
2618
                if (1 == $exists) {
 
2619
                        # if we found any leftover directories, delete them now before they pile up and cause problems
 
2620
                        # this is a directory
 
2621
                        if (0 == $is_file) {
 
2622
                                display_rm_rf("$config_vars{'snapshot_root'}/$interval.delete/");
 
2623
                                if (0 == $test) {
 
2624
                                        $result = rm_rf( "$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/" );
 
2625
                                        if (0 == $result) {
 
2626
                                                bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.delete/\")");
 
2627
                                        }               
 
2628
                                }               
 
2629
                                
 
2630
                        # this is a file or symlink
 
2631
                        } else {
 
2632
                                print_cmd("rm -f $config_vars{'snapshot_root'}/$interval.delete");
 
2633
                                if (0 == $test) {
 
2634
                                        $result = unlink("$config_vars{'snapshot_root'}/$interval.delete");
 
2635
                                        if (0 == $result) {
 
2636
                                                bail("Could not remove \"$config_vars{'snapshot_root'}/$interval.delete\" in handle_interval()");
 
2637
                                        }
 
2638
                                }
 
2639
                        }
 
2640
                }
 
2641
        }
 
2642
        
 
2643
        # handle toggling between sync_first being enabled and disabled
 
2644
        
 
2645
        # link_dest is enabled
 
2646
        if (1 == $link_dest) {
 
2647
                
 
2648
                # sync_first is enabled
 
2649
                if ($config_vars{'sync_first'}) {
 
2650
                        
 
2651
                        # create the sync root if it doesn't exist (and we need it right now)
 
2652
                        if ($cmd eq 'sync') {
 
2653
                                # don't create tye .sync directory, it gets created later on
 
2654
                        }
 
2655
                                
 
2656
                # sync_first is disabled
 
2657
                } else {
 
2658
                        # if the sync directory is still here after sync_first is disabled, delete it
 
2659
                        if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
 
2660
                                
 
2661
                                display_rm_rf("$config_vars{'snapshot_root'}/.sync/");
 
2662
                                if (0 == $test) {
 
2663
                                        $result = rm_rf( "$config_vars{'snapshot_root'}/.sync/" );
 
2664
                                        if (0 == $result) {
 
2665
                                                bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/.sync/\")");
 
2666
                                        }
 
2667
                                }
 
2668
                        }
 
2669
                }
 
2670
                
 
2671
        # link_dest is disabled
 
2672
        } else {
 
2673
                
 
2674
                # sync_first is enabled
 
2675
                if ($config_vars{'sync_first'}) {
 
2676
                        # create the sync root if it doesn't exist
 
2677
                        if ( ! -d "$config_vars{'snapshot_root'}/.sync" ) {
 
2678
                                
 
2679
                                # cp_al() will create the directory for us
 
2680
                                
 
2681
                                # call generic cp_al() subroutine
 
2682
                                my $interval_0  = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0";
 
2683
                                my $sync_dir    = "$config_vars{'snapshot_root'}/.sync";
 
2684
                                
 
2685
                                display_cp_al( "$interval_0", "$sync_dir" );
 
2686
                                if (0 == $test) {
 
2687
                                        $result = cp_al( "$interval_0", "$sync_dir" );
 
2688
                                        if (! $result) {
 
2689
                                                bail("Error! cp_al(\"$interval_0\", \"$sync_dir\")");
 
2690
                                        }
 
2691
                                }
 
2692
                        }
 
2693
                        
 
2694
                # sync_first is disabled
 
2695
                } else {
 
2696
                        # if the sync directory still exists, delete it
 
2697
                        if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
 
2698
                                display_rm_rf("$config_vars{'snapshot_root'}/.sync/");
 
2699
                                if (0 == $test) {
 
2700
                                        $result = rm_rf( "$config_vars{'snapshot_root'}/.sync/" );
 
2701
                                        if (0 == $result) {
 
2702
                                                bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/.sync/\")");
 
2703
                                        }
 
2704
                                }
 
2705
                        }
 
2706
                }
 
2707
        }
 
2708
        
 
2709
        #
 
2710
        # now that the preliminaries are out of the way, the main backups happen here
 
2711
        #
 
2712
        
 
2713
        # backup the lowest interval (or sync content to staging area)
 
2714
        # we're not sure yet going in whether we'll be doing an actual backup, or just rotating snapshots for the lowest interval
 
2715
        if ((defined($$id_ref{'interval_num'}) && (0 == $$id_ref{'interval_num'})) or ($cmd eq 'sync')) {
 
2716
                # if we're doing a sync, run the pre/post exec scripts, and do the backup
 
2717
                if ($cmd eq 'sync') {
 
2718
                        exec_cmd_preexec();
 
2719
                        backup_lowest_interval( $id_ref );
 
2720
                        exec_cmd_postexec();
 
2721
                        
 
2722
                # if we're working on the lowest interval, either run the backup and rotate the snapshots, or just rotate them
 
2723
                # (depending on whether sync_first is enabled
 
2724
                } else {
 
2725
                        if ($config_vars{'sync_first'}) {
 
2726
                                rotate_lowest_snapshots( $$id_ref{'interval'} );
 
2727
                        } else {
 
2728
                                exec_cmd_preexec();
 
2729
                                rotate_lowest_snapshots( $$id_ref{'interval'} );
 
2730
                                backup_lowest_interval( $id_ref );
 
2731
                                exec_cmd_postexec();
 
2732
                        }
 
2733
                }
 
2734
                
 
2735
        # just rotate the higher intervals
2361
2736
        } else {
2362
2737
                # this is not the most frequent unit, just rotate
2363
2738
                rotate_higher_interval( $id_ref );
2364
2739
        }
 
2740
        
 
2741
        # if use_lazy_delete is on, delete the interval.delete directory
 
2742
        # we just check for the directory, it will have been created or not depending on the value of use_lazy_delete
 
2743
        if ( -d "$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete" ) {
 
2744
                # this is the last thing to do here, and it can take quite a while.
 
2745
                # we remove the lockfile here since this delete shouldn't block other rsnapshot jobs from running
 
2746
                remove_lockfile();
 
2747
                
 
2748
                # start the delete
 
2749
                display_rm_rf("$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/");
 
2750
                if (0 == $test) {
 
2751
                        my $result = rm_rf( "$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/" );
 
2752
                        if (0 == $result) {
 
2753
                                bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/\")\n");
 
2754
                        }
 
2755
                }
 
2756
        }
2365
2757
}
2366
2758
 
2367
2759
# accepts an interval_data_ref
2368
 
# acts on the interval defined as $$id_ref{'interval'} (i.e. hourly)
2369
 
# this should be the smallest interval (i.e. hourly, not daily)
 
2760
# acts on the interval defined as $$id_ref{'interval'} (e.g., hourly)
 
2761
# this should be the smallest interval (e.g., hourly, not daily)
2370
2762
#
2371
2763
# rotates older dirs within this interval, hard links .0 to .1,
2372
2764
# and rsync data over to .0
2376
2768
        my $id_ref = shift(@_);
2377
2769
        
2378
2770
        # this should never happen
2379
 
        if (!defined($id_ref)) { bail('backup_lowest_interval() expects an argument'); }
 
2771
        if (!defined($id_ref))                          { bail('backup_lowest_interval() expects an argument'); }
 
2772
        if (!defined($$id_ref{'interval'}))     { bail('backup_lowest_interval() expects an interval'); }
2380
2773
        
2381
2774
        # this also should never happen
2382
 
        if (!defined($$id_ref{'interval_num'}) or (0 != $$id_ref{'interval_num'})) {
2383
 
                bail('backup_lowest_interval() can only operate on the lowest interval');
2384
 
        }
2385
 
        
2386
 
        # rotate the higher directories in this interval
2387
 
        #
2388
 
        rotate_lowest_snapshots( $$id_ref{'interval'} );
2389
 
        
2390
 
        # sync live filesystem data to $interval.0
 
2775
        if ($$id_ref{'interval'} ne 'sync') {
 
2776
                if (!defined($$id_ref{'interval_num'}) or (0 != $$id_ref{'interval_num'})) {
 
2777
                        bail('backup_lowest_interval() can only operate on the lowest interval');
 
2778
                }
 
2779
        }
 
2780
        
 
2781
        my $sync_dest_matches   = 0;
 
2782
        my $sync_dest_dir               = undef;
 
2783
        
 
2784
        # if we're trying to sync only certain directories, remember the path to match
 
2785
        if ($ARGV[1]) {
 
2786
                $sync_dest_dir = $ARGV[1];
 
2787
        }
 
2788
        
 
2789
        # sync live filesystem data and backup script output to $interval.0
2391
2790
        # loop through each backup point and backup script
2392
2791
        foreach my $bp_ref (@backup_points) {
2393
2792
                
2394
2793
                # rsync the given backup point into the snapshot root
2395
 
                if ($$bp_ref{'src'}) {
2396
 
                        rsync_backup_point( $$id_ref{'interval'}, $bp_ref );
 
2794
                if ( defined($$bp_ref{'dest'}) && (defined($$bp_ref{'src'}) or defined($$bp_ref{'script'})) ) {
2397
2795
                        
2398
 
                # run the backup script
2399
 
                } elsif ($$bp_ref{'script'}) {
2400
 
                        exec_backup_script( $$id_ref{'interval'}, $bp_ref );
 
2796
                        # if we're doing a sync and we specified an parameter on the command line (for the destination path),
 
2797
                        # only sync directories matching the destination path
 
2798
                        if (($$id_ref{'interval'} eq 'sync') && (defined($sync_dest_dir))) {
 
2799
                                my $avail_path  = remove_trailing_slash( $$bp_ref{'dest'} );
 
2800
                                my $req_path    = remove_trailing_slash( $sync_dest_dir );
 
2801
                                
 
2802
                                # if we have a match, sync this entry
 
2803
                                if ($avail_path eq $req_path) {
 
2804
                                        # rsync
 
2805
                                        if ($$bp_ref{'src'}) {
 
2806
                                                rsync_backup_point( $$id_ref{'interval'}, $bp_ref );
 
2807
                                                
 
2808
                                        # backup_script
 
2809
                                        } elsif ($$bp_ref{'script'}) {
 
2810
                                                exec_backup_script( $$id_ref{'interval'}, $bp_ref );
 
2811
                                        }
 
2812
                                        
 
2813
                                        # ok, we got at least one dest match
 
2814
                                        $sync_dest_matches++;
 
2815
                                }
 
2816
                                
 
2817
                        # this is a normal operation, either a sync or a lowest interval sync/rotate
 
2818
                        } else {
 
2819
                                # rsync
 
2820
                                if ($$bp_ref{'src'}) {
 
2821
                                        rsync_backup_point( $$id_ref{'interval'}, $bp_ref );
 
2822
                                        
 
2823
                                # backup_script
 
2824
                                } elsif ($$bp_ref{'script'}) {
 
2825
                                        exec_backup_script( $$id_ref{'interval'}, $bp_ref );
 
2826
                                }
 
2827
                        }
2401
2828
                        
2402
2829
                # this should never happen
2403
2830
                } else {
2404
2831
                        bail('invalid backup point data in backup_lowest_interval()');
2405
2832
                }
2406
 
                
 
2833
        }
 
2834
        
 
2835
        if ($$id_ref{'interval'} eq 'sync') {
 
2836
                if (defined($sync_dest_dir) && (0 == $sync_dest_matches)) {
 
2837
                        bail ("No matches found for \"$sync_dest_dir\"");
 
2838
                }
2407
2839
        }
2408
2840
        
2409
2841
        # rollback failed backups
2410
2842
        rollback_failed_backups( $$id_ref{'interval'} );
2411
2843
        
2412
2844
        # update mtime on $interval.0/ to show when the snapshot completed
2413
 
        touch_interval_0( $$id_ref{'interval'} );
 
2845
        touch_interval_dir( $$id_ref{'interval'} );
2414
2846
}
2415
2847
 
2416
2848
# accepts $interval
2434
2866
        my $prev_interval = $$id_ref{'prev_interval'};
2435
2867
        my $prev_interval_max = $$id_ref{'prev_interval_max'};
2436
2868
        
 
2869
        my $result;
 
2870
        
2437
2871
        # remove oldest directory
2438
2872
        if ( (-d "$config_vars{'snapshot_root'}/$interval.$interval_max") && ($interval_max > 0) ) {
2439
 
                display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
2440
 
                if (0 == $test) {
2441
 
                        my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
2442
 
                        if (0 == $result) {
2443
 
                                bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\")\n");
 
2873
                # if use_lazy_deletes is set move the oldest directory to interval.delete
 
2874
                if (1 == $use_lazy_deletes) {
 
2875
                        print_cmd("mv",
 
2876
                                "$config_vars{'snapshot_root'}/$interval.$interval_max/",
 
2877
                                "$config_vars{'snapshot_root'}/$interval.delete/"
 
2878
                        );
 
2879
                        
 
2880
                        if (0 == $test) {
 
2881
                                my $result = safe_rename(
 
2882
                                        "$config_vars{'snapshot_root'}/$interval.$interval_max",
 
2883
                                        "$config_vars{'snapshot_root'}/$interval.delete"
 
2884
                                );
 
2885
                                if (0 == $result) {
 
2886
                                        my $errstr = '';
 
2887
                                        $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\", \"";
 
2888
                                        $errstr .= "$config_vars{'snapshot_root'}/$interval.delete/\")";
 
2889
                                        bail($errstr);
 
2890
                                }                               
 
2891
                        }                               
 
2892
                        
 
2893
                # otherwise the default is to delete the oldest directory for this interval
 
2894
                } else {
 
2895
                        display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
 
2896
                        
 
2897
                        if (0 == $test) {
 
2898
                                my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
 
2899
                                if (0 == $result) {
 
2900
                                        bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\")\n");
 
2901
                                }
2444
2902
                        }
2445
2903
                }
2446
2904
        }
2449
2907
        if ($interval_max > 0) {
2450
2908
                for (my $i=($interval_max-1); $i>0; $i--) {
2451
2909
                        if ( -d "$config_vars{'snapshot_root'}/$interval.$i" ) {
2452
 
                                print_cmd("mv ",
2453
 
                                                        "$config_vars{'snapshot_root'}/$interval.$i/ ",
2454
 
                                                        "$config_vars{'snapshot_root'}/$interval." . ($i+1) . "/");
 
2910
                                print_cmd("mv",
 
2911
                                        "$config_vars{'snapshot_root'}/$interval.$i/ ",
 
2912
                                        "$config_vars{'snapshot_root'}/$interval." . ($i+1) . "/"
 
2913
                                );
2455
2914
                                
2456
2915
                                if (0 == $test) {
2457
2916
                                        my $result = safe_rename(
2458
 
                                                                        "$config_vars{'snapshot_root'}/$interval.$i",
2459
 
                                                                        ("$config_vars{'snapshot_root'}/$interval." . ($i+1))
 
2917
                                                "$config_vars{'snapshot_root'}/$interval.$i",
 
2918
                                                ("$config_vars{'snapshot_root'}/$interval." . ($i+1))
2460
2919
                                        );
2461
2920
                                        if (0 == $result) {
2462
2921
                                                my $errstr = '';
2469
2928
                }
2470
2929
        }
2471
2930
        
2472
 
        # .0 and .1 require more attention:
2473
 
        if ( (-d "$config_vars{'snapshot_root'}/$interval.0") && ($interval_max > 0) ) {
2474
 
                my $result;
 
2931
        # .0 and .1 require more attention, especially now with link_dest and sync_first
 
2932
        
 
2933
        # sync_first enabled
 
2934
        if ($config_vars{'sync_first'}) {
 
2935
                # we move .0 to .1 no matter what (assuming it exists)
 
2936
                
 
2937
                if ( -d "$config_vars{'snapshot_root'}/$interval.0/" ) {
 
2938
                        print_cmd("mv",
 
2939
                                "$config_vars{'snapshot_root'}/$interval.0/",
 
2940
                                "$config_vars{'snapshot_root'}/$interval.1/"
 
2941
                        );
 
2942
                        
 
2943
                        if (0 == $test) {
 
2944
                                my $result = safe_rename(
 
2945
                                        "$config_vars{'snapshot_root'}/$interval.0",
 
2946
                                        "$config_vars{'snapshot_root'}/$interval.1"
 
2947
                                );
 
2948
                                if (0 == $result) {
 
2949
                                        my $errstr = '';
 
2950
                                        $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", \"";
 
2951
                                        $errstr .= "$config_vars{'snapshot_root'}/$interval.1/\")";
 
2952
                                        bail($errstr);
 
2953
                                }                               
 
2954
                        }                               
 
2955
                }                               
 
2956
                        
 
2957
                # if we're using rsync --link-dest, we need to mv sync to .0 now
 
2958
                if (1 == $link_dest) {
 
2959
                        # mv sync .0
 
2960
                        
 
2961
                        if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
 
2962
                                print_cmd("mv",
 
2963
                                        "$config_vars{'snapshot_root'}/.sync/",
 
2964
                                        "$config_vars{'snapshot_root'}/$interval.0/"
 
2965
                                );
 
2966
                                
 
2967
                                if (0 == $test) {
 
2968
                                        my $result = safe_rename(
 
2969
                                                "$config_vars{'snapshot_root'}/.sync",
 
2970
                                                "$config_vars{'snapshot_root'}/$interval.0"
 
2971
                                        );
 
2972
                                        if (0 == $result) {
 
2973
                                                my $errstr = '';
 
2974
                                                $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/.sync/\", \"";
 
2975
                                                $errstr .= "$config_vars{'snapshot_root'}/$interval.0/\")";
 
2976
                                                bail($errstr);
 
2977
                                        }                               
 
2978
                                }                               
 
2979
                        }       
 
2980
                        
 
2981
                # otherwise, we hard link (except for directories, symlinks, and special files) sync to .0
 
2982
                } else {
 
2983
                        # cp -al .sync .0
 
2984
                        
 
2985
                        if ( -d "$config_vars{'snapshot_root'}/.sync/" ) {
 
2986
                                display_cp_al( "$config_vars{'snapshot_root'}/.sync/", "$config_vars{'snapshot_root'}/$interval.0/" );
 
2987
                                if (0 == $test) {
 
2988
                                        $result = cp_al( "$config_vars{'snapshot_root'}/.sync", "$config_vars{'snapshot_root'}/$interval.0" );
 
2989
                                        if (! $result) {
 
2990
                                                bail("Error! cp_al(\"$config_vars{'snapshot_root'}/.sync\", \"$config_vars{'snapshot_root'}/$interval.0\")");
 
2991
                                        }
 
2992
                                }
 
2993
                        }
 
2994
                }
 
2995
                
 
2996
        # sync_first disabled (make sure we have a .0 directory and someplace to put it)
 
2997
        } elsif ( (-d "$config_vars{'snapshot_root'}/$interval.0") && ($interval_max > 0) ) {
2475
2998
                
2476
2999
                # if we're using rsync --link-dest, we need to mv .0 to .1 now
2477
3000
                if (1 == $link_dest) {
2478
 
                        print_cmd("mv $config_vars{'snapshot_root'}/$interval.0/ $config_vars{'snapshot_root'}/$interval.1/");
2479
 
                        
2480
3001
                        # move .0 to .1
2481
 
                        if (0 == $test) {
2482
 
                                my $result = safe_rename(
2483
 
                                                                "$config_vars{'snapshot_root'}/$interval.0",
2484
 
                                                                "$config_vars{'snapshot_root'}/$interval.1"
2485
 
                                );
2486
 
                                if (0 == $result) {
2487
 
                                        my $errstr = '';
2488
 
                                        $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
2489
 
                                        $errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
2490
 
                                        bail($errstr);
 
3002
                        
 
3003
                        if ( -d "$config_vars{'snapshot_root'}/$interval.0/" ) {
 
3004
                                print_cmd("mv $config_vars{'snapshot_root'}/$interval.0/ $config_vars{'snapshot_root'}/$interval.1/");
 
3005
                                
 
3006
                                if (0 == $test) {
 
3007
                                        my $result = safe_rename(
 
3008
                                                "$config_vars{'snapshot_root'}/$interval.0",
 
3009
                                                "$config_vars{'snapshot_root'}/$interval.1"
 
3010
                                        );
 
3011
                                        if (0 == $result) {
 
3012
                                                my $errstr = '';
 
3013
                                                $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
 
3014
                                                $errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
 
3015
                                                bail($errstr);
 
3016
                                        }
2491
3017
                                }
2492
3018
                        }
2493
3019
                # otherwise, we hard link (except for directories, symlinks, and special files) .0 over to .1
2494
3020
                } else {
2495
3021
                        # call generic cp_al() subroutine
2496
 
                        display_cp_al( "$config_vars{'snapshot_root'}/$interval.0/", "$config_vars{'snapshot_root'}/$interval.1/" );
2497
 
                        
2498
 
                        if (0 == $test) {
2499
 
                                $result = cp_al(
2500
 
                                                        "$config_vars{'snapshot_root'}/$interval.0/",
2501
 
                                                        "$config_vars{'snapshot_root'}/$interval.1/"
2502
 
                                );
2503
 
                                if (! $result) {
2504
 
                                        my $errstr = '';
2505
 
                                        $errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
2506
 
                                        $errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
2507
 
                                        bail($errstr);
 
3022
                        if ( -d "$config_vars{'snapshot_root'}/$interval.0/" ) {
 
3023
                                display_cp_al( "$config_vars{'snapshot_root'}/$interval.0", "$config_vars{'snapshot_root'}/$interval.1" );
 
3024
                                if (0 == $test) {
 
3025
                                        $result = cp_al(
 
3026
                                                "$config_vars{'snapshot_root'}/$interval.0/",
 
3027
                                                "$config_vars{'snapshot_root'}/$interval.1/"
 
3028
                                        );
 
3029
                                        if (! $result) {
 
3030
                                                my $errstr = '';
 
3031
                                                $errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
 
3032
                                                $errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
 
3033
                                                bail($errstr);
 
3034
                                        }
2508
3035
                                }
2509
3036
                        }
2510
3037
                }
2514
3041
# accepts interval, backup_point_ref, ssh_rsync_args_ref
2515
3042
# returns no args
2516
3043
# runs rsync on the given backup point
 
3044
# this is only run on the lowest points, not for rotations
2517
3045
sub rsync_backup_point {
2518
3046
        my $interval    = shift(@_);
2519
3047
        my $bp_ref              = shift(@_);
2534
3062
        my @rsync_long_args_stack       = undef;
2535
3063
        my $src                                         = undef;
2536
3064
        my $result                                      = undef;
 
3065
        my $using_relative                      = 0;
 
3066
        
 
3067
        if (defined($$bp_ref{'src'})) {
 
3068
                $src = remove_trailing_slash( "$$bp_ref{'src'}" );
 
3069
                $src = add_slashdot_if_root( "$src" );
 
3070
        }
 
3071
        
 
3072
        # if we're using link-dest later, that target depends on whether we're doing a 'sync' or a regular interval
 
3073
        # if we're doing a "sync", then look at [lowest-interval].0 instead of [cur-interval].1
 
3074
        my $interval_link_dest;
 
3075
        my $interval_num_link_dest;
 
3076
        
 
3077
        # start looking for link_dest targets at interval.$start_num
 
3078
        my $start_num = 1;
 
3079
        
 
3080
        my $sync_dir_was_present = 0;
 
3081
        
 
3082
        # if we're doing a sync, we'll start looking at [lowest-interval].0 for a link_dest target
 
3083
        if ($interval eq 'sync') {
 
3084
                $start_num = 0;
 
3085
                
 
3086
                # remember now if the .sync directory exists
 
3087
                if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
 
3088
                        $sync_dir_was_present = 1;
 
3089
                }
 
3090
        }
 
3091
        
 
3092
        # look for the most recent link_dest target directory
 
3093
        # loop through all snapshots until we find the first match
 
3094
        foreach my $i_ref (@intervals) {
 
3095
                if (defined($$i_ref{'number'})) {
 
3096
                        for (my $i = $start_num; $i < $$i_ref{'number'}; $i++) {
 
3097
                                
 
3098
                                # once we find a valid link_dest target, the search is over
 
3099
                                if ( -e "$config_vars{'snapshot_root'}/$$i_ref{'interval'}.$i/$$bp_ref{'dest'}" ) {
 
3100
                                        if (!defined($interval_link_dest) && !defined($interval_num_link_dest)) {
 
3101
                                                $interval_link_dest             = $$i_ref{'interval'};
 
3102
                                                $interval_num_link_dest = $i;
 
3103
                                        }
 
3104
                                        
 
3105
                                        # we'll still loop through the outer loop a few more times, but the defined() check above
 
3106
                                        # will make sure the first match wins
 
3107
                                        last;
 
3108
                                }
 
3109
                        }
 
3110
                }
 
3111
        }
2537
3112
        
2538
3113
        # check to see if this destination path has already failed
2539
3114
        # if it's set to be rolled back, skip out now
2543
3118
                        my $tmp_rollback_point  = $rollback_point;
2544
3119
                        
2545
3120
                        # don't compare the slashes at the end
2546
 
                        $tmp_dest                       =~ s/\/+$//o;
2547
 
                        $tmp_rollback_point     =~ s/\/+$//o;
 
3121
                        $tmp_dest                       = remove_trailing_slash($tmp_dest);
 
3122
                        $tmp_rollback_point     = remove_trailing_slash($tmp_rollback_point);
2548
3123
                        
2549
3124
                        if ("$tmp_dest" eq "$tmp_rollback_point") {
2550
 
                                print_warn ("$$bp_ref{'src'} skipped due to rollback plan", 3);
 
3125
                                print_warn ("$$bp_ref{'src'} skipped due to rollback plan", 2);
2551
3126
                                syslog_warn("$$bp_ref{'src'} skipped due to rollback plan");
2552
3127
                                return (undef);
2553
3128
                        }
2569
3144
        if ($verbose > 3) { $rsync_short_args .= 'v'; }
2570
3145
        
2571
3146
        # split up rsync long args into an array
2572
 
        @rsync_long_args_stack  = ( split(/\s+/, $rsync_long_args) );
2573
 
        
2574
 
        # append a trailing slash if src is a directory
2575
 
        if (defined($$bp_ref{'src'})) {
2576
 
                if ((-d "$$bp_ref{'src'}") && ($$bp_ref{'src'} !~ /\/$/)) {
2577
 
                        $$bp_ref{'src'} .= '/';
2578
 
                }
2579
 
        }
2580
 
        
2581
 
        # create $interval.0/$$bp_ref{'dest'} directory if it doesn't exist
 
3147
        @rsync_long_args_stack = ( split(/\s+/, $rsync_long_args) );
 
3148
        
 
3149
        # create $interval.0/$$bp_ref{'dest'} or .sync/$$bp_ref{'dest'} directory if it doesn't exist
 
3150
        # (this may create the .sync dir, which is why we had to check for it above)
2582
3151
        #
2583
3152
        create_backup_point_dir($interval, $bp_ref);
2584
3153
        
2645
3214
                # make rsync quiet if we're not running EXTRA verbose
2646
3215
                if ($verbose < 4) { $rsync_short_args .= 'q'; }
2647
3216
                
 
3217
        # cwrsync path
 
3218
        } elsif ( is_cwrsync_path($$bp_ref{'src'}) ) {
 
3219
                # make rsync quiet if we're not running EXTRA verbose
 
3220
                if ($verbose < 4) { $rsync_short_args .= 'q'; }
 
3221
                
2648
3222
        # this should have already been validated once, but better safe than sorry
2649
3223
        } else {
2650
3224
                bail("Could not understand source \"$$bp_ref{'src'}\" in backup_lowest_interval()");
2651
3225
        }
2652
3226
        
2653
 
        # if we're using --link-dest, we'll need to specify .1 as the link-dest directory
 
3227
        # if we're using --link-dest, we'll need to specify the link-dest directory target
 
3228
        # this varies depending on whether we're operating on the lowest interval or doing a 'sync'
2654
3229
        if (1 == $link_dest) {
2655
 
                if ( -d "$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}" ) {
2656
 
                        push(@rsync_long_args_stack, "--link-dest=$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}");
 
3230
                # bp_ref{'dest'} and snapshot_root have already been validated, but these might be blank
 
3231
                if (defined($interval_link_dest) && defined($interval_num_link_dest)) {
 
3232
                        
 
3233
                        # make sure the directory exists
 
3234
                        if ( -d "$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}" ) {
 
3235
                                
 
3236
                                # we don't use link_dest if we already synced once to this directory
 
3237
                                if ($sync_dir_was_present) {
 
3238
                                        
 
3239
                                        # skip --link-dest, this is the second time the sync has been run, because the .sync directory already exists
 
3240
                                        
 
3241
                                # default: push link_dest arguments onto cmd stack
 
3242
                                } else {
 
3243
                                        push(
 
3244
                                                @rsync_long_args_stack,
 
3245
                                                "--link-dest=$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}"
 
3246
                                        );
 
3247
                                }
 
3248
                        }
2657
3249
                }
2658
3250
        }
2659
3251
        
2663
3255
        #
2664
3256
        #   This is necessary because --link-dest only works on directories
2665
3257
        #
2666
 
        if ((1 == $link_dest) && (is_file($$bp_ref{'src'})) && (-f "$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}")) {
 
3258
        if (
 
3259
                (1 == $link_dest) &&
 
3260
                (is_file($$bp_ref{'src'})) &&
 
3261
                defined($interval_link_dest) &&
 
3262
                defined($interval_num_link_dest) &&
 
3263
                (-f "$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}")
 
3264
        ) {
 
3265
                
2667
3266
                # these are both "destination" paths, but we're moving from .1 to .0
2668
 
                my $srcpath             = "$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}";
2669
 
                my $destpath    = "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}";
 
3267
                my $srcpath;
 
3268
                my $destpath;
 
3269
                
 
3270
                $srcpath = "$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}";
 
3271
                
 
3272
                if ($interval eq 'sync') {
 
3273
                        $destpath = "$config_vars{'snapshot_root'}/.sync/$$bp_ref{'dest'}";
 
3274
                } else {
 
3275
                        $destpath = "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}";
 
3276
                }
2670
3277
                
2671
3278
                print_cmd("ln $srcpath $destpath");
2672
3279
                
2680
3287
                }
2681
3288
        }
2682
3289
        
 
3290
        # figure out if we're using the --relative flag to rsync.
 
3291
        # this influences how the source paths are constructed below.
 
3292
        foreach my $rsync_long_arg (@rsync_long_args_stack) {
 
3293
                if (defined($rsync_long_arg)) {
 
3294
                        if ('--relative' eq $rsync_long_arg) {
 
3295
                                $using_relative = 1;
 
3296
                        }
 
3297
                }
 
3298
        }
 
3299
        
 
3300
        if (defined($$bp_ref{'src'})) {
 
3301
                # make sure that the source path doesn't have a trailing slash if we're using the --relative flag
 
3302
                # this is to work around a bug in most versions of rsync that don't properly delete entries
 
3303
                # when the --relative flag is set.
 
3304
                #
 
3305
                if (1 == $using_relative) {
 
3306
                        $src = remove_trailing_slash( "$$bp_ref{'src'}" );
 
3307
                        $src = add_slashdot_if_root( "$src" );
 
3308
                        
 
3309
                # no matter what, we need a source path
 
3310
                } else {
 
3311
                        # put a trailing slash on it if we know it's a directory and it doesn't have one
 
3312
                        if ((-d "$$bp_ref{'src'}") && ($$bp_ref{'src'} !~ /\/$/)) {
 
3313
                                $src = $$bp_ref{'src'} . '/';
 
3314
                                
 
3315
                        # just use it as-is
 
3316
                        } else {
 
3317
                                $src = $$bp_ref{'src'};
 
3318
                        }
 
3319
                }
 
3320
        }
 
3321
        
2683
3322
        # BEGIN RSYNC COMMAND ASSEMBLY
2684
3323
        #   take care not to introduce blank elements into the array,
2685
3324
        #   since it can confuse rsync, which in turn causes strange errors
2704
3343
        }
2705
3344
        #
2706
3345
        # src
2707
 
        push(@cmd_stack, $$bp_ref{'src'});
 
3346
        push(@cmd_stack, "$src");
2708
3347
        #
2709
3348
        # dest
2710
 
        push(@cmd_stack, "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}");
 
3349
        if ($interval eq 'sync') {
 
3350
                push(@cmd_stack, "$config_vars{'snapshot_root'}/.sync/$$bp_ref{'dest'}");
 
3351
        } else {
 
3352
                push(@cmd_stack, "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}");
 
3353
        }
2711
3354
        #
2712
3355
        # END RSYNC COMMAND ASSEMBLY
2713
3356
        
2732
3375
 
2733
3376
# accepts rsync exit code, backup_point_ref
2734
3377
# prints out an appropriate error message (and logs it)
2735
 
# if we're using link_dest, we might set this up for rollback later
 
3378
# also adds destination path to the rollback queue if link_dest is enabled
2736
3379
sub handle_rsync_error {
2737
3380
        my $retval      = shift(@_);
2738
3381
        my $bp_ref      = shift(@_);
2767
3410
                
2768
3411
        # other error
2769
3412
        } else {
2770
 
                print_err ("$config_vars{'cmd_rsync'} returned $retval", 2);
2771
 
                syslog_err("$config_vars{'cmd_rsync'} returned $retval");
 
3413
                print_err ("$config_vars{'cmd_rsync'} returned $retval while processing $$bp_ref{'src'}", 2);
 
3414
                syslog_err("$config_vars{'cmd_rsync'} returned $retval while processing $$bp_ref{'src'}");
2772
3415
                
2773
3416
                # set this directory to rollback if we're using link_dest
2774
3417
                # (since $interval.0/ will have been moved to $interval.1/ by now)
2786
3429
        my $bp_ref              = shift(@_);
2787
3430
        
2788
3431
        # validate subroutine args
2789
 
        if (!defined($interval))        { bail('interval not defined in handle_backup_point()'); }
2790
 
        if (!defined($bp_ref))          { bail('bp_ref not defined in handle_backup_point()'); }
 
3432
        if (!defined($interval))        { bail('interval not defined in exec_backup_script()'); }
 
3433
        if (!defined($bp_ref))          { bail('bp_ref not defined in exec_backup_script()'); }
2791
3434
        
2792
3435
        # other misc variables
2793
3436
        my $script      = undef;
2848
3491
        # run the backup script
2849
3492
        #
2850
3493
        # the assumption here is that the backup script is written in such a way
2851
 
        # that it creates files in it's current working directory.
 
3494
        # that it creates files in its current working directory.
2852
3495
        #
2853
3496
        # the backup script should return 0 on success, anything else is
2854
3497
        # considered a failure.
2863
3506
                        
2864
3507
                        print_err ("backup_script $$bp_ref{'script'} returned $retval", 2);
2865
3508
                        syslog_err("backup_script $$bp_ref{'script'} returned $retval");
 
3509
                        
 
3510
                        # if the backup script failed, roll back to the last good data
 
3511
                        push(@rollback_points, $$bp_ref{'dest'} );
2866
3512
                }
2867
3513
        }
2868
3514
        
2883
3529
        # from .1 back to .0 if possible. these will be used as a baseline for diff comparisons by
2884
3530
        # sync_if_different() down below.
2885
3531
        if (1 == $link_dest) {
2886
 
                my $lastdir     = "$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}";
2887
 
                my $curdir      = "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}";
 
3532
                my $lastdir;
 
3533
                my $curdir;
 
3534
                
 
3535
                if ($interval eq 'sync') {
 
3536
                        $lastdir        = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0/$$bp_ref{'dest'}";
 
3537
                        $curdir         = "$config_vars{'snapshot_root'}/.sync/$$bp_ref{'dest'}";
 
3538
                } else {
 
3539
                        $lastdir        = "$config_vars{'snapshot_root'}/$interval.1/$$bp_ref{'dest'}";
 
3540
                        $curdir         = "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}";
 
3541
                }
2888
3542
                
2889
3543
                # make sure we have a slash at the end
2890
3544
                if ($lastdir !~ m/\/$/) {
2901
3555
                        if ( ! -e "$curdir" ) {
2902
3556
                                
2903
3557
                                # call generic cp_al() subroutine
 
3558
                                display_cp_al( "$lastdir", "$curdir" );
2904
3559
                                if (0 == $test) {
2905
 
                                        display_cp_al( "$lastdir", "$curdir" );
2906
3560
                                        $result = cp_al( "$lastdir", "$curdir" );
2907
3561
                                        if (! $result) {
2908
3562
                                                print_err("Warning! cp_al(\"$lastdir\", \"$curdir/\")", 2);
2918
3572
        # rsync doesn't work here because it sees that the timestamps are different, and
2919
3573
        # insists on changing things even if the files are bit for bit identical on content.
2920
3574
        #
2921
 
        print_cmd("sync_if_different(\"$tmpdir\", \"$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}\")");
 
3575
        # check to see where we're syncing to
 
3576
        my $target_dir;
 
3577
        if ($interval eq 'sync') {
 
3578
                $target_dir = "$config_vars{'snapshot_root'}/.sync/$$bp_ref{'dest'}";
 
3579
        } else {
 
3580
                $target_dir = "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}";
 
3581
        }
 
3582
        
 
3583
        print_cmd("sync_if_different(\"$tmpdir\", \"$target_dir\")");
2922
3584
        
2923
3585
        if (0 == $test) {
2924
 
                $result = sync_if_different("$tmpdir", "$config_vars{'snapshot_root'}/$interval.0/$$bp_ref{'dest'}");
 
3586
                $result = sync_if_different("$tmpdir", "$target_dir");
2925
3587
                if (!defined($result)) {
2926
3588
                        print_err("Warning! sync_if_different(\"$tmpdir\", \"$$bp_ref{'dest'}\") returned undef", 2);
2927
3589
                }
2940
3602
        }
2941
3603
}
2942
3604
 
 
3605
# accepts and runs an arbitrary command string
 
3606
# returns the exit value of the command
 
3607
sub exec_cmd {
 
3608
        my $cmd = shift(@_);
 
3609
        
 
3610
        my $return = 0;
 
3611
        my $retval = 0;
 
3612
        
 
3613
        if (!defined($cmd) or ('' eq $cmd)) {
 
3614
                print_err("Warning! Command \"$cmd\" not found", 2);
 
3615
                return (undef);
 
3616
        }
 
3617
        
 
3618
        print_cmd($cmd);
 
3619
        if (0 == $test) {
 
3620
                $return = system($cmd);
 
3621
                if (!defined($return)) {
 
3622
                        print_err("Warning! exec_cmd(\"$cmd\") returned undef", 2);
 
3623
                }
 
3624
                
 
3625
                # bitmask to get the real return value
 
3626
                $retval = get_retval($return);
 
3627
        }
 
3628
        
 
3629
        return ($retval);
 
3630
}
 
3631
 
 
3632
# accepts no arguments
 
3633
# returns the exit code of the defined preexec script, or undef if the command is not found
 
3634
sub exec_cmd_preexec {
 
3635
        my $retval = 0;
 
3636
        
 
3637
        # exec_cmd will only run if we're not in test mode
 
3638
        if (defined($config_vars{'cmd_preexec'})) {
 
3639
                $retval = exec_cmd( "$config_vars{'cmd_preexec'}" );
 
3640
        }
 
3641
        
 
3642
        if (!defined($retval)) {
 
3643
                print_err("$config_vars{'cmd_preexec'} not found", 2);
 
3644
        }
 
3645
        
 
3646
        if (0 != $retval) {
 
3647
                print_warn("cmd_preexec \"$config_vars{'cmd_preexec'}\" returned $retval", 2);
 
3648
        }
 
3649
        
 
3650
        return ($retval);
 
3651
}
 
3652
 
 
3653
# accepts no arguments
 
3654
# returns the exit code of the defined preexec script, or undef if the command is not found
 
3655
sub exec_cmd_postexec {
 
3656
        my $retval = 0;
 
3657
        
 
3658
        # exec_cmd will only run if we're not in test mode
 
3659
        if (defined($config_vars{'cmd_postexec'})) {
 
3660
                $retval = exec_cmd( "$config_vars{'cmd_postexec'}" );
 
3661
        }
 
3662
        
 
3663
        if (!defined($retval)) {
 
3664
                print_err("$config_vars{'cmd_postexec'} not found", 2);
 
3665
        }
 
3666
        
 
3667
        if (0 != $retval) {
 
3668
                print_warn("cmd_postexec \"$config_vars{'cmd_postexec'}\" returned $retval", 2);
 
3669
        }
 
3670
        
 
3671
        return ($retval);
 
3672
}
 
3673
 
2943
3674
# accepts interval, backup_point_ref
2944
3675
# returns nothing
2945
3676
# exits the program if it encounters a fatal error
2956
3687
        pop(@dirs);
2957
3688
        
2958
3689
        # don't mkdir for dest unless we have to
2959
 
        my $destpath = "$config_vars{'snapshot_root'}/$interval.0/" . join('/', @dirs);
 
3690
        my $destpath;
 
3691
        if ($interval eq 'sync') {
 
3692
                $destpath = "$config_vars{'snapshot_root'}/.sync/" . join('/', @dirs);
 
3693
        } else {
 
3694
                $destpath = "$config_vars{'snapshot_root'}/$interval.0/" . join('/', @dirs);
 
3695
        }
2960
3696
        
2961
3697
        # make sure we DON'T have a trailing slash (for mkpath)
2962
3698
        if ($destpath =~ m/\/$/) {
2963
 
                $destpath =~ s/\/$//;
 
3699
                $destpath = remove_trailing_slash($destpath);
2964
3700
        }
2965
3701
        
2966
3702
        # create the directory if it doesn't exist
2981
3717
# accepts interval we're operating on
2982
3718
# returns nothing important
2983
3719
# rolls back failed backups, as defined in the @rollback_points array
2984
 
# this is only necessary if we're using link_dest, since it moves the .0 to .1 directory,
2985
 
# instead of recursively copying links to the files
 
3720
# this is necessary if we're using link_dest, since it moves the .0 to .1 directory,
 
3721
# instead of recursively copying links to the files. it also helps with failed
 
3722
# backup scripts.
2986
3723
#
2987
3724
sub rollback_failed_backups {
2988
3725
        my $interval = shift(@_);
2989
3726
        
2990
 
        my $result;
2991
 
        my $rsync_short_args    = $default_rsync_short_args;
2992
 
        
2993
3727
        if (!defined($interval)) { bail('interval not defined in rollback_failed_backups()'); }
2994
3728
        
 
3729
        my $result;
 
3730
        my $rsync_short_args    = $default_rsync_short_args;
 
3731
        
 
3732
        # handle 'sync' case
 
3733
        my $interval_src;
 
3734
        my $interval_dest;
 
3735
        
 
3736
        if ($interval eq 'sync') {
 
3737
                $interval_src   = $intervals[0]->{'interval'} . '.0';
 
3738
                $interval_dest  = '.sync';
 
3739
        } else {
 
3740
                $interval_src   = "$interval.1";
 
3741
                $interval_dest  = "$interval.0";
 
3742
        }
 
3743
        
2995
3744
        # extra verbose?
2996
3745
        if ($verbose > 3) { $rsync_short_args .= 'v'; }
2997
3746
        
3006
3755
                syslog_warn("Rolling back \"$rollback_point\"");
3007
3756
                
3008
3757
                # using link_dest, this probably won't happen
3009
 
                # just in case, we may have to delete the old backup point from interval.0/
3010
 
                if ( -e "$config_vars{'snapshot_root'}/$interval.0/$rollback_point" ) {
3011
 
                        display_rm_rf("$config_vars{'snapshot_root'}/$interval.0/$rollback_point");
 
3758
                # just in case, we may have to delete the old backup point from interval.0 / .sync
 
3759
                if ( -e "$config_vars{'snapshot_root'}/$interval_dest/$rollback_point" ) {
 
3760
                        display_rm_rf("$config_vars{'snapshot_root'}/$interval_dest/$rollback_point");
3012
3761
                        if (0 == $test) {
3013
 
                                $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.0/$rollback_point" );
 
3762
                                $result = rm_rf( "$config_vars{'snapshot_root'}/$interval_dest/$rollback_point" );
3014
3763
                                if (0 == $result) {
3015
 
                                        bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.0/$rollback_point\")\n");
 
3764
                                        bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval_dest/$rollback_point\")\n");
3016
3765
                                }
3017
3766
                        }
3018
3767
                }
3019
3768
                
3020
3769
                # copy hard links back from .1 to .0
3021
3770
                # this will re-populate the .0 directory without taking up (much) additional space
 
3771
                #
 
3772
                # if we're doing a 'sync', then instead of .1 and .0, it's lowest.0 and .sync
3022
3773
                display_cp_al(
3023
 
                        "$config_vars{'snapshot_root'}/$interval.1/$rollback_point",
3024
 
                        "$config_vars{'snapshot_root'}/$interval.0/$rollback_point"
 
3774
                        "$config_vars{'snapshot_root'}/$interval_src/$rollback_point",
 
3775
                        "$config_vars{'snapshot_root'}/$interval_dest/$rollback_point"
3025
3776
                );
3026
3777
                if (0 == $test) {
3027
3778
                        $result = cp_al(
3028
 
                                "$config_vars{'snapshot_root'}/$interval.1/$rollback_point",
3029
 
                                "$config_vars{'snapshot_root'}/$interval.0/$rollback_point"
 
3779
                                "$config_vars{'snapshot_root'}/$interval_src/$rollback_point",
 
3780
                                "$config_vars{'snapshot_root'}/$interval_dest/$rollback_point"
3030
3781
                        );
3031
3782
                        if (! $result) {
3032
3783
                                my $errstr = '';
3033
 
                                $errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval.1/$rollback_point\", ";
3034
 
                                $errstr .= "\"$config_vars{'snapshot_root'}/$interval.0/$rollback_point\")";
 
3784
                                $errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval_src/$rollback_point\", ";
 
3785
                                $errstr .= "\"$config_vars{'snapshot_root'}/$interval_dest/$rollback_point\")";
3035
3786
                                bail($errstr);
3036
3787
                        }
3037
3788
                }
3041
3792
# accepts interval
3042
3793
# returns nothing
3043
3794
# updates mtime on $interval.0
3044
 
sub touch_interval_0 {
 
3795
sub touch_interval_dir {
3045
3796
        my $interval = shift(@_);
3046
3797
        
3047
3798
        if (!defined($interval)) { bail('interval not defined in touch_interval()'); }
3048
3799
        
 
3800
        my $interval_dir;
 
3801
        
 
3802
        if ($interval eq 'sync') {
 
3803
                $interval_dir = '.sync';
 
3804
        } else {
 
3805
                $interval_dir = $interval . '.0';
 
3806
        }
 
3807
        
3049
3808
        # update mtime of $interval.0 to reflect the time this snapshot was taken
3050
 
        print_cmd("touch $config_vars{'snapshot_root'}/$interval.0/");
 
3809
        print_cmd("touch $config_vars{'snapshot_root'}/$interval_dir/");
3051
3810
        
3052
3811
        if (0 == $test) {
3053
 
                my $result = utime(time(), time(), "$config_vars{'snapshot_root'}/$interval.0/");
 
3812
                my $result = utime(time(), time(), "$config_vars{'snapshot_root'}/$interval_dir/");
3054
3813
                if (0 == $result) {
3055
 
                        bail("Could not utime(time(), time(), \"$config_vars{'snapshot_root'}/$interval.0/\");");
 
3814
                        bail("Could not utime(time(), time(), \"$config_vars{'snapshot_root'}/$interval_dir/\");");
3056
3815
                }
3057
3816
        }
3058
3817
}
3059
3818
 
3060
3819
# accepts an interval_data_ref
3061
3820
# looks at $$id_ref{'interval'} as the interval to act on,
3062
 
# and the previous interval $$id_ref{'prev_interval'} to pull up the directory from (i.e. daily, hourly)
 
3821
# and the previous interval $$id_ref{'prev_interval'} to pull up the directory from (e.g., daily, hourly)
3063
3822
# the interval being acted upon should not be the lowest one.
3064
3823
#
3065
3824
# rotates older dirs within this interval, and hard links
3074
3833
        
3075
3834
        # this also should never happen
3076
3835
        if (!defined($$id_ref{'interval_num'}) or (0 == $$id_ref{'interval_num'})) {
3077
 
                bail('backup_lowest_interval() can only operate on the higher intervals');
 
3836
                bail('rotate_higher_interval() can only operate on the higher intervals');
3078
3837
        }
3079
3838
        
3080
3839
        # set up variables for convenience since we refer to them extensively
3088
3847
        #
3089
3848
        # delete the oldest one (if we're keeping more than one)
3090
3849
        if ( -d "$config_vars{'snapshot_root'}/$interval.$interval_max" ) {
3091
 
                display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
3092
 
                
3093
 
                if (0 == $test) {
3094
 
                        my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
3095
 
                        if (0 == $result) {
3096
 
                                bail("Could not rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\");");
 
3850
                # if use_lazy_deletes is set move the oldest directory to interval.delete
 
3851
                # otherwise preform the default behavior
 
3852
                if (1 == $use_lazy_deletes) {
 
3853
                        print_cmd("mv ",
 
3854
                                "$config_vars{'snapshot_root'}/$interval.$interval_max/ ",
 
3855
                                "$config_vars{'snapshot_root'}/$interval.delete/"
 
3856
                        );
 
3857
                        
 
3858
                        if (0 == $test) {
 
3859
                                my $result = safe_rename(
 
3860
                                        "$config_vars{'snapshot_root'}/$interval.$interval_max",
 
3861
                                        ("$config_vars{'snapshot_root'}/$interval.delete")
 
3862
                                );
 
3863
                                if (0 == $result) {
 
3864
                                        my $errstr = '';
 
3865
                                        $errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\", \"";
 
3866
                                        $errstr .= "$config_vars{'snapshot_root'}/$interval.delete/\")";
 
3867
                                        bail($errstr);
 
3868
                                }                               
 
3869
                        }                               
 
3870
                } else {
 
3871
                        display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
 
3872
                        
 
3873
                        if (0 == $test) {
 
3874
                                my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
 
3875
                                if (0 == $result) {
 
3876
                                        bail("Could not rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\");");
 
3877
                                }
3097
3878
                        }
3098
3879
                }
 
3880
 
3099
3881
        } else {
3100
3882
                print_msg("$config_vars{'snapshot_root'}/$interval.$interval_max not present (yet), nothing to delete", 4);
3101
3883
        }
3103
3885
        # rotate the middle ones
3104
3886
        for (my $i=($interval_max-1); $i>=0; $i--) {
3105
3887
                if ( -d "$config_vars{'snapshot_root'}/$interval.$i" ) {
3106
 
                        print_cmd("mv $config_vars{'snapshot_root'}/$interval.$i/ ",
3107
 
                                                "$config_vars{'snapshot_root'}/$interval." . ($i+1) . "/");
 
3888
                        print_cmd(
 
3889
                                "mv $config_vars{'snapshot_root'}/$interval.$i/ ",
 
3890
                                "$config_vars{'snapshot_root'}/$interval." . ($i+1) . "/"
 
3891
                        );
3108
3892
                        
3109
3893
                        if (0 == $test) {
3110
3894
                                my $result = safe_rename(
3111
 
                                                                "$config_vars{'snapshot_root'}/$interval.$i",
3112
 
                                                                ("$config_vars{'snapshot_root'}/$interval." . ($i+1))
 
3895
                                        "$config_vars{'snapshot_root'}/$interval.$i",
 
3896
                                        ("$config_vars{'snapshot_root'}/$interval." . ($i+1))
3113
3897
                                );
3114
3898
                                if (0 == $result) {
3115
3899
                                        my $errstr = '';
3132
3916
                # move the last one up a level
3133
3917
                if (($prev_interval_max >= 1) or ($interval_num >= 2)) {
3134
3918
                        # mv hourly.5 to daily.0 (or whatever intervals we're using)
3135
 
                        print_cmd("mv $config_vars{'snapshot_root'}/$prev_interval.$prev_interval_max/ ",
3136
 
                                                "$config_vars{'snapshot_root'}/$interval.0/");
 
3919
                        print_cmd(
 
3920
                                "mv $config_vars{'snapshot_root'}/$prev_interval.$prev_interval_max/ ",
 
3921
                                "$config_vars{'snapshot_root'}/$interval.0/"
 
3922
                        );
3137
3923
                        
3138
3924
                        if (0 == $test) {
3139
3925
                                $result = safe_rename(
3140
 
                                                                "$config_vars{'snapshot_root'}/$prev_interval.$prev_interval_max",
3141
 
                                                                "$config_vars{'snapshot_root'}/$interval.0"
 
3926
                                        "$config_vars{'snapshot_root'}/$prev_interval.$prev_interval_max",
 
3927
                                        "$config_vars{'snapshot_root'}/$interval.0"
3142
3928
                                );
3143
3929
                                if (0 == $result) {
3144
3930
                                        my $errstr = '';
3162
3948
        my $src         = shift(@_);
3163
3949
        my $dest        = shift(@_);
3164
3950
        
 
3951
        # remove trailing slashes (for newer versions of GNU cp)
 
3952
        $src  = remove_trailing_slash($src);
 
3953
        $dest = remove_trailing_slash($dest);
 
3954
        
3165
3955
        if (!defined($src))             { bail('src not defined in display_cp_al()'); }
3166
3956
        if (!defined($dest))    { bail('dest not defined in display_cp_al()'); }
3167
3957
        
3212
4002
        if (!defined($src))  { return(0); }
3213
4003
        if (!defined($dest)) { return(0); }
3214
4004
        
 
4005
        # remove trailing slashes (for newer versions of GNU cp)
 
4006
        $src  = remove_trailing_slash($src);
 
4007
        $dest = remove_trailing_slash($dest);
 
4008
        
3215
4009
        if ( ! -d "$src" ) {
3216
4010
                print_err("gnu_cp_al() needs a valid directory as an argument", 2);
3217
4011
                return (0);
3218
4012
        }
3219
4013
        
3220
 
        $src = remove_trailing_slash($src);
3221
 
        $dest = remove_trailing_slash($dest);
3222
 
        
3223
4014
        # make the system call to GNU cp
3224
4015
        $result = system( $config_vars{'cmd_cp'}, '-al', "$src", "$dest" );
3225
4016
        if ($result != 0) {
3238
4029
# If you absolutely have to have snapshots of FIFOs, etc, just get GNU
3239
4030
# cp on your system, and specify it in the config file.
3240
4031
#
 
4032
# Please note that more recently, this subroutine is followed up by
 
4033
# an rsync clean-up step. This combination effectively removes most of
 
4034
# the limitations of this technique.
 
4035
#
3241
4036
# In the great perl tradition, this returns 1 on success, 0 on failure.
3242
4037
#
3243
4038
sub native_cp_al {
3264
4059
        # LSTAT SRC
3265
4060
        my $st = lstat("$src");
3266
4061
        if (!defined($st)) {
3267
 
                print_err("Warning! Could not lstat(\"$src\")", 2);
 
4062
                print_err("Warning! Could not lstat source dir (\"$src\") : $!", 2);
3268
4063
                return(0);
3269
4064
        }
3270
4065
        
3283
4078
                
3284
4079
                $result = mkdir("$dest", $st->mode);
3285
4080
                if ( ! $result ) {
3286
 
                        print_err("Warning! Could not mkdir(\"$dest\", $st->mode);", 2);
 
4081
                        print_err("Warning! Could not mkdir(\"$dest\", $st->mode) : $!", 2);
3287
4082
                        return(0);
3288
4083
                }
3289
4084
        }
3294
4089
                if ( ! -l "$dest" ) {
3295
4090
                        # print and/or log this if necessary
3296
4091
                        if (($verbose > 4) or ($loglevel > 4)) {
3297
 
                                my $cmd_string = "chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")";
 
4092
                                my $cmd_string = "safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")";
3298
4093
                        
3299
4094
                                if ($verbose > 4) {
3300
4095
                                        print_cmd($cmd_string);
3303
4098
                                }
3304
4099
                        }
3305
4100
                        
3306
 
                        $result = chown($st->uid, $st->gid, "$dest");
 
4101
                        $result = safe_chown($st->uid, $st->gid, "$dest");
3307
4102
                        if (! $result) {
3308
 
                                print_err("Warning! Could not chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");", 2);
 
4103
                                print_err("Warning! Could not safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");", 2);
3309
4104
                                return(0);
3310
4105
                        }
3311
4106
                }
3325
4120
                        # make sure the node we just got is valid (this is highly unlikely to fail)
3326
4121
                        my $st = lstat("$src/$node");
3327
4122
                        if (!defined($st)) {
3328
 
                                print_err("Warning! Could not lstat(\"$src/$node\")", 2);
 
4123
                                print_err("Warning! Could not lstat source node (\"$src/$node\") : $!", 2);
3329
4124
                                next;
3330
4125
                        }
3331
4126
                        
3364
4159
                                # make a hard link
3365
4160
                                $result = link("$src/$node", "$dest/$node");
3366
4161
                                if (! $result) {
3367
 
                                        print_err("Warning! Could not link(\"$src/$node\", \"$dest/$node\")", 2);
 
4162
                                        print_err("Warning! Could not link(\"$src/$node\", \"$dest/$node\") : $!", 2);
3368
4163
                                        next;
3369
4164
                                }
3370
4165
                                
3387
4182
                                        print_err("Warning! Recursion error in native_cp_al(\"$src/$node\", \"$dest/$node\")", 2);
3388
4183
                                        next;
3389
4184
                                }
3390
 
                        
3391
 
                        # rsync_cleanup_after_native_cp_al() will take care of the files we can't handle here
3392
 
                        
3393
 
                        # FIFO
3394
 
                        } elsif ( -p "$src/$node" ) {
3395
 
                                # print_err("Warning! Ignoring FIFO $src/$node", 2);
3396
 
                                
3397
 
                        # SOCKET
3398
 
                        } elsif ( -S "$src/$node" ) {
3399
 
                                # print_err("Warning! Ignoring socket: $src/$node", 2);
3400
 
                                
3401
 
                        # BLOCK DEVICE
3402
 
                        } elsif ( -b "$src/$node" ) {
3403
 
                                # print_err("Warning! Ignoring special block file: $src/$node", 2);
3404
 
                                
3405
 
                        # CHAR DEVICE
3406
 
                        } elsif ( -c "$src/$node" ) {
3407
 
                                # print_err("Warning! Ignoring special character file: $src/$node", 2);
3408
4185
                        }
 
4186
                        
 
4187
                        ## rsync_cleanup_after_native_cp_al() will take care of the files we can't handle here
 
4188
                        #
 
4189
                        ## FIFO
 
4190
                        #} elsif ( -p "$src/$node" ) {
 
4191
                        #       # print_err("Warning! Ignoring FIFO $src/$node", 2);
 
4192
                        #       
 
4193
                        ## SOCKET
 
4194
                        #} elsif ( -S "$src/$node" ) {
 
4195
                        #       # print_err("Warning! Ignoring socket: $src/$node", 2);
 
4196
                        #       
 
4197
                        ## BLOCK DEVICE
 
4198
                        #} elsif ( -b "$src/$node" ) {
 
4199
                        #       # print_err("Warning! Ignoring special block file: $src/$node", 2);
 
4200
                        #       
 
4201
                        ## CHAR DEVICE
 
4202
                        #} elsif ( -c "$src/$node" ) {
 
4203
                        #       # print_err("Warning! Ignoring special character file: $src/$node", 2);
 
4204
                        #}
3409
4205
                }
3410
4206
                
3411
4207
        } else {
3430
4226
        }
3431
4227
        $result = utime($st->atime, $st->mtime, "$dest");
3432
4228
        if (! $result) {
3433
 
                print_err("Warning! Could not utime(" . $st->atime . ", " . $st->mtime . ", \"$dest\");", 2);
 
4229
                print_err("Warning! Could not set utime(" . $st->atime . ", " . $st->mtime . ", \"$dest\") : $!", 2);
3434
4230
                return(0);
3435
4231
        }
3436
4232
        
3507
4303
                        # bitmask return value
3508
4304
                        my $retval = get_retval($result);
3509
4305
                        
3510
 
                        bail("rsync returned error $retval in rsync_cleanup_after_native_cp_al()");
 
4306
                        # a partial list of rsync exit values
 
4307
                        # 0             Success
 
4308
                        # 23    Partial transfer due to error
 
4309
                        # 24    Partial transfer due to vanished source files
 
4310
        
 
4311
                        if (23 == $retval) {
 
4312
                                print_warn ("Some files and/or directories in $src only transferred partially during rsync_cleanup_after_native_cp_al operation", 2);
 
4313
                                syslog_warn("Some files and/or directories in $src only transferred partially during rsync_cleanup_after_native_cp_al operation");
 
4314
                        } elsif (24 == $retval) {
 
4315
                                print_warn ("Some files and/or directories in $src vanished during rsync_cleanup_after_native_cp_al operation", 2);
 
4316
                                syslog_warn("Some files and/or directories in $src vanished during rsync_cleanup_after_native_cp_al operation");
 
4317
 
 
4318
                        } else {
 
4319
                                # other error
 
4320
                                bail("rsync returned error $retval in rsync_cleanup_after_native_cp_al()");
 
4321
                        }
3511
4322
                }
3512
4323
        }
3513
4324
        
3601
4412
                exit(1);
3602
4413
        }
3603
4414
        
 
4415
        # check for 'du' program
 
4416
        if ( defined($config_vars{'cmd_du'}) ) {
 
4417
                # it was specified in the config file, use that version
 
4418
                $cmd_du = $config_vars{'cmd_du'};
 
4419
        }
 
4420
        
 
4421
        # check for du args
 
4422
        if ( defined($config_vars{'du_args'}) ) {
 
4423
                # it this was specified in the config file, use that version
 
4424
                $du_args = $config_vars{'du_args'};
 
4425
        }
 
4426
        
3604
4427
        # are we looking in subdirectories or at files?
3605
4428
        if (defined($ARGV[1])) {
3606
4429
                $dest_path = $ARGV[1];
3618
4441
                }
3619
4442
        }
3620
4443
        
3621
 
        
3622
 
        # find the intervals that apply here
 
4444
        # find the directories to look through, in order
 
4445
        # only add them to the list if we have read permissions
3623
4446
        if (-r "$config_vars{'snapshot_root'}/") {
 
4447
                # if we have a .sync directory, that will have the most recent files, and should be first
 
4448
                if (-d "$config_vars{'snapshot_root'}/.sync") {
 
4449
                        if (-r "$config_vars{'snapshot_root'}/.sync") {
 
4450
                                $intervals_str .= "$config_vars{'snapshot_root'}/.sync ";
 
4451
                        }
 
4452
                }
 
4453
                
 
4454
                # loop through the intervals, most recent to oldest
3624
4455
                foreach my $interval_ref (@intervals) {
3625
4456
                        my $interval                    = $$interval_ref{'interval'};
3626
4457
                        my $max_interval_num    = $$interval_ref{'number'};
3634
4465
        }
3635
4466
        chop($intervals_str);
3636
4467
        
3637
 
        # check for 'du' program
3638
 
        if ( defined($config_vars{'cmd_du'}) ) {
3639
 
                # it was specified in the config file, use that version
3640
 
                $cmd_du = $config_vars{'cmd_du'};
3641
 
        }
3642
 
        
3643
 
        # check for du args
3644
 
        if ( defined($config_vars{'du_args'}) ) {
3645
 
                # it this was specified in the config file, use that version
3646
 
                $du_args = $config_vars{'du_args'};
3647
 
        }
3648
 
        
3649
4468
        # if we can see any of the intervals, find out how much space they're taking up
 
4469
        # most likely we can either see all of them or none at all
3650
4470
        if ('' ne $intervals_str) {
3651
4471
                if (defined($verbose) && ($verbose >= 3)) {
3652
 
                        print wrap_cmd("$cmd_du $du_args $intervals_str", 76, 4), "\n\n";
 
4472
                        print wrap_cmd("$cmd_du $du_args $intervals_str"), "\n\n";
3653
4473
                }
3654
4474
                
3655
4475
                if (0 == $test) {
3664
4484
                                print STDERR "GNU du is recommended.\n";
3665
4485
                                exit(1);
3666
4486
                        }
 
4487
                } else {
 
4488
                        # test was successful
 
4489
                        exit(0);
3667
4490
                }
3668
4491
        } else {
3669
4492
                print STDERR ("No files or directories found\n");
3674
4497
        exit(1);
3675
4498
}
3676
4499
 
 
4500
# accept two args from $ARGV[1] and [2], like "daily.0" "daily.1" etc.
 
4501
# stick the full snapshot_root path on the beginning, and call rsnapshot-diff with these args
 
4502
# NOTE: since this is a read-only operation, we're not concerned with directory traversals and relative paths
 
4503
sub show_rsnapshot_diff {
 
4504
        my $cmd_rsnapshot_diff = 'rsnapshot-diff';
 
4505
        
 
4506
        my $retval;
 
4507
        
 
4508
        # this will only hold two entries, no more no less
 
4509
        # paths_in holds the incoming arguments
 
4510
        # args will be assigned the arguments that rsnapshot-diff will use
 
4511
        #
 
4512
        my @paths_in    = ();
 
4513
        my @cmd_args    = ();
 
4514
        
 
4515
        # first, make sure we have permission to see the snapshot root
 
4516
        if ( ! -r "$config_vars{'snapshot_root'}" ) {
 
4517
                print STDERR ("ERROR: Permission denied\n");
 
4518
                exit(1);
 
4519
        }
 
4520
        
 
4521
        # check for rsnapshot-diff program (falling back on $PATH)
 
4522
        if (defined($config_vars{'cmd_rsnapshot_diff'})) {
 
4523
                $cmd_rsnapshot_diff = $config_vars{'cmd_rsnapshot_diff'};
 
4524
        }
 
4525
        
 
4526
        # see if we even got the right number of arguments (none is OK, but 1 isn't. 2 is also OK)
 
4527
        if (defined($ARGV[1]) && !defined($ARGV[2])) {
 
4528
                print STDERR "Usage: rsnapshot diff [interval|dir] [interval|dir]\n";
 
4529
                exit(1);
 
4530
        }
 
4531
        
 
4532
        # make this automatically pick the two lowest intervals (or .sync dir) for comparison, as the default
 
4533
        # we actually want to specify the older directory first, since rsnapshot-diff will flip them around
 
4534
        # anyway based on mod times. doing it this way should make both programs consistent, and cause less
 
4535
        # surprises.
 
4536
        if (!defined($ARGV[1]) && !defined($ARGV[2])) {
 
4537
                # sync_first is enabled, and .sync exists
 
4538
                if ($config_vars{'sync_first'} && (-d "$config_vars{'snapshot_root'}/.sync/")) {
 
4539
                        # interval.0
 
4540
                        if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0" ) ) {
 
4541
                                $cmd_args[0] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0";
 
4542
                        }
 
4543
                        
 
4544
                        # .sync
 
4545
                        $cmd_args[1] = "$config_vars{'snapshot_root'}/.sync";
 
4546
                        
 
4547
                # sync_first is not enabled, or .sync doesn't exist
 
4548
                } else {
 
4549
                        # interval.1
 
4550
                        if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".1" ) ) {
 
4551
                                $cmd_args[0] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".1";
 
4552
                        }
 
4553
                        # interval.0
 
4554
                        if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0" ) ) {
 
4555
                                $cmd_args[1] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0";
 
4556
                        }
 
4557
                }
 
4558
                        
 
4559
        # if we got some command line arguments, loop through twice and figure out what they mean
 
4560
        } else {
 
4561
                $paths_in[0] = $ARGV[1];        # the 1st path is the 2nd cmd line argument
 
4562
                $paths_in[1] = $ARGV[2];        # the 2nd path is the 3rd cmd line argument
 
4563
        
 
4564
                for (my $i=0; $i<2; $i++) {
 
4565
                        # no interval would start with ../
 
4566
                        if (is_directory_traversal( "$paths_in[$i]" )) {
 
4567
                                $cmd_args[$i] = $paths_in[$i];
 
4568
                                
 
4569
                        # if this directory exists locally, it must be local
 
4570
                        } elsif ( -e "$paths_in[$i]" ) {
 
4571
                                $cmd_args[$i] = $paths_in[$i];
 
4572
                                
 
4573
                        # absolute path
 
4574
                        } elsif (is_valid_local_abs_path( "$paths_in[$i]" )) {
 
4575
                                $cmd_args[$i] = $paths_in[$i];
 
4576
                                
 
4577
                        # we didn't find it locally, but it's in the snapshot root
 
4578
                        } elsif ( -e "$config_vars{'snapshot_root'}/$paths_in[$i]" ) {
 
4579
                                $cmd_args[$i] = "$config_vars{'snapshot_root'}/$paths_in[$i]";
 
4580
                        }
 
4581
                }
 
4582
        }
 
4583
        
 
4584
        # double check to make sure the directories exists (and are directories)
 
4585
        if ( (!defined($cmd_args[0]) or (!defined($cmd_args[1]))) or ((! -d "$cmd_args[0]") or (! -d "$cmd_args[1]")) ) {
 
4586
                print STDERR "ERROR: Arguments must be valid intervals or directories\n";
 
4587
                exit(1);
 
4588
        }
 
4589
        
 
4590
        # remove trailing slashes from directories
 
4591
        $cmd_args[0] = remove_trailing_slash($cmd_args[0]);
 
4592
        $cmd_args[1] = remove_trailing_slash($cmd_args[1]);
 
4593
        
 
4594
        # increase verbosity (by possibly sticking a verbose flag in as the first argument)
 
4595
        #
 
4596
        # debug
 
4597
        if ($verbose >= 5) {
 
4598
                unshift(@cmd_args, '-V');
 
4599
                
 
4600
        } elsif ($verbose >= 4) {
 
4601
                unshift(@cmd_args, '-v');
 
4602
                
 
4603
        # verbose
 
4604
        } elsif ($verbose >= 3) {
 
4605
                unshift(@cmd_args, '-vi');
 
4606
        }
 
4607
        
 
4608
        # run rsnapshot-diff
 
4609
        if (defined($verbose) && ($verbose >= 3)) {
 
4610
                print wrap_cmd(("$cmd_rsnapshot_diff " . join(' ', @cmd_args))), "\n\n";
 
4611
        }
 
4612
        if (0 == $test) {
 
4613
                $retval = system($cmd_rsnapshot_diff, @cmd_args);
 
4614
                if (0 == $retval) {
 
4615
                        exit(0);
 
4616
                } else {
 
4617
                        # exit showing error
 
4618
                        print STDERR "Error while calling $cmd_rsnapshot_diff\n";
 
4619
                        exit(1);
 
4620
                }
 
4621
        } else {
 
4622
                # test was successful
 
4623
                exit(0);
 
4624
        }
 
4625
        
 
4626
        # shouldn't happen
 
4627
        exit(1);
 
4628
}
 
4629
 
3677
4630
# This subroutine works the way I hoped rsync would under certain conditions.
3678
4631
# This is no fault of rsync, I just had something slightly different in mind :)
3679
4632
#
3751
4704
# accepts src, dest
3752
4705
# "copies" everything from src to dest, mainly using hard links
3753
4706
# called only from sync_if_different()
 
4707
# returns 1 on success, 0 if any failures occur
3754
4708
sub sync_cp_src_dest {
3755
4709
        my $src         = shift(@_);
3756
4710
        my $dest        = shift(@_);
3757
4711
        my $dh          = undef;
3758
4712
        my $result      = 0;
 
4713
        my $retval      = 1;    # return code for this subroutine
3759
4714
        
3760
4715
        # make sure we were passed two arguments
3761
4716
        if (!defined($src))  { return(0); }
3781
4736
        
3782
4737
        # MKDIR DEST (AND SET MODE)
3783
4738
        if ( ! -d "$dest" ) {
 
4739
                # check to make sure we don't have something here that's not a directory
 
4740
                if ( -e "$dest" ) {
 
4741
                        $result = unlink("$dest");
 
4742
                        if (0 == $result) {
 
4743
                                print_err("Warning! Could not unlink(\"$dest\")", 2);
 
4744
                                return(0);
 
4745
                        }
 
4746
                }
 
4747
                
 
4748
                # create the directory
3784
4749
                $result = mkdir("$dest", $st->mode);
3785
4750
                if ( ! $result ) {
3786
4751
                        print_err("Warning! Could not mkdir(\"$dest\", $st->mode);", 2);
3790
4755
        
3791
4756
        # CHOWN DEST (if root)
3792
4757
        if (0 == $<) {
3793
 
                # make sure dest is not a symlink
 
4758
                # make sure destination is not a symlink (should never happen because of unlink() above)
3794
4759
                if ( ! -l "$dest" ) {
3795
 
                        $result = chown($st->uid, $st->gid, "$dest");
 
4760
                        $result = safe_chown($st->uid, $st->gid, "$dest");
3796
4761
                        if (! $result) {
3797
 
                                print_err("Warning! Could not chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");", 2);
 
4762
                                print_err("Warning! Could not safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");", 2);
3798
4763
                                return(0);
3799
4764
                        }
3800
4765
                }
3811
4776
                        # skip '.' and '..'
3812
4777
                        next if ($node =~ m/^\.\.?$/o);
3813
4778
                        
3814
 
                        # make sure the node we just got is valid (this is highly unlikely to fail)
3815
 
                        my $st = lstat("$src/$node");
3816
 
                        if (!defined($st)) {
3817
 
                                print_err("Could not lstat(\"$src/$node\")", 2);
3818
 
                                return(0);
3819
 
                        }
3820
 
                        
3821
4779
                        # if it's a symlink, create the link
3822
4780
                        # this check must be done before dir and file because it will
3823
4781
                        # pretend to be a file or a directory as well as a symlink
3824
4782
                        if ( -l "$src/$node" ) {
 
4783
                                # nuke whatever is in the destination, since we'd have to recreate the symlink anyway
 
4784
                                # and a real file or directory will be in our way
 
4785
                                # symlinks pretend to be directories, which is why we check it the way that we do
 
4786
                                if ( -e "$dest/$node" ) {
 
4787
                                        if ((-l "$dest/$node") or (! -d "$dest/$node")) {
 
4788
                                                $result = unlink("$dest/$node");
 
4789
                                                if (0 == $result) {
 
4790
                                                        print_err("Warning! Could not unlink(\"$dest/$node\")", 2);
 
4791
                                                        next;
 
4792
                                                }
 
4793
                                                
 
4794
                                        # nuke the destination directory
 
4795
                                        } else {
 
4796
                                                $result = rm_rf("$dest/$node");
 
4797
                                                if (0 == $result) {
 
4798
                                                        print_err("Could not rm_rf(\"$dest/$node\")", 2);
 
4799
                                                        next;
 
4800
                                                }
 
4801
                                        }
 
4802
                                }
 
4803
                                
3825
4804
                                $result = copy_symlink("$src/$node", "$dest/$node");
3826
4805
                                if (0 == $result) {
3827
 
                                        print_err("Warning! copy_symlink(\"$src/$node\", \"$dest/$node\")", 2);
 
4806
                                        print_err("Warning! copy_symlink(\"$src/$node\", \"$dest/$node\") failed", 2);
 
4807
                                        return(0);
3828
4808
                                }
3829
4809
                                
3830
4810
                        # if it's a directory, recurse!
3831
4811
                        } elsif ( -d "$src/$node" ) {
 
4812
                                # if the destination exists but isn't a directory, delete it
 
4813
                                if (-e "$dest/$node") {
 
4814
                                        # a symlink might claim to be a directory, so check for that first
 
4815
                                        if ((-l "$dest/$node") or (! -d "$dest/$node")) {
 
4816
                                                $result = unlink("$dest/$node");
 
4817
                                                if (0 == $result) {
 
4818
                                                        print_err("Warning! unlink(\"$dest/$node\") failed", 2);
 
4819
                                                        next;
 
4820
                                                }
 
4821
                                        }
 
4822
                                }
 
4823
                                
 
4824
                                # ok, dest is a real directory or it isn't there yet, go recurse
3832
4825
                                $result = sync_cp_src_dest("$src/$node", "$dest/$node");
3833
4826
                                if (! $result) {
3834
4827
                                        print_err("Warning! Recursion error in sync_cp_src_dest(\"$src/$node\", \"$dest/$node\")", 2);
3836
4829
                                
3837
4830
                        # if it's a file...
3838
4831
                        } elsif ( -f "$src/$node" ) {
3839
 
                                
3840
 
                                # if dest exists, check for differences
 
4832
                                # if dest is a symlink, we need to remove it first
 
4833
                                if ( -l "$dest/$node" ) {
 
4834
                                        $result = unlink("$dest/$node");
 
4835
                                        if (0 == $result) {
 
4836
                                                print_err("Warning! unlink(\"$dest/$node\") failed", 2);
 
4837
                                                next;
 
4838
                                        }
 
4839
                                }
 
4840
                                
 
4841
                                # if dest is a directory, we need to wipe it out first
 
4842
                                if ( -d "$dest/$node" ) {
 
4843
                                        $result = rm_rf("$dest/$node");
 
4844
                                        if (0 == $result) {
 
4845
                                                print_err("Could not rm_rf(\"$dest/$node\")", 2);
 
4846
                                                return(0);
 
4847
                                        }
 
4848
                                }
 
4849
                                
 
4850
                                # if dest (still) exists, check for differences
3841
4851
                                if ( -e "$dest/$node" ) {
3842
4852
                                        
3843
4853
                                        # if they are different, unlink dest and link src to dest
3844
4854
                                        if (1 == file_diff("$src/$node", "$dest/$node")) {
3845
4855
                                                $result = unlink("$dest/$node");
3846
4856
                                                if (0 == $result) {
3847
 
                                                        print_err("Warning! unlink(\"$dest/$node\")", 2);
 
4857
                                                        print_err("Warning! unlink(\"$dest/$node\") failed", 2);
3848
4858
                                                        next;
3849
4859
                                                }
3850
4860
                                                $result = link("$src/$node", "$dest/$node");
3851
4861
                                                if (0 == $result) {
3852
 
                                                        print_err("Warning! link(\"$src/$node\", \"$dest/$node\")", 2);
 
4862
                                                        print_err("Warning! link(\"$src/$node\", \"$dest/$node\") failed", 2);
3853
4863
                                                        next;
3854
4864
                                                }
3855
4865
                                                
3862
4872
                                } else {
3863
4873
                                        $result = link("$src/$node", "$dest/$node");
3864
4874
                                        if (0 == $result) {
3865
 
                                                print_err("Warning! link(\"$src/$node\", \"$dest/$node\")", 2);
 
4875
                                                print_err("Warning! link(\"$src/$node\", \"$dest/$node\") failed", 2);
3866
4876
                                        }
3867
4877
                                }
3868
4878
                                
3912
4922
        
3913
4923
        # make sure we have a destination directory
3914
4924
        if ( ! -d "$dest" ) {
3915
 
                print_err("sync_rm_dest() needs a valid destination directory as its first argument", 2);
 
4925
                print_err("sync_rm_dest() needs a valid destination directory as its second argument", 2);
3916
4926
                return (0);
3917
4927
        }
3918
4928
        
3932
4942
                        # skip '.' and '..'
3933
4943
                        next if ($node =~ m/^\.\.?$/o);
3934
4944
                        
3935
 
                        # make sure the node we just got is valid (this is highly unlikely to fail)
3936
 
                        my $st = lstat("$dest/$node");
3937
 
                        if (!defined($st)) {
3938
 
                                print_err("Warning! Could not lstat(\"$dest/$node\")", 2);
3939
 
                                next;
3940
 
                        }
3941
 
                        
3942
4945
                        # if this node isn't present in src, delete it
3943
4946
                        if ( ! -e "$src/$node" ) {
3944
 
                                $result = rm_rf("$dest/$node");
3945
 
                                if (0 == $result) {
3946
 
                                        print_err("Warning! Could not delete \"$dest/$node\"", 2);
 
4947
                                # file or symlink
 
4948
                                if ((-l "$dest/$node") or (! -d "$dest/$node")) {
 
4949
                                        $result = unlink("$dest/$node");
 
4950
                                        if (0 == $result) {
 
4951
                                                print_err("Warning! Could not delete \"$dest/$node\"", 2);
 
4952
                                                next;
 
4953
                                        }
 
4954
                                        
 
4955
                                # directory
 
4956
                                } else {
 
4957
                                        $result = rm_rf("$dest/$node");
 
4958
                                        if (0 == $result) {
 
4959
                                                print_err("Warning! Could not delete \"$dest/$node\"", 2);
 
4960
                                        }
 
4961
                                }
 
4962
                                next;
 
4963
                        }
 
4964
                        
 
4965
                        # ok, this also exists in src...
 
4966
                        # theoretically, sync_cp_src_dest() should have caught this already, but better safe than sorry
 
4967
                        # also, symlinks can pretend to be directories, so we have to check for those too
 
4968
                        
 
4969
                        # if src is a file but dest is a directory, we need to recursively remove the dest dir
 
4970
                        if ((-l "$src/$node") or (! -d "$src/$node")) {
 
4971
                                if (-d "$dest/$node") {
 
4972
                                        $result = rm_rf("$dest/$node");
 
4973
                                        if (0 == $result) {
 
4974
                                                print_err("Warning! Could not delete \"$dest/$node\"", 2);
 
4975
                                        }
3947
4976
                                }
3948
4977
                                
3949
 
                        # ok, this also exists in src
3950
 
                        # if it's a directory, let's recurse into it and compare files there
3951
 
                        } elsif ( -d "$src/$node" ) {
 
4978
                        # otherwise, if src is a directory, but dest is a file, remove the file in dest
 
4979
                        } elsif (-d "$src/$node") {
 
4980
                                if ((-l "$dest/$node") or (! -d "$dest/$node")) {
 
4981
                                        $result = unlink("$dest/$node");
 
4982
                                        if (0 == $result) {
 
4983
                                                print_err("Warning! Could not delete \"$dest/$node\"", 2);
 
4984
                                                next;
 
4985
                                        }
 
4986
                                }
 
4987
                        }
 
4988
                        
 
4989
                        # if it's a directory in src, let's recurse into it and compare files there
 
4990
                        if ( -d "$src/$node" ) {
3952
4991
                                $result = sync_rm_dest("$src/$node", "$dest/$node");
3953
4992
                                if ( ! $result ) {
3954
4993
                                        print_err("Warning! Recursion error in sync_rm_dest(\"$src/$node\", \"$dest/$node\")", 2);
3973
5012
        my $st          = undef;
3974
5013
        my $result      = undef;
3975
5014
        
 
5015
        my $link_deref_path     = undef;
 
5016
        
3976
5017
        # make sure it's actually a symlink
3977
5018
        if ( ! -l "$src" ) {
3978
5019
                print_err("Warning! \"$src\" not a symlink in copy_symlink()", 2);
3982
5023
        # make sure we aren't clobbering the destination
3983
5024
        if ( -e "$dest" ) {
3984
5025
                print_err("Warning! \"$dest\" exists!", 2);
 
5026
                return (0);
3985
5027
        }
3986
5028
        
3987
5029
        # LSTAT
3988
5030
        $st = lstat("$src");
3989
5031
        if (!defined($st)) {
3990
 
                print_err("Warning! lstat(\"$src\")", 2);
 
5032
                print_err("Warning! lstat(\"$src\") failed", 2);
3991
5033
                return (0);
3992
5034
        }
3993
5035
        
3994
5036
        # CREATE THE SYMLINK
3995
 
        # print and/or log this if necessary
3996
 
        if (($verbose > 4) or ($loglevel > 4)) {
3997
 
                my $cmd_string = "symlink(\"" . readlink("$src") . "\", \"$dest\");";
3998
 
        
3999
 
                if ($verbose > 4) {
4000
 
                        print_cmd($cmd_string);
4001
 
                } elsif ($loglevel > 4) {
4002
 
                        log_msg($cmd_string, 4);
4003
 
                }
4004
 
        }
4005
 
        $result = symlink(readlink("$src"), "$dest");
4006
 
        if (! $result) {
4007
 
                print_err("Warning! Could not symlink(readlink(\"$src\"), \"$dest\")", 2);
 
5037
        # This is done in two steps:
 
5038
        # Reading/dereferencing the link, and creating a new one
 
5039
        #
 
5040
        # Step 1: READ THE LINK
 
5041
        if (($verbose > 4) or ($loglevel > 4)) {
 
5042
                my $cmd_string = "readlink(\"$src\")\n";
 
5043
                
 
5044
                if ($verbose > 4) {
 
5045
                        print_cmd($cmd_string);
 
5046
                } elsif ($loglevel > 4) {
 
5047
                        log_msg($cmd_string, 4);
 
5048
                }
 
5049
        }
 
5050
        $link_deref_path = readlink("$src");
 
5051
        if (!defined($link_deref_path)) {
 
5052
                print_err("Warning! Could not readlink(\"$src\")", 2);
 
5053
                return (0);
 
5054
        }
 
5055
        #
 
5056
        # Step 2: RECREATE THE LINK
 
5057
        if (($verbose > 4) or ($loglevel > 4)) {
 
5058
                my $cmd_string = "symlink(\"$link_deref_path\", \"$dest\")\n";
 
5059
                
 
5060
                if ($verbose > 4) {
 
5061
                        print_cmd($cmd_string);
 
5062
                } elsif ($loglevel > 4) {
 
5063
                        log_msg($cmd_string, 4);
 
5064
                }
 
5065
        }
 
5066
        $result = symlink("$link_deref_path", "$dest");
 
5067
        if (0 == $result) {
 
5068
                print_err("Warning! Could not symlink(\"$link_deref_path\"), \"$dest\")", 2);
4008
5069
                return (0);
4009
5070
        }
4010
5071
        
4011
5072
        # CHOWN DEST (if root)
4012
5073
        if (0 == $<) {
 
5074
                # make sure the symlink even exists
4013
5075
                if ( -e "$dest" ) {
4014
 
                        # make sure destination is not a symlink
4015
 
                        if ( ! -l "$dest" ) {
4016
 
                                # print and/or log this if necessary
4017
 
                                if (($verbose > 4) or ($loglevel > 4)) {
4018
 
                                        my $cmd_string = "chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");";
4019
 
                                
4020
 
                                        if ($verbose > 4) {
4021
 
                                                print_cmd($cmd_string);
4022
 
                                        } elsif ($loglevel > 4) {
4023
 
                                                log_msg($cmd_string, 4);
4024
 
                                        }
4025
 
                                }
4026
 
                                
4027
 
                                $result = chown($st->uid, $st->gid, "$dest");
4028
 
                                
4029
 
                                if (! $result) {
4030
 
                                        print_err("Warning! Could not chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")", 2);
4031
 
                                        return (0);
4032
 
                                }
 
5076
                        
 
5077
                        # print and/or log this if necessary
 
5078
                        if (($verbose > 4) or ($loglevel > 4)) {
 
5079
                                my $cmd_string = "safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\");";
 
5080
                        
 
5081
                                if ($verbose > 4) {
 
5082
                                        print_cmd($cmd_string);
 
5083
                                } elsif ($loglevel > 4) {
 
5084
                                        log_msg($cmd_string, 4);
 
5085
                                }
 
5086
                        }
 
5087
                        
 
5088
                        $result = safe_chown($st->uid, $st->gid, "$dest");
 
5089
                        
 
5090
                        if (0 == $result) {
 
5091
                                print_err("Warning! Could not safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")", 2);
 
5092
                                return (0);
4033
5093
                        }
4034
5094
                }
4035
5095
        }
4037
5097
        return (1);
4038
5098
}
4039
5099
 
4040
 
# accepts a file permission number from $st->mode (i.e. 33188)
4041
 
# returns a "normal" file permission number (i.e. 644)
 
5100
# accepts a file permission number from $st->mode (e.g., 33188)
 
5101
# returns a "normal" file permission number (e.g., 644)
4042
5102
# do the appropriate bit shifting to get a "normal" UNIX file permission mode
4043
5103
sub get_perms {
4044
5104
        my $raw_mode = shift(@_);
4053
5113
}
4054
5114
 
4055
5115
# accepts return value from the system() command
4056
 
# bitmasks it, and returns the same thing "echo $?" would
 
5116
# bitmasks it, and returns the same thing "echo $?" would from the shell
4057
5117
sub get_retval {
4058
5118
        my $retval = shift(@_);
4059
5119
        
4181
5241
                return (0);
4182
5242
        }
4183
5243
        
4184
 
        # return whatever we got
 
5244
        # if we made it this far, it must have worked
4185
5245
        return (1);
4186
5246
}
4187
5247
 
4267
5327
                print STDERR "ERROR: Could not read config file during version check.\n";
4268
5328
                exit(1);
4269
5329
        }
 
5330
        # right now 1.2 is the only valid version
4270
5331
        if ('1.2' eq $config_version) {
4271
5332
                print "$config_file file is already up to date.\n";
4272
5333
                exit(0);
4273
 
        }
 
5334
                
4274
5335
        # config_version is set, but not to anything we know about
4275
 
        if ('unknown' eq $config_version) {
 
5336
        } elsif ('unknown' eq $config_version) {
4276
5337
                # this is good, it means the config_version was not already set to anything
4277
5338
                # and is a good candidate for the upgrade
 
5339
                
4278
5340
        } else {
4279
5341
                print STDERR "ERROR: config_version is set to unknown version: $config_version.\n";
4280
5342
                exit(1);
4331
5393
 
4332
5394
# accepts array_ref of config file lines
4333
5395
# exits 1 on errors
4334
 
# attempts to backup rsnapshot.conf to rsnapshot.conf.backup(.#)
 
5396
# attempts to backup rsnapshot.conf to rsnapshot.conf.backup.(#)
4335
5397
sub backup_config_file {
4336
5398
        my $lines_ref = shift(@_);
4337
5399
        
4416
5478
        $upgrade_notice .= "#\n";
4417
5479
        
4418
5480
        if (defined($add_rsync_long_args) && (1 == $add_rsync_long_args)) {
 
5481
                $upgrade_notice .= "# In this file, \"rsync_long_args\" was not enabled before the upgrade,\n";
 
5482
                $upgrade_notice .= "# so it has been set to the old default value.\n";
 
5483
        } else {
4419
5484
                $upgrade_notice .= "# In this file, \"rsync_long_args\" was already enabled before the upgrade,\n";
4420
5485
                $upgrade_notice .= "# so it was not changed.\n";
4421
 
        } else {
4422
 
                $upgrade_notice .= "# In this file, \"rsync_long_args\" was not enabled before the upgrade,\n";
4423
 
                $upgrade_notice .= "# so it has been set to the old default value.\n";
4424
5486
        }
4425
5487
        
4426
5488
        $upgrade_notice .= "#\n";
4467
5529
        }
4468
5530
}
4469
5531
 
 
5532
# accepts no arguments
 
5533
# dynamically loads the CPAN Lchown module, if available
 
5534
# sets the global variable $have_lchown
 
5535
sub use_lchown {
 
5536
        if ($verbose >= 5) {
 
5537
                print_msg('require Lchown', 5);
 
5538
        }
 
5539
        eval {
 
5540
                require Lchown;
 
5541
        };
 
5542
        if ($@) {
 
5543
                $have_lchown = 0;
 
5544
                
 
5545
                if ($verbose >= 5) {
 
5546
                        print_msg('Lchown module not found', 5);
 
5547
                }
 
5548
                
 
5549
                return(0);
 
5550
        }
 
5551
        
 
5552
        # if it loaded, see if this OS supports the lchown() system call
 
5553
        {
 
5554
                no strict 'subs';
 
5555
                if (defined(Lchown) && defined(Lchown::LCHOWN_AVAILABLE)) {
 
5556
                        if (1 == Lchown::LCHOWN_AVAILABLE()) {
 
5557
                                $have_lchown = 1;
 
5558
                                
 
5559
                                if ($verbose >= 5) {
 
5560
                                        print_msg('Lchown module loaded successfully', 5);
 
5561
                                }
 
5562
                                
 
5563
                                return(1);
 
5564
                        }
 
5565
                }
 
5566
        }
 
5567
        
 
5568
        if ($verbose >= 5) {
 
5569
                print_msg("Lchown module loaded, but operating system doesn't support lchown()", 5);
 
5570
        }
 
5571
        
 
5572
        return(0);
 
5573
}
 
5574
 
 
5575
# accepts uid, gid, filepath
 
5576
# uses lchown() to change ownership of the file, if possible
 
5577
# returns 1 upon success (or if lchown() not present)
 
5578
# returns 0 on failure
 
5579
sub safe_chown {
 
5580
        my $uid                 = shift(@_);
 
5581
        my $gid                 = shift(@_);
 
5582
        my $filepath    = shift(@_);
 
5583
        
 
5584
        my $result = undef;
 
5585
        
 
5586
        if (!defined($uid) or !defined($gid) or !defined($filepath)) {
 
5587
                print_err("safe_chown() needs uid, gid, and filepath", 2);
 
5588
                return(0);
 
5589
        }
 
5590
        if ( ! -e "$filepath" ) {
 
5591
                print_err("safe_chown() needs a valid filepath (not \"$filepath\")", 2);
 
5592
                return(0);
 
5593
        }
 
5594
        
 
5595
        # if it's a symlink, use lchown() or skip it
 
5596
        if (-l "$filepath") {
 
5597
                # use Lchown
 
5598
                if (1 == $have_lchown) {
 
5599
                        $result = Lchown::lchown($uid, $gid, "$filepath");
 
5600
                        if (!defined($result)) {
 
5601
                                return (0);
 
5602
                        }
 
5603
                        
 
5604
                # we can't safely do anything here, skip it
 
5605
                } else {
 
5606
                        raise_warning();
 
5607
                        
 
5608
                        if ($verbose > 2) {
 
5609
                                print_warn("Could not lchown() symlink \"$filepath\"", 2);
 
5610
                        } elsif ($loglevel > 2) {
 
5611
                                log_warn("Could not lchown() symlink \"$filepath\"", 2);
 
5612
                        }
 
5613
                        
 
5614
                        # we'll still return 1 at the bottom, because we did as well as we could
 
5615
                        # the warning raised will tell the user what happened
 
5616
                }
 
5617
                
 
5618
        # if it's not a symlink, use chown()
 
5619
        } else {
 
5620
                $result = chown($uid, $gid, "$filepath");
 
5621
                if (! $result) {
 
5622
                        return (0);
 
5623
                }
 
5624
        }
 
5625
        
 
5626
        return (1);
 
5627
}
 
5628
 
4470
5629
########################################
4471
5630
###          PERLDOC / POD           ###
4472
5631
########################################
4479
5638
 
4480
5639
=head1 SYNOPSIS
4481
5640
 
4482
 
B<rsnapshot> [B<-vtxqVD>] [B<-c> cfgfile] [command]
 
5641
B<rsnapshot> [B<-vtxqVD>] [B<-c> cfgfile] [command] [args]
4483
5642
 
4484
5643
=head1 DESCRIPTION
4485
5644
 
4540
5699
 
4541
5700
=over 4
4542
5701
 
4543
 
B<config_version>    Config file version (required). Default is 1.2. Make sure this is the first parameter
4544
 
 
4545
 
B<snapshot_root>     Local filesystem path to save all snapshots
4546
 
 
4547
 
B<no_create_root>    If set to 1, rsnapshot won't create snapshot_root directory
4548
 
 
4549
 
B<cmd_rsync>         Full path to rsync (required)
4550
 
 
4551
 
B<cmd_ssh>           Full path to ssh (optional)
4552
 
 
4553
 
B<cmd_cp>            Full path to cp  (optional, but must be GNU version)
 
5702
B<config_version>     Config file version (required). Default is 1.2. Make sure this is the first parameter
 
5703
 
 
5704
B<snapshot_root>      Local filesystem path to save all snapshots
 
5705
 
 
5706
B<include_conf>       Include another file in the configuration at this point.
 
5707
 
 
5708
=over 4
 
5709
 
 
5710
This is recursive, but you may need to be careful about paths when specifying
 
5711
which file to include.  We check to see if the file you have specified is
 
5712
readable, and will yell an error if it isn't.  We recommend using a full
 
5713
path.
 
5714
 
 
5715
B<no_create_root>     If set to 1, rsnapshot won't create snapshot_root directory
 
5716
 
 
5717
B<cmd_rsync>          Full path to rsync (required)
 
5718
 
 
5719
B<cmd_ssh>            Full path to ssh (optional)
 
5720
 
 
5721
B<cmd_cp>             Full path to cp  (optional, but must be GNU version)
4554
5722
 
4555
5723
=over 4
4556
5724
 
4562
5730
 
4563
5731
If cmd_cp is disabled, rsnapshot will use its own built-in function,
4564
5732
native_cp_al() to backup up regular files and directories. This will
4565
 
then be followed up by a seperate call to rsync, to move the special
 
5733
then be followed up by a separate call to rsync, to move the special
4566
5734
files over (assuming there are any).
4567
5735
 
4568
5736
=back
4569
5737
 
4570
 
B<cmd_rm>            Full path to rm  (optional)
4571
 
 
4572
 
B<cmd_logger>        Full path to logger (optional, for syslog support)
4573
 
 
4574
 
B<cmd_du>            Full path to du (optional, for disk usage reports)
4575
 
 
4576
 
B<interval>      [name] [number]
4577
 
 
4578
 
=over 4
4579
 
 
4580
 
"name" refers to the name of this interval (i.e. hourly, daily). "number"
 
5738
B<cmd_rm>             Full path to rm (optional)
 
5739
 
 
5740
B<cmd_logger>         Full path to logger (optional, for syslog support)
 
5741
 
 
5742
B<cmd_du>             Full path to du (optional, for disk usage reports)
 
5743
 
 
5744
B<cmd_rsnapshot_diff> Full path to rsnapshot-diff (optional)
 
5745
 
 
5746
B<cmd_preexec>
 
5747
 
 
5748
=over 4
 
5749
 
 
5750
Full path (plus any arguments) to preexec script (optional).
 
5751
This script will run immediately before a backup operation (but not any
 
5752
rotations).
 
5753
 
 
5754
=back
 
5755
 
 
5756
B<cmd_postexec>
 
5757
 
 
5758
=over 4
 
5759
 
 
5760
Full path (plus any arguments) to postexec script (optional).
 
5761
This script will run immediately after a backup operation (but not any
 
5762
rotations).
 
5763
 
 
5764
=back
 
5765
 
 
5766
B<interval>           [name]   [number]
 
5767
 
 
5768
=over 4
 
5769
 
 
5770
"name" refers to the name of this interval (e.g., hourly, daily). "number"
4581
5771
is the number of snapshots for this type of interval that will be stored.
4582
5772
The value of "name" will be the command passed to B<rsnapshot> to perform
4583
5773
this type of backup.
4600
5790
 
4601
5791
Intervals must be specified in the config file in order, from most
4602
5792
frequent to least frequent. The first entry is the one which will be
4603
 
synced with the backup points. The subsequent intervals (i.e. daily,
 
5793
synced with the backup points. The subsequent intervals (e.g., daily,
4604
5794
weekly, etc) simply rotate, with each higher interval pulling from the
4605
5795
one below it for its .0 directory.
4606
5796
 
4633
5823
 
4634
5824
=back
4635
5825
 
 
5826
B<sync_first          1>
 
5827
 
 
5828
=over 4
 
5829
 
 
5830
sync_first changes the behaviour of rsnapshot. When this is enabled, all calls
 
5831
to rsnapshot with various intervals simply rotate files. All backups are handled
 
5832
by calling rsnapshot with the "sync" argument. The synced files are stored in
 
5833
a ".sync" directory under the snapshot_root.
 
5834
 
 
5835
This allows better recovery in the event that rsnapshot is interrupted in the
 
5836
middle of a sync operation, since the sync step and rotation steps are
 
5837
seperated. This also means that you can easily run "rsnapshot sync" on the
 
5838
command line without fear of forcing all the other directories to rotate up.
 
5839
This benefit comes at the cost of one more snapshot worth of disk space.
 
5840
The default is 0 (off).
 
5841
 
 
5842
=back
 
5843
 
4636
5844
B<verbose             2>
4637
5845
 
4638
5846
=over 4
4656
5864
much data is written to the logfile, if one is being written.
4657
5865
 
4658
5866
The only thing missing from this at the higher levels is the direct output
4659
 
from rsync. We hope to add support for this in a future relase.
 
5867
from rsync. We hope to add support for this in a future release.
4660
5868
 
4661
5869
=back
4662
5870
 
4736
5944
the values to make the program behave like the old version or the current
4737
5945
version. The newer settings are recommended if you're just starting. If
4738
5946
you are upgrading, read the upgrade guide in the INSTALL file in the
4739
 
source distribution for more information. NOTE: In Debian the --relative option is automatically omitted if rsnapshot is used with a pre v1.2 config file to ensure backward compatibility. However it is strongly recommended to update the config file, please read /usr/share/doc/rsnapshot/NEWS.Debian.gz for a starting point on how to do this.
 
5947
source distribution for more information.  NOTE: In Debian the --relative option is automatically omitted if rsnapshot is used with a pre v1.2 config file to ensure backward compatibility. However it is strongly recommended to update the config file, please read /usr/share/doc/rsnapshot/NEWS.Debian.gz for a starting point on how to do this.
4740
5948
 
4741
5949
=back
4742
5950
 
4776
5984
 
4777
5985
Prevents rsync from crossing filesystem partitions. Setting this to a value
4778
5986
of 1 enables this feature. 0 turns it off. This parameter is optional.
4779
 
The default is off.
 
5987
The default is 0 (off).
 
5988
 
 
5989
=back
 
5990
 
 
5991
B<use_lazy_deletes    1>
 
5992
 
 
5993
=over 4
 
5994
 
 
5995
Changes default behavior of rsnapshot and does not initially remove the 
 
5996
oldest snapshot. Instead it moves that directory to "interval".delete, and 
 
5997
continues as normal. Once the backup has been completed, the lockfile will
 
5998
be removed before rsnapshot starts deleting the directory.
 
5999
 
 
6000
Enabling this means that snapshots get taken sooner (since the delete doesn't
 
6001
come first), and any other rsnapshot processes are allowed to start while the
 
6002
final delete is happening. This benefit comes at the cost of one more
 
6003
snapshot worth of disk space. The default is 0 (off).
4780
6004
 
4781
6005
=back
4782
6006
 
4868
6092
=over 4
4869
6093
 
4870
6094
In this example, we specify a script or program to run. This script should simply
4871
 
create files and/or directories in it's current working directory. rsnapshot will
 
6095
create files and/or directories in its current working directory. rsnapshot will
4872
6096
then take that output and move it into the directory specified in the third column.
4873
6097
 
4874
6098
Please note that whatever is in the destination directory will be completely
4908
6132
 
4909
6133
=over 4
4910
6134
 
4911
 
    # THIS IS A COMMENT, REMEMBER TABS MUST SEPERATE ALL ELEMENTS
 
6135
    # THIS IS A COMMENT, REMEMBER TABS MUST SEPARATE ALL ELEMENTS
4912
6136
 
4913
6137
    config_version  1.2
4914
6138
 
5017
6241
not support the -h flag (use -k instead, to see the totals in kilobytes). Other
5018
6242
versions of "du", such as Solaris, may not work at all.
5019
6243
 
 
6244
To check the differences between two directories, call rsnapshot with the "diff"
 
6245
argument, followed by two intervals or directory paths.
 
6246
 
 
6247
For example:
 
6248
 
 
6249
=over 4
 
6250
 
 
6251
B<rsnapshot diff daily.0 daily.1>
 
6252
 
 
6253
B<rsnapshot diff daily.0/localhost/etc daily.1/localhost/etc>
 
6254
 
 
6255
B<rsnapshot diff /.snapshots/daily.0 /.snapshots/daily.1>
 
6256
 
 
6257
=back
 
6258
 
 
6259
This will call the rsnapshot-diff program, which will scan both directories
 
6260
looking for differences (based on hard links).
 
6261
 
 
6262
B<rsnapshot sync>
 
6263
 
 
6264
=over 4
 
6265
 
 
6266
When B<sync_first> is enabled, rsnapshot must first be called with the B<sync>
 
6267
argument, followed by the other usual cron entries. The sync should happen as
 
6268
the lowest, most frequent interval, and right before. For example:
 
6269
 
 
6270
=over 4
 
6271
 
 
6272
B<0 */4 * * *         /usr/local/bin/rsnapshot sync && /usr/local/bin/rsnapshot hourly>
 
6273
 
 
6274
B<50 23 * * *         /usr/local/bin/rsnapshot daily>
 
6275
 
 
6276
B<40 23 1,8,15,22 * * /usr/local/bin/rsnapshot weekly>
 
6277
 
 
6278
B<30 23 1 * *         /usr/local/bin/rsnapshot monthly>
 
6279
 
 
6280
=back
 
6281
 
 
6282
The sync operation simply runs rsync and all backup scripts. In this scenario, all
 
6283
interval calls simply rotate directories, even the lowest interval.
 
6284
 
 
6285
=back
 
6286
 
 
6287
B<rsnapshot sync [dest]>
 
6288
 
 
6289
=over 4
 
6290
 
 
6291
When B<sync_first> is enabled, all sync behaviour happens during an additional
 
6292
sync step (see above). When using the sync argument, it is also possible to specify
 
6293
a backup point destination as an optional parameter. If this is done, only backup
 
6294
points sharing that destination path will be synced.
 
6295
 
 
6296
For example, let's say that example.com is a destination path shared by one or more
 
6297
of your backup points.
 
6298
 
 
6299
=over 4
 
6300
 
 
6301
rsnapshot sync example.com
 
6302
 
 
6303
=back
 
6304
 
 
6305
This command will only sync the files that normally get backed up into example.com.
 
6306
It will NOT get any other backup points with slightly different values (like
 
6307
example.com/etc/, for example). In order to sync example.com/etc, you would need to
 
6308
run rsnapshot again, using example.com/etc as the optional parameter.
 
6309
 
 
6310
=back
 
6311
 
5020
6312
=head1 EXIT VALUES
5021
6313
 
5022
6314
=over 4
5162
6454
 
5163
6455
=over 4
5164
6456
 
5165
 
- Primary author and maintainer of rsnapshot.
 
6457
- Primary author and previous maintainer of rsnapshot.
 
6458
 
 
6459
=back
 
6460
 
 
6461
David Cantrell (B<david@cantrell.org.uk>)
 
6462
 
 
6463
=over 4
 
6464
 
 
6465
- Current maintainer of rsnapshot
 
6466
- Wrote the rsnapshot-diff utility
5166
6467
 
5167
6468
=back
5168
6469
 
5240
6541
 
5241
6542
=back
5242
6543
 
5243
 
Nicolas Kaiser <nikai@nikai.net>
 
6544
Nicolas Kaiser (B<nikai@nikai.net>)
5244
6545
 
5245
6546
=over 4
5246
6547
 
5248
6549
 
5249
6550
=back
5250
6551
 
 
6552
Chris Petersen - (B<http://www.forevermore.net/>)
 
6553
 
 
6554
=over 4
 
6555
 
 
6556
Added cwrsync permanent-share support
 
6557
 
 
6558
=back
 
6559
 
 
6560
Robert Jackson (B<RobertJ@promedicalinc.com>)
 
6561
 
 
6562
=over 4
 
6563
 
 
6564
Added use_lazy_deletes feature
 
6565
 
 
6566
=back
 
6567
 
 
6568
Justin Grote (B<justin@grote.name>)
 
6569
 
 
6570
=over 4
 
6571
 
 
6572
Improved rsync error reporting code
 
6573
 
 
6574
=back
 
6575
 
 
6576
David Keegel (B<djk@cybersource.com.au>)
 
6577
 
 
6578
=over 4
 
6579
 
 
6580
- Fixed race condition in lock file creation, improved error reporting
 
6581
- Allowed remote ssh directory paths starting with "~/" as well as "/"
 
6582
- Fixed a number of other bugs and buglets
 
6583
- Release management for rsnapshot 1.2.9
 
6584
 
 
6585
=back
 
6586
 
 
6587
Anthony Ettinger (B<apwebdesign@yahoo.com>)
 
6588
 
 
6589
=over 4
 
6590
 
 
6591
Wrote the utils/mysqlbackup.pl script
 
6592
 
 
6593
=back
 
6594
 
 
6595
Sherman Boyd
 
6596
 
 
6597
=over 4
 
6598
 
 
6599
Wrote utils/random_file_verify.sh script
 
6600
 
 
6601
=back
 
6602
 
 
6603
William Bear (B<bear@umn.edu>)
 
6604
 
 
6605
=over 4
 
6606
 
 
6607
Wrote the utils/rsnapreport.pl script (pretty summary of rsync stats)
 
6608
 
 
6609
=back
 
6610
 
 
6611
Eric Anderson (B<anderson@centtech.com>)
 
6612
 
 
6613
=over 4
 
6614
 
 
6615
Improvements to utils/rsnapreport.pl.
 
6616
 
 
6617
=back
 
6618
 
5251
6619
=head1 COPYRIGHT
5252
6620
 
5253
6621
Copyright (C) 2003-2005 Nathan Rosenquist
5254
6622
 
5255
6623
Portions Copyright (C) 2002-2005 Mike Rubel, Carl Wilhelm Soderstrom,
5256
 
Ted Zlatanov, Carl Boe, Shane Liebling, Bharat Mediratta, Peter Palfrader
 
6624
Ted Zlatanov, Carl Boe, Shane Liebling, Bharat Mediratta, Peter Palfrader,
 
6625
Nicolas Kaiser, David Cantrell, Chris Petersen, Robert Jackson, Justin Grote,
 
6626
David Keegel
5257
6627
 
5258
6628
This man page is distributed under the same license as rsnapshot:
5259
6629
the GPL (see below).
5268
6638
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
5269
6639
GNU General Public License for more details.
5270
6640
 
5271
 
You should have received a copy of the GNU General Public License
5272
 
along with this program; if not, write to the Free Software
5273
 
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
6641
You should have received a copy of the GNU General Public License along
 
6642
with this program; if not, write to the Free Software Foundation, Inc.,
 
6643
51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
5274
6644
 
5275
6645
=cut
5276
6646