atomic-rsync 2.7 KB

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