Added a fully atomic update if the user has setup a symlink
[rsync/rsync.git] / support / atomic-rsync
... / ...
CommitLineData
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 warnings;
10use Cwd 'abs_path';
11
12my $RSYNC_PROG = '/usr/bin/rsync';
13my $RM_PROG = '/bin/rm';
14
15my $dest_dir = $ARGV[-1];
16&usage if !defined $dest_dir || $dest_dir =~ /(^-|^$)/ || grep(/^--help/, @ARGV);
17$dest_dir =~ s{(?<=.)/+$} {};
18
19if (!-d $dest_dir) {
20 die "$dest_dir is not a directory.\nUse --help for help.\n";
21}
22
23if (@_ = grep(/^--[a-z]+-dest\b/, @ARGV)) {
24 $_ = join(' or ', @_);
25 die "You cannot use the $_ option with atomic-rsync.\nUse --help for help.\n";
26}
27
28my $symlink_content = readlink $dest_dir; # undef when a real dir
29
30my $dest_arg = $dest_dir;
31# This gives us the real destination dir, with all symlinks dereferenced.
32$dest_dir = abs_path($dest_dir);
33if ($dest_dir eq '/') {
34 die qq|You must not use "/" as the destination directory.\nUse --help for help.\n|;
35}
36
37my($old_dir, $new_dir);
38if (defined $symlink_content && $dest_dir =~ /-([12])$/) {
39 my $num = 3 - $1;
40 $old_dir = undef;
41 ($new_dir = $dest_dir) =~ s/-[12]$/-$num/;
42 $symlink_content =~ s/-[12]$/-$num/;
43} else {
44 $old_dir = "$dest_dir~old~";
45 $new_dir = "$dest_dir~new~";
46}
47
48$ARGV[-1] = "$new_dir/";
49
50system($RM_PROG, '-rf', $old_dir) if defined $old_dir && -d $old_dir;
51system($RM_PROG, '-rf', $new_dir) if -d $new_dir;
52
53if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) {
54 if ($? == -1) {
55 print "failed to execute $RSYNC_PROG: $!\n";
56 } elsif ($? & 127) {
57 printf "child died with signal %d, %s coredump\n",
58 ($? & 127), ($? & 128) ? 'with' : 'without';
59 } else {
60 printf "child exited with value %d\n", $? >> 8;
61 }
62 exit $?;
63}
64
65if (!defined $old_dir) {
66 atomic_symlink($symlink_content, $dest_arg);
67 exit;
68}
69
70rename($dest_dir, $old_dir) or die "Unable to rename $dest_dir to $old_dir: $!";
71rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!";
72
73exit;
74
75sub atomic_symlink
76{
77 my($target, $link) = @_;
78 my $newlink = "$link~new~";
79
80 unlink($newlink); # Just in case
81 symlink($target, $newlink) or die "Unable to symlink $newlink -> $target: $!\n";
82 rename($newlink, $link) or die "Unable to rename $newlink to $link: $!\n";
83}
84
85
86sub usage
87{
88 die <<EOT;
89Usage: atomic-rsync [RSYNC-OPTIONS] HOST:/SOURCE/DIR/ /DEST/DIR/
90 atomic-rsync [RSYNC-OPTIONS] HOST::MOD/DIR/ /DEST/DIR/
91
92This script lets you update a hierarchy of files in an atomic way by first
93creating a new hierarchy (using hard-links to leverage the existing files),
94and then swapping the new hierarchy into place. You must be pulling files
95to a local directory, and that directory must already exist. For example:
96
97 mkdir /local/files-1
98 ln -s files-1 /local/files
99 atomic-rsync -av host:/remote/files/ /local/files/
100
101If /local/files is a symlink to a directory that ends in -1 or -2, the
102copy will go to the alternate suffix and the symlink will be changed to
103point to the new dir. This is a fully atomic update. If the destination
104is not a symlink (or not a symlink to a *-1 or a *-2 directory), this
105will instead create a directory with "~new~" suffixed, move the current
106directory to a name with "~old~" suffixed, and then move the ~new~
107directory to the original destination name (this double rename is not
108fully atomic, but is rapid). In both cases, the prior destintaion
109directory will be preserved until the next update, at which point it
110will be deleted.
111
112In all likelihood, you do NOT want to specify this command:
113
114 atomic-rsync -av host:/remote/files /local/
115
116... UNLESS you want the entire /local dir to be swapped out!
117
118See the "rsync" command for its list of options. You may not use the
119--link-dest, --compare-dest, or --copy-dest options (since this script
120uses --link-dest to make the transfer efficient).
121EOT
122}