#!/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 <