diff options
author | Wayne Davison <wayned@samba.org> | 2008-10-11 09:29:23 -0700 |
---|---|---|
committer | Wayne Davison <wayned@samba.org> | 2008-10-11 09:29:23 -0700 |
commit | f3721ed133cc2464aba94386f4ef2911c5f53ed7 (patch) | |
tree | 69c0005254d87661c4e7577e3085e2429a562667 | |
parent | b1220d62f49ce39d924fc916235b30916c7da6b7 (diff) | |
download | rsync-f3721ed133cc2464aba94386f4ef2911c5f53ed7.tar.gz rsync-f3721ed133cc2464aba94386f4ef2911c5f53ed7.tar.bz2 rsync-f3721ed133cc2464aba94386f4ef2911c5f53ed7.zip |
Added a fully atomic update if the user has setup a symlink
to a *-1 or *-2 directory. A few other minor improvements.
-rwxr-xr-x | support/atomic-rsync | 80 |
1 files changed, 56 insertions, 24 deletions
diff --git a/support/atomic-rsync b/support/atomic-rsync index 33a493c2..9e620cfe 100755 --- a/support/atomic-rsync +++ b/support/atomic-rsync @@ -6,35 +6,49 @@ # more details and some important caveats!** use strict; +use warnings; use Cwd 'abs_path'; my $RSYNC_PROG = '/usr/bin/rsync'; my $RM_PROG = '/bin/rm'; my $dest_dir = $ARGV[-1]; -usage(1) if $dest_dir eq '' || $dest_dir =~ /^--/; +&usage if !defined $dest_dir || $dest_dir =~ /(^-|^$)/ || grep(/^--help/, @ARGV); +$dest_dir =~ s{(?<=.)/+$} {}; if (!-d $dest_dir) { - print STDERR "$dest_dir is not a directory.\n\n"; - usage(1); + die "$dest_dir is not a directory.\nUse --help for help.\n"; } -if (@_ = grep(/^--(link|compare)-dest/, @ARGV)) { +if (@_ = grep(/^--[a-z]+-dest\b/, @ARGV)) { $_ = join(' or ', @_); - print STDERR "You may not use $_ as an rsync option.\n\n"; - usage(1); + die "You cannot use the $_ option with atomic-rsync.\nUse --help for help.\n"; } +my $symlink_content = readlink $dest_dir; # undef when a real dir + +my $dest_arg = $dest_dir; +# This gives us the real destination dir, with all symlinks dereferenced. $dest_dir = abs_path($dest_dir); if ($dest_dir eq '/') { - print STDERR 'You must not use "/" as the destination directory.', "\n\n"; - usage(1); + die qq|You must not use "/" as the destination directory.\nUse --help for help.\n|; +} + +my($old_dir, $new_dir); +if (defined $symlink_content && $dest_dir =~ /-([12])$/) { + my $num = 3 - $1; + $old_dir = undef; + ($new_dir = $dest_dir) =~ s/-[12]$/-$num/; + $symlink_content =~ s/-[12]$/-$num/; +} else { + $old_dir = "$dest_dir~old~"; + $new_dir = "$dest_dir~new~"; } -my $old_dir = "$dest_dir~old~"; -my $new_dir = $ARGV[-1] = "$dest_dir~new~"; +$ARGV[-1] = "$new_dir/"; -system($RM_PROG, '-rf', $old_dir) if -d $old_dir; +system($RM_PROG, '-rf', $old_dir) if defined $old_dir && -d $old_dir; +system($RM_PROG, '-rf', $new_dir) if -d $new_dir; if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) { if ($? == -1) { @@ -48,17 +62,30 @@ if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) { exit $?; } +if (!defined $old_dir) { + atomic_symlink($symlink_content, $dest_arg); + exit; +} + rename($dest_dir, $old_dir) or die "Unable to rename $dest_dir to $old_dir: $!"; rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!"; exit; +sub atomic_symlink +{ + my($target, $link) = @_; + my $newlink = "$link~new~"; + + unlink($newlink); # Just in case + symlink($target, $newlink) or die "Unable to symlink $newlink -> $target: $!\n"; + rename($newlink, $link) or die "Unable to rename $newlink to $link: $!\n"; +} + sub usage { - my($ret) = @_; - my $fh = $ret ? *STDERR : *STDOUT; - print $fh <<EOT; + die <<EOT; Usage: atomic-rsync [RSYNC-OPTIONS] HOST:/SOURCE/DIR/ /DEST/DIR/ atomic-rsync [RSYNC-OPTIONS] HOST::MOD/DIR/ /DEST/DIR/ @@ -67,24 +94,29 @@ creating a new hierarchy (using hard-links to leverage the existing files), and then swapping the new hierarchy into place. You must be pulling files to a local directory, and that directory must already exist. For example: + mkdir /local/files-1 + ln -s files-1 /local/files atomic-rsync -av host:/remote/files/ /local/files/ -This would make the transfer to the directory /local/files~new~ and then -swap out /local/files at the end of the transfer by renaming it to -/local/files~old~ and putting the new directory into its place. The -/local/files~old~ directory will be preserved until the next update, at -which point it will be deleted. +If /local/files is a symlink to a directory that ends in -1 or -2, the +copy will go to the alternate suffix and the symlink will be changed to +point to the new dir. This is a fully atomic update. If the destination +is not a symlink (or not a symlink to a *-1 or a *-2 directory), this +will instead create a directory with "~new~" suffixed, move the current +directory to a name with "~old~" suffixed, and then move the ~new~ +directory to the original destination name (this double rename is not +fully atomic, but is rapid). In both cases, the prior destintaion +directory will be preserved until the next update, at which point it +will be deleted. -Do NOT specify this command: +In all likelihood, you do NOT want to specify this command: atomic-rsync -av host:/remote/files /local/ ... UNLESS you want the entire /local dir to be swapped out! See the "rsync" command for its list of options. You may not use the ---link-dest or --compare-dest options (since this script uses --link-dest -to make the transfer efficient). Also, the destination directory cannot -be "/". +--link-dest, --compare-dest, or --copy-dest options (since this script +uses --link-dest to make the transfer efficient). EOT - exit $ret; } |