| #!/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 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 =~ /^--/; |
| |
| if (!-d $dest_dir) { |
| print STDERR "$dest_dir is not a directory.\n\n"; |
| usage(1); |
| } |
| |
| if (@_ = grep(/^--(link|compare)-dest/, @ARGV)) { |
| $_ = join(' or ', @_); |
| print STDERR "You may not use $_ as an rsync option.\n\n"; |
| usage(1); |
| } |
| |
| $dest_dir = abs_path($dest_dir); |
| if ($dest_dir eq '/') { |
| print STDERR 'You must not use "/" as the destination directory.', "\n\n"; |
| usage(1); |
| } |
| |
| my $old_dir = "$dest_dir~old~"; |
| my $new_dir = $ARGV[-1] = "$dest_dir~new~"; |
| |
| system($RM_PROG, '-rf', $old_dir) if -d $old_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 $?; |
| } |
| |
| rename($dest_dir, $old_dir) or die "Unable to rename $new_dir to $old_dir: $!"; |
| rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!"; |
| |
| exit; |
| |
| |
| sub usage |
| { |
| my($ret) = @_; |
| my $fh = $ret ? *STDERR : *STDOUT; |
| print $fh <<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: |
| |
| 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. |
| |
| Do NOT 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 "/". |
| EOT |
| exit $ret; |
| } |