Commit 76181461 authored by Wayne Davison's avatar Wayne Davison

Added a fully atomic update if the user has setup a symlink

to a *-1 or *-2 directory.  A few other minor improvements.
parent 2c11e80e
......@@ -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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment