6
6
# by Nathan Rosenquist #
8
# Based on code originally by Mike Rubel #
9
# http://www.mikerubel.org/computers/rsync_snapshots/ #
7
# now maintained by David Cantrell #
11
9
# The official rsnapshot website is located at #
12
10
# http://www.rsnapshot.org/ #
12
# Copyright (C) 2003-2005 Nathan Rosenquist #
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 #
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. #
24
# Based on code originally by Mike Rubel #
25
# http://www.mikerubel.org/computers/rsync_snapshots/ #
18
27
########################################################################
29
# $Id: rsnapshot-program.pl,v 1.339 2006/05/18 10:12:37 djk20 Exp $
20
31
# tabstops are set to 4 spaces
21
32
# in vi, do: set ts=4 sw=4
266
298
See the GNU General Public License for details.
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).
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.
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
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];
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");
698
$config_vars{'cmd_preexec'} = $full_script;
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
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];
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");
720
$config_vars{'cmd_postexec'} = $full_script;
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;
733
config_err($file_line_num, "$line - $value is not executable");
590
739
if ($var eq 'interval') {
591
740
# check if interval is blank
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); }
2343
2555
$str =~ s/\/+$//;
2348
# accepts an interval_data_ref
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(@_);
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
2352
2578
sub handle_interval {
2353
my $id_ref = shift(@_);
2355
if (!defined($id_ref)) { bail('id_ref not defined in handle_interval()'); }
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 );
2579
my $cmd = shift(@_);
2581
if (!defined($cmd)) { bail('cmd not defined in handle_interval()'); }
2583
my $id_ref = get_interval_data( $cmd );
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'};
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" ) {
2602
# file (should never be here)
2603
} elsif ( -f "$config_vars{'snapshot_root'}/$interval.delete" ) {
2607
# directory (this is what we're expecting)
2608
} elsif ( -d "$config_vars{'snapshot_root'}/$interval.delete" ) {
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");
2617
# we don't use if (-e $dir), because that fails for invalid symlinks
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/");
2624
$result = rm_rf( "$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/" );
2626
bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.delete/\")");
2630
# this is a file or symlink
2632
print_cmd("rm -f $config_vars{'snapshot_root'}/$interval.delete");
2634
$result = unlink("$config_vars{'snapshot_root'}/$interval.delete");
2636
bail("Could not remove \"$config_vars{'snapshot_root'}/$interval.delete\" in handle_interval()");
2643
# handle toggling between sync_first being enabled and disabled
2645
# link_dest is enabled
2646
if (1 == $link_dest) {
2648
# sync_first is enabled
2649
if ($config_vars{'sync_first'}) {
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
2656
# sync_first is disabled
2658
# if the sync directory is still here after sync_first is disabled, delete it
2659
if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
2661
display_rm_rf("$config_vars{'snapshot_root'}/.sync/");
2663
$result = rm_rf( "$config_vars{'snapshot_root'}/.sync/" );
2665
bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/.sync/\")");
2671
# link_dest is disabled
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" ) {
2679
# cp_al() will create the directory for us
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";
2685
display_cp_al( "$interval_0", "$sync_dir" );
2687
$result = cp_al( "$interval_0", "$sync_dir" );
2689
bail("Error! cp_al(\"$interval_0\", \"$sync_dir\")");
2694
# sync_first is disabled
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/");
2700
$result = rm_rf( "$config_vars{'snapshot_root'}/.sync/" );
2702
bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/.sync/\")");
2710
# now that the preliminaries are out of the way, the main backups happen here
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') {
2719
backup_lowest_interval( $id_ref );
2720
exec_cmd_postexec();
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
2725
if ($config_vars{'sync_first'}) {
2726
rotate_lowest_snapshots( $$id_ref{'interval'} );
2729
rotate_lowest_snapshots( $$id_ref{'interval'} );
2730
backup_lowest_interval( $id_ref );
2731
exec_cmd_postexec();
2735
# just rotate the higher intervals
2362
2737
# this is not the most frequent unit, just rotate
2363
2738
rotate_higher_interval( $id_ref );
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
2749
display_rm_rf("$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/");
2751
my $result = rm_rf( "$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/" );
2753
bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$$id_ref{'interval'}.delete/\")\n");
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)
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(@_);
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'); }
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');
2386
# rotate the higher directories in this interval
2388
rotate_lowest_snapshots( $$id_ref{'interval'} );
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');
2781
my $sync_dest_matches = 0;
2782
my $sync_dest_dir = undef;
2784
# if we're trying to sync only certain directories, remember the path to match
2786
$sync_dest_dir = $ARGV[1];
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) {
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'})) ) {
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 );
2802
# if we have a match, sync this entry
2803
if ($avail_path eq $req_path) {
2805
if ($$bp_ref{'src'}) {
2806
rsync_backup_point( $$id_ref{'interval'}, $bp_ref );
2809
} elsif ($$bp_ref{'script'}) {
2810
exec_backup_script( $$id_ref{'interval'}, $bp_ref );
2813
# ok, we got at least one dest match
2814
$sync_dest_matches++;
2817
# this is a normal operation, either a sync or a lowest interval sync/rotate
2820
if ($$bp_ref{'src'}) {
2821
rsync_backup_point( $$id_ref{'interval'}, $bp_ref );
2824
} elsif ($$bp_ref{'script'}) {
2825
exec_backup_script( $$id_ref{'interval'}, $bp_ref );
2402
2829
# this should never happen
2404
2831
bail('invalid backup point data in backup_lowest_interval()');
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\"");
2409
2841
# rollback failed backups
2410
2842
rollback_failed_backups( $$id_ref{'interval'} );
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'} );
2416
2848
# accepts $interval
2434
2866
my $prev_interval = $$id_ref{'prev_interval'};
2435
2867
my $prev_interval_max = $$id_ref{'prev_interval_max'};
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/");
2441
my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
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) {
2876
"$config_vars{'snapshot_root'}/$interval.$interval_max/",
2877
"$config_vars{'snapshot_root'}/$interval.delete/"
2881
my $result = safe_rename(
2882
"$config_vars{'snapshot_root'}/$interval.$interval_max",
2883
"$config_vars{'snapshot_root'}/$interval.delete"
2887
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\", \"";
2888
$errstr .= "$config_vars{'snapshot_root'}/$interval.delete/\")";
2893
# otherwise the default is to delete the oldest directory for this interval
2895
display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
2898
my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
2900
bail("Error! rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\")\n");
2472
# .0 and .1 require more attention:
2473
if ( (-d "$config_vars{'snapshot_root'}/$interval.0") && ($interval_max > 0) ) {
2931
# .0 and .1 require more attention, especially now with link_dest and sync_first
2933
# sync_first enabled
2934
if ($config_vars{'sync_first'}) {
2935
# we move .0 to .1 no matter what (assuming it exists)
2937
if ( -d "$config_vars{'snapshot_root'}/$interval.0/" ) {
2939
"$config_vars{'snapshot_root'}/$interval.0/",
2940
"$config_vars{'snapshot_root'}/$interval.1/"
2944
my $result = safe_rename(
2945
"$config_vars{'snapshot_root'}/$interval.0",
2946
"$config_vars{'snapshot_root'}/$interval.1"
2950
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", \"";
2951
$errstr .= "$config_vars{'snapshot_root'}/$interval.1/\")";
2957
# if we're using rsync --link-dest, we need to mv sync to .0 now
2958
if (1 == $link_dest) {
2961
if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
2963
"$config_vars{'snapshot_root'}/.sync/",
2964
"$config_vars{'snapshot_root'}/$interval.0/"
2968
my $result = safe_rename(
2969
"$config_vars{'snapshot_root'}/.sync",
2970
"$config_vars{'snapshot_root'}/$interval.0"
2974
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/.sync/\", \"";
2975
$errstr .= "$config_vars{'snapshot_root'}/$interval.0/\")";
2981
# otherwise, we hard link (except for directories, symlinks, and special files) sync to .0
2985
if ( -d "$config_vars{'snapshot_root'}/.sync/" ) {
2986
display_cp_al( "$config_vars{'snapshot_root'}/.sync/", "$config_vars{'snapshot_root'}/$interval.0/" );
2988
$result = cp_al( "$config_vars{'snapshot_root'}/.sync", "$config_vars{'snapshot_root'}/$interval.0" );
2990
bail("Error! cp_al(\"$config_vars{'snapshot_root'}/.sync\", \"$config_vars{'snapshot_root'}/$interval.0\")");
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) ) {
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/");
2480
3001
# move .0 to .1
2482
my $result = safe_rename(
2483
"$config_vars{'snapshot_root'}/$interval.0",
2484
"$config_vars{'snapshot_root'}/$interval.1"
2488
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
2489
$errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
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/");
3007
my $result = safe_rename(
3008
"$config_vars{'snapshot_root'}/$interval.0",
3009
"$config_vars{'snapshot_root'}/$interval.1"
3013
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
3014
$errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
2493
3019
# otherwise, we hard link (except for directories, symlinks, and special files) .0 over to .1
2495
3021
# call generic cp_al() subroutine
2496
display_cp_al( "$config_vars{'snapshot_root'}/$interval.0/", "$config_vars{'snapshot_root'}/$interval.1/" );
2500
"$config_vars{'snapshot_root'}/$interval.0/",
2501
"$config_vars{'snapshot_root'}/$interval.1/"
2505
$errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
2506
$errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
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" );
3026
"$config_vars{'snapshot_root'}/$interval.0/",
3027
"$config_vars{'snapshot_root'}/$interval.1/"
3031
$errstr .= "Error! cp_al(\"$config_vars{'snapshot_root'}/$interval.0/\", ";
3032
$errstr .= "\"$config_vars{'snapshot_root'}/$interval.1/\")";
2534
3062
my @rsync_long_args_stack = undef;
2535
3063
my $src = undef;
2536
3064
my $result = undef;
3065
my $using_relative = 0;
3067
if (defined($$bp_ref{'src'})) {
3068
$src = remove_trailing_slash( "$$bp_ref{'src'}" );
3069
$src = add_slashdot_if_root( "$src" );
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;
3077
# start looking for link_dest targets at interval.$start_num
3080
my $sync_dir_was_present = 0;
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') {
3086
# remember now if the .sync directory exists
3087
if ( -d "$config_vars{'snapshot_root'}/.sync" ) {
3088
$sync_dir_was_present = 1;
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++) {
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;
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
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
2645
3214
# make rsync quiet if we're not running EXTRA verbose
2646
3215
if ($verbose < 4) { $rsync_short_args .= 'q'; }
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'; }
2648
3222
# this should have already been validated once, but better safe than sorry
2650
3224
bail("Could not understand source \"$$bp_ref{'src'}\" in backup_lowest_interval()");
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)) {
3233
# make sure the directory exists
3234
if ( -d "$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}" ) {
3236
# we don't use link_dest if we already synced once to this directory
3237
if ($sync_dir_was_present) {
3239
# skip --link-dest, this is the second time the sync has been run, because the .sync directory already exists
3241
# default: push link_dest arguments onto cmd stack
3244
@rsync_long_args_stack,
3245
"--link-dest=$config_vars{'snapshot_root'}/$interval_link_dest.$interval_num_link_dest/$$bp_ref{'dest'}"
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;
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.
3305
if (1 == $using_relative) {
3306
$src = remove_trailing_slash( "$$bp_ref{'src'}" );
3307
$src = add_slashdot_if_root( "$src" );
3309
# no matter what, we need a source path
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'} . '/';
3317
$src = $$bp_ref{'src'};
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
3605
# accepts and runs an arbitrary command string
3606
# returns the exit value of the command
3608
my $cmd = shift(@_);
3613
if (!defined($cmd) or ('' eq $cmd)) {
3614
print_err("Warning! Command \"$cmd\" not found", 2);
3620
$return = system($cmd);
3621
if (!defined($return)) {
3622
print_err("Warning! exec_cmd(\"$cmd\") returned undef", 2);
3625
# bitmask to get the real return value
3626
$retval = get_retval($return);
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 {
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'}" );
3642
if (!defined($retval)) {
3643
print_err("$config_vars{'cmd_preexec'} not found", 2);
3647
print_warn("cmd_preexec \"$config_vars{'cmd_preexec'}\" returned $retval", 2);
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 {
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'}" );
3663
if (!defined($retval)) {
3664
print_err("$config_vars{'cmd_postexec'} not found", 2);
3668
print_warn("cmd_postexec \"$config_vars{'cmd_postexec'}\" returned $retval", 2);
2943
3674
# accepts interval, backup_point_ref
2944
3675
# returns nothing
2945
3676
# exits the program if it encounters a fatal error
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
2987
3724
sub rollback_failed_backups {
2988
3725
my $interval = shift(@_);
2991
my $rsync_short_args = $default_rsync_short_args;
2993
3727
if (!defined($interval)) { bail('interval not defined in rollback_failed_backups()'); }
3730
my $rsync_short_args = $default_rsync_short_args;
3732
# handle 'sync' case
3736
if ($interval eq 'sync') {
3737
$interval_src = $intervals[0]->{'interval'} . '.0';
3738
$interval_dest = '.sync';
3740
$interval_src = "$interval.1";
3741
$interval_dest = "$interval.0";
2995
3744
# extra verbose?
2996
3745
if ($verbose > 3) { $rsync_short_args .= 'v'; }
3006
3755
syslog_warn("Rolling back \"$rollback_point\"");
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");
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
3772
# if we're doing a 'sync', then instead of .1 and .0, it's lowest.0 and .sync
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"
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"
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\")";
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(@_);
3047
3798
if (!defined($interval)) { bail('interval not defined in touch_interval()'); }
3802
if ($interval eq 'sync') {
3803
$interval_dir = '.sync';
3805
$interval_dir = $interval . '.0';
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/");
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/\");");
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.
3065
3824
# rotates older dirs within this interval, and hard links
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/");
3094
my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
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) {
3854
"$config_vars{'snapshot_root'}/$interval.$interval_max/ ",
3855
"$config_vars{'snapshot_root'}/$interval.delete/"
3859
my $result = safe_rename(
3860
"$config_vars{'snapshot_root'}/$interval.$interval_max",
3861
("$config_vars{'snapshot_root'}/$interval.delete")
3865
$errstr .= "Error! safe_rename(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\", \"";
3866
$errstr .= "$config_vars{'snapshot_root'}/$interval.delete/\")";
3871
display_rm_rf("$config_vars{'snapshot_root'}/$interval.$interval_max/");
3874
my $result = rm_rf( "$config_vars{'snapshot_root'}/$interval.$interval_max/" );
3876
bail("Could not rm_rf(\"$config_vars{'snapshot_root'}/$interval.$interval_max/\");");
3100
3882
print_msg("$config_vars{'snapshot_root'}/$interval.$interval_max not present (yet), nothing to delete", 4);
3387
4182
print_err("Warning! Recursion error in native_cp_al(\"$src/$node\", \"$dest/$node\")", 2);
3391
# rsync_cleanup_after_native_cp_al() will take care of the files we can't handle here
3394
} elsif ( -p "$src/$node" ) {
3395
# print_err("Warning! Ignoring FIFO $src/$node", 2);
3398
} elsif ( -S "$src/$node" ) {
3399
# print_err("Warning! Ignoring socket: $src/$node", 2);
3402
} elsif ( -b "$src/$node" ) {
3403
# print_err("Warning! Ignoring special block file: $src/$node", 2);
3406
} elsif ( -c "$src/$node" ) {
3407
# print_err("Warning! Ignoring special character file: $src/$node", 2);
4187
## rsync_cleanup_after_native_cp_al() will take care of the files we can't handle here
4190
#} elsif ( -p "$src/$node" ) {
4191
# # print_err("Warning! Ignoring FIFO $src/$node", 2);
4194
#} elsif ( -S "$src/$node" ) {
4195
# # print_err("Warning! Ignoring socket: $src/$node", 2);
4198
#} elsif ( -b "$src/$node" ) {
4199
# # print_err("Warning! Ignoring special block file: $src/$node", 2);
4202
#} elsif ( -c "$src/$node" ) {
4203
# # print_err("Warning! Ignoring special character file: $src/$node", 2);
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';
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
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");
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'};
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";
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
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/")) {
4540
if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0" ) ) {
4541
$cmd_args[0] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0";
4545
$cmd_args[1] = "$config_vars{'snapshot_root'}/.sync";
4547
# sync_first is not enabled, or .sync doesn't exist
4550
if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".1" ) ) {
4551
$cmd_args[0] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".1";
4554
if ( -d ("$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0" ) ) {
4555
$cmd_args[1] = "$config_vars{'snapshot_root'}/" . $intervals[0]->{'interval'} . ".0";
4559
# if we got some command line arguments, loop through twice and figure out what they mean
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
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];
4569
# if this directory exists locally, it must be local
4570
} elsif ( -e "$paths_in[$i]" ) {
4571
$cmd_args[$i] = $paths_in[$i];
4574
} elsif (is_valid_local_abs_path( "$paths_in[$i]" )) {
4575
$cmd_args[$i] = $paths_in[$i];
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]";
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";
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]);
4594
# increase verbosity (by possibly sticking a verbose flag in as the first argument)
4597
if ($verbose >= 5) {
4598
unshift(@cmd_args, '-V');
4600
} elsif ($verbose >= 4) {
4601
unshift(@cmd_args, '-v');
4604
} elsif ($verbose >= 3) {
4605
unshift(@cmd_args, '-vi');
4608
# run rsnapshot-diff
4609
if (defined($verbose) && ($verbose >= 3)) {
4610
print wrap_cmd(("$cmd_rsnapshot_diff " . join(' ', @cmd_args))), "\n\n";
4613
$retval = system($cmd_rsnapshot_diff, @cmd_args);
4617
# exit showing error
4618
print STDERR "Error while calling $cmd_rsnapshot_diff\n";
4622
# test was successful
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 :)
3811
4776
# skip '.' and '..'
3812
4777
next if ($node =~ m/^\.\.?$/o);
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);
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");
4790
print_err("Warning! Could not unlink(\"$dest/$node\")", 2);
4794
# nuke the destination directory
4796
$result = rm_rf("$dest/$node");
4798
print_err("Could not rm_rf(\"$dest/$node\")", 2);
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);
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");
4818
print_err("Warning! unlink(\"$dest/$node\") failed", 2);
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);
3837
4830
# if it's a file...
3838
4831
} elsif ( -f "$src/$node" ) {
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");
4836
print_err("Warning! unlink(\"$dest/$node\") failed", 2);
4841
# if dest is a directory, we need to wipe it out first
4842
if ( -d "$dest/$node" ) {
4843
$result = rm_rf("$dest/$node");
4845
print_err("Could not rm_rf(\"$dest/$node\")", 2);
4850
# if dest (still) exists, check for differences
3841
4851
if ( -e "$dest/$node" ) {
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);
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);
3932
4942
# skip '.' and '..'
3933
4943
next if ($node =~ m/^\.\.?$/o);
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);
3942
4945
# if this node isn't present in src, delete it
3943
4946
if ( ! -e "$src/$node" ) {
3944
$result = rm_rf("$dest/$node");
3946
print_err("Warning! Could not delete \"$dest/$node\"", 2);
4948
if ((-l "$dest/$node") or (! -d "$dest/$node")) {
4949
$result = unlink("$dest/$node");
4951
print_err("Warning! Could not delete \"$dest/$node\"", 2);
4957
$result = rm_rf("$dest/$node");
4959
print_err("Warning! Could not delete \"$dest/$node\"", 2);
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
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");
4974
print_err("Warning! Could not delete \"$dest/$node\"", 2);
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");
4983
print_err("Warning! Could not delete \"$dest/$node\"", 2);
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);
3982
5023
# make sure we aren't clobbering the destination
3983
5024
if ( -e "$dest" ) {
3984
5025
print_err("Warning! \"$dest\" exists!", 2);
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);
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\");";
4000
print_cmd($cmd_string);
4001
} elsif ($loglevel > 4) {
4002
log_msg($cmd_string, 4);
4005
$result = symlink(readlink("$src"), "$dest");
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
5040
# Step 1: READ THE LINK
5041
if (($verbose > 4) or ($loglevel > 4)) {
5042
my $cmd_string = "readlink(\"$src\")\n";
5045
print_cmd($cmd_string);
5046
} elsif ($loglevel > 4) {
5047
log_msg($cmd_string, 4);
5050
$link_deref_path = readlink("$src");
5051
if (!defined($link_deref_path)) {
5052
print_err("Warning! Could not readlink(\"$src\")", 2);
5056
# Step 2: RECREATE THE LINK
5057
if (($verbose > 4) or ($loglevel > 4)) {
5058
my $cmd_string = "symlink(\"$link_deref_path\", \"$dest\")\n";
5061
print_cmd($cmd_string);
5062
} elsif ($loglevel > 4) {
5063
log_msg($cmd_string, 4);
5066
$result = symlink("$link_deref_path", "$dest");
5068
print_err("Warning! Could not symlink(\"$link_deref_path\"), \"$dest\")", 2);
4011
5072
# CHOWN DEST (if root)
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\");";
4021
print_cmd($cmd_string);
4022
} elsif ($loglevel > 4) {
4023
log_msg($cmd_string, 4);
4027
$result = chown($st->uid, $st->gid, "$dest");
4030
print_err("Warning! Could not chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")", 2);
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\");";
5082
print_cmd($cmd_string);
5083
} elsif ($loglevel > 4) {
5084
log_msg($cmd_string, 4);
5088
$result = safe_chown($st->uid, $st->gid, "$dest");
5091
print_err("Warning! Could not safe_chown(" . $st->uid . ", " . $st->gid . ", \"$dest\")", 2);
5532
# accepts no arguments
5533
# dynamically loads the CPAN Lchown module, if available
5534
# sets the global variable $have_lchown
5536
if ($verbose >= 5) {
5537
print_msg('require Lchown', 5);
5545
if ($verbose >= 5) {
5546
print_msg('Lchown module not found', 5);
5552
# if it loaded, see if this OS supports the lchown() system call
5555
if (defined(Lchown) && defined(Lchown::LCHOWN_AVAILABLE)) {
5556
if (1 == Lchown::LCHOWN_AVAILABLE()) {
5559
if ($verbose >= 5) {
5560
print_msg('Lchown module loaded successfully', 5);
5568
if ($verbose >= 5) {
5569
print_msg("Lchown module loaded, but operating system doesn't support lchown()", 5);
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
5580
my $uid = shift(@_);
5581
my $gid = shift(@_);
5582
my $filepath = shift(@_);
5586
if (!defined($uid) or !defined($gid) or !defined($filepath)) {
5587
print_err("safe_chown() needs uid, gid, and filepath", 2);
5590
if ( ! -e "$filepath" ) {
5591
print_err("safe_chown() needs a valid filepath (not \"$filepath\")", 2);
5595
# if it's a symlink, use lchown() or skip it
5596
if (-l "$filepath") {
5598
if (1 == $have_lchown) {
5599
$result = Lchown::lchown($uid, $gid, "$filepath");
5600
if (!defined($result)) {
5604
# we can't safely do anything here, skip it
5609
print_warn("Could not lchown() symlink \"$filepath\"", 2);
5610
} elsif ($loglevel > 2) {
5611
log_warn("Could not lchown() symlink \"$filepath\"", 2);
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
5618
# if it's not a symlink, use chown()
5620
$result = chown($uid, $gid, "$filepath");
4470
5629
########################################
4471
5630
### PERLDOC / POD ###
4472
5631
########################################
4543
B<config_version> Config file version (required). Default is 1.2. Make sure this is the first parameter
4545
B<snapshot_root> Local filesystem path to save all snapshots
4547
B<no_create_root> If set to 1, rsnapshot won't create snapshot_root directory
4549
B<cmd_rsync> Full path to rsync (required)
4551
B<cmd_ssh> Full path to ssh (optional)
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
5704
B<snapshot_root> Local filesystem path to save all snapshots
5706
B<include_conf> Include another file in the configuration at this point.
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
5715
B<no_create_root> If set to 1, rsnapshot won't create snapshot_root directory
5717
B<cmd_rsync> Full path to rsync (required)
5719
B<cmd_ssh> Full path to ssh (optional)
5721
B<cmd_cp> Full path to cp (optional, but must be GNU version)
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).
4570
B<cmd_rm> Full path to rm (optional)
4572
B<cmd_logger> Full path to logger (optional, for syslog support)
4574
B<cmd_du> Full path to du (optional, for disk usage reports)
4576
B<interval> [name] [number]
4580
"name" refers to the name of this interval (i.e. hourly, daily). "number"
5738
B<cmd_rm> Full path to rm (optional)
5740
B<cmd_logger> Full path to logger (optional, for syslog support)
5742
B<cmd_du> Full path to du (optional, for disk usage reports)
5744
B<cmd_rsnapshot_diff> Full path to rsnapshot-diff (optional)
5750
Full path (plus any arguments) to preexec script (optional).
5751
This script will run immediately before a backup operation (but not any
5760
Full path (plus any arguments) to postexec script (optional).
5761
This script will run immediately after a backup operation (but not any
5766
B<interval> [name] [number]
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.
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.
6244
To check the differences between two directories, call rsnapshot with the "diff"
6245
argument, followed by two intervals or directory paths.
6251
B<rsnapshot diff daily.0 daily.1>
6253
B<rsnapshot diff daily.0/localhost/etc daily.1/localhost/etc>
6255
B<rsnapshot diff /.snapshots/daily.0 /.snapshots/daily.1>
6259
This will call the rsnapshot-diff program, which will scan both directories
6260
looking for differences (based on hard links).
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:
6272
B<0 */4 * * * /usr/local/bin/rsnapshot sync && /usr/local/bin/rsnapshot hourly>
6274
B<50 23 * * * /usr/local/bin/rsnapshot daily>
6276
B<40 23 1,8,15,22 * * /usr/local/bin/rsnapshot weekly>
6278
B<30 23 1 * * /usr/local/bin/rsnapshot monthly>
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.
6287
B<rsnapshot sync [dest]>
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.
6296
For example, let's say that example.com is a destination path shared by one or more
6297
of your backup points.
6301
rsnapshot sync example.com
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.
5020
6312
=head1 EXIT VALUES
6552
Chris Petersen - (B<http://www.forevermore.net/>)
6556
Added cwrsync permanent-share support
6560
Robert Jackson (B<RobertJ@promedicalinc.com>)
6564
Added use_lazy_deletes feature
6568
Justin Grote (B<justin@grote.name>)
6572
Improved rsync error reporting code
6576
David Keegel (B<djk@cybersource.com.au>)
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
6587
Anthony Ettinger (B<apwebdesign@yahoo.com>)
6591
Wrote the utils/mysqlbackup.pl script
6599
Wrote utils/random_file_verify.sh script
6603
William Bear (B<bear@umn.edu>)
6607
Wrote the utils/rsnapreport.pl script (pretty summary of rsync stats)
6611
Eric Anderson (B<anderson@centtech.com>)
6615
Improvements to utils/rsnapreport.pl.
5251
6619
=head1 COPYRIGHT
5253
6621
Copyright (C) 2003-2005 Nathan Rosenquist
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,
5258
6628
This man page is distributed under the same license as rsnapshot:
5259
6629
the GPL (see below).