mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-01-14 01:47:59 -05:00
123 lines
3.9 KiB
Perl
Executable File
123 lines
3.9 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#
|
|
# This script lets you update a hierarchy of files in an atomic way by
|
|
# first creating a new hierarchy using rsync's --link-dest option, and
|
|
# then swapping the hierarchy into place. **See the usage message for
|
|
# 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 if !defined $dest_dir || $dest_dir =~ /(^-|^$)/ || grep(/^--help/, @ARGV);
|
|
$dest_dir =~ s{(?<=.)/+$} {};
|
|
|
|
if (!-d $dest_dir) {
|
|
die "$dest_dir is not a directory.\nUse --help for help.\n";
|
|
}
|
|
|
|
if (@_ = grep(/^--[a-z]+-dest\b/, @ARGV)) {
|
|
$_ = join(' or ', @_);
|
|
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 '/') {
|
|
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~";
|
|
}
|
|
|
|
$ARGV[-1] = "$new_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) {
|
|
print "failed to execute $RSYNC_PROG: $!\n";
|
|
} elsif ($? & 127) {
|
|
printf "child died with signal %d, %s coredump\n",
|
|
($? & 127), ($? & 128) ? 'with' : 'without';
|
|
} else {
|
|
printf "child exited with value %d\n", $? >> 8;
|
|
}
|
|
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
|
|
{
|
|
die <<EOT;
|
|
Usage: atomic-rsync [RSYNC-OPTIONS] HOST:/SOURCE/DIR/ /DEST/DIR/
|
|
atomic-rsync [RSYNC-OPTIONS] HOST::MOD/DIR/ /DEST/DIR/
|
|
|
|
This script lets you update a hierarchy of files in an atomic way by first
|
|
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/
|
|
|
|
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.
|
|
|
|
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, --compare-dest, or --copy-dest options (since this script
|
|
uses --link-dest to make the transfer efficient).
|
|
EOT
|
|
}
|