1#!/usr/bin/perl 2# 3# This script lets you update a hierarchy of files in an atomic way by 4# first creating a new hierarchy using rsync's --link-dest option, and 5# then swapping the hierarchy into place. **See the usage message for 6# more details and some important caveats!** 7 8use strict; 9use Cwd 'abs_path'; 10 11my $RSYNC_PROG = '/usr/bin/rsync'; 12my $RM_PROG = '/bin/rm'; 13 14my $dest_dir = $ARGV[-1]; 15usage(1) if $dest_dir eq '' || $dest_dir =~ /^--/; 16 17if (!-d $dest_dir) { 18 print STDERR "$dest_dir is not a directory.\n\n"; 19 usage(1); 20} 21 22if (@_ = grep(/^--(link|compare)-dest/, @ARGV)) { 23 $_ = join(' or ', @_); 24 print STDERR "You may not use $_ as an rsync option.\n\n"; 25 usage(1); 26} 27 28$dest_dir = abs_path($dest_dir); 29if ($dest_dir eq '/') { 30 print STDERR 'You must not use "/" as the destination directory.', "\n\n"; 31 usage(1); 32} 33 34my $old_dir = "$dest_dir~old~"; 35my $new_dir = $ARGV[-1] = "$dest_dir~new~"; 36 37system($RM_PROG, '-rf', $old_dir) if -d $old_dir; 38 39if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) { 40 if ($? == -1) { 41 print "failed to execute $RSYNC_PROG: $!\n"; 42 } elsif ($? & 127) { 43 printf "child died with signal %d, %s coredump\n", 44 ($? & 127), ($? & 128) ? 'with' : 'without'; 45 } else { 46 printf "child exited with value %d\n", $? >> 8; 47 } 48 exit $?; 49} 50 51rename($dest_dir, $old_dir) or die "Unable to rename $new_dir to $old_dir: $!"; 52rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!"; 53 54exit; 55 56 57sub usage 58{ 59 my($ret) = @_; 60 my $fh = $ret ? *STDERR : *STDOUT; 61 print $fh <<EOT; 62Usage: atomic-rsync [RSYNC-OPTIONS] HOST:/SOURCE/DIR/ /DEST/DIR/ 63 atomic-rsync [RSYNC-OPTIONS] HOST::MOD/DIR/ /DEST/DIR/ 64 65This script lets you update a hierarchy of files in an atomic way by first 66creating a new hierarchy (using hard-links to leverage the existing files), 67and then swapping the new hierarchy into place. You must be pulling files 68to a local directory, and that directory must already exist. For example: 69 70 atomic-rsync -av host:/remote/files/ /local/files/ 71 72This would make the transfer to the directory /local/files~new~ and then 73swap out /local/files at the end of the transfer by renaming it to 74/local/files~old~ and putting the new directory into its place. The 75/local/files~old~ directory will be preserved until the next update, at 76which point it will be deleted. 77 78Do NOT specify this command: 79 80 atomic-rsync -av host:/remote/files /local/ 81 82... UNLESS you want the entire /local dir to be swapped out! 83 84See the "rsync" command for its list of options. You may not use the 85--link-dest or --compare-dest options (since this script uses --link-dest 86to make the transfer efficient). Also, the destination directory cannot 87be "/". 88EOT 89 exit $ret; 90} 91