- We now remove the DEST~old~ dir instead of renaming it to DEST~new~.
[rsync/rsync.git] / support / atomic-rsync
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 --link-dest to rsync, and then
5 # swapping the hierarchy into place.  See the usage message for more
6 # details and some important caveats!
7
8 use strict;
9 use Cwd 'abs_path';
10
11 my $RSYNC_PROG = '/usr/bin/rsync';
12 my $RM_PROG = '/bin/rm';
13
14 my $dest_dir = $ARGV[-1];
15 usage(1) if $dest_dir eq '' || $dest_dir =~ /^--/;
16
17 if (!-d $dest_dir) {
18     print STDERR "$dest_dir is not a directory.\n\n";
19     usage(1);
20 }
21
22 if (@_ = 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);
29 if ($dest_dir eq '/') {
30     print STDERR 'You must not use "/" as the destination directory.', "\n\n";
31     usage(1);
32 }
33
34 my $old_dir = "$dest_dir~old~";
35 my $new_dir = $ARGV[-1] = "$dest_dir~new~";
36
37 system($RM_PROG, '-rf', $old_dir) if -d $old_dir;
38
39 if (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
51 rename($dest_dir, $old_dir) or die "Unable to rename $new_dir to $old_dir: $!";
52 rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!";
53
54 exit;
55
56
57 sub usage
58 {
59     my($ret) = @_;
60     my $fh = $ret ? *STDERR : *STDOUT;
61     print $fh <<EOT;
62 Usage: atomic-rsync [RSYNC-OPTIONS] HOST:/SOURCE/DIR/ /DEST/DIR/
63        atomic-rsync [RSYNC-OPTIONS] HOST::MOD/DIR/ /DEST/DIR/
64
65 This script lets you update a hierarchy of files in an atomic way by first
66 creating a new hierarchy (using hard-links to leverage the existing files),
67 and then swapping the new hierarchy into place.  You must be pulling files
68 to a local directory, and that directory must already exist.  For example:
69
70     atomic-rsync -av host:/remote/files/ /local/files/
71
72 This would make the transfer to the directory /local/files~new~ and then
73 swap 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
76 which point it will be deleted.
77
78 Do 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
84 See 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
86 to make the transfer efficient).  Also, the destination directory cannot
87 be "/".
88 EOT
89     exit $ret;
90 }