A few more improvements to the hostspec-parsing code.
[rsync/rsync.git] / support / atomic-rsync
CommitLineData
fa170b2e 1#!/usr/bin/perl
716b46c5
WD
2#
3# This script lets you update a hierarchy of files in an atomic way by
e96d7972
WD
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!**
fa170b2e
WD
7
8use strict;
f3721ed1 9use warnings;
fa170b2e
WD
10use Cwd 'abs_path';
11
716b46c5
WD
12my $RSYNC_PROG = '/usr/bin/rsync';
13my $RM_PROG = '/bin/rm';
fa170b2e
WD
14
15my $dest_dir = $ARGV[-1];
f3721ed1
WD
16&usage if !defined $dest_dir || $dest_dir =~ /(^-|^$)/ || grep(/^--help/, @ARGV);
17$dest_dir =~ s{(?<=.)/+$} {};
fa170b2e
WD
18
19if (!-d $dest_dir) {
f3721ed1 20 die "$dest_dir is not a directory.\nUse --help for help.\n";
fa170b2e
WD
21}
22
f3721ed1 23if (@_ = grep(/^--[a-z]+-dest\b/, @ARGV)) {
fa170b2e 24 $_ = join(' or ', @_);
f3721ed1 25 die "You cannot use the $_ option with atomic-rsync.\nUse --help for help.\n";
fa170b2e
WD
26}
27
f3721ed1
WD
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.
fa170b2e
WD
32$dest_dir = abs_path($dest_dir);
33if ($dest_dir eq '/') {
f3721ed1
WD
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~";
fa170b2e
WD
46}
47
f3721ed1 48$ARGV[-1] = "$new_dir/";
fa170b2e 49
f3721ed1
WD
50system($RM_PROG, '-rf', $old_dir) if defined $old_dir && -d $old_dir;
51system($RM_PROG, '-rf', $new_dir) if -d $new_dir;
fa170b2e 52
716b46c5 53if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) {
fa170b2e 54 if ($? == -1) {
716b46c5 55 print "failed to execute $RSYNC_PROG: $!\n";
fa170b2e
WD
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
f3721ed1
WD
65if (!defined $old_dir) {
66 atomic_symlink($symlink_content, $dest_arg);
67 exit;
68}
69
b1220d62 70rename($dest_dir, $old_dir) or die "Unable to rename $dest_dir to $old_dir: $!";
fa170b2e
WD
71rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!";
72
73exit;
74
f3721ed1
WD
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
fa170b2e
WD
85
86sub usage
87{
f3721ed1 88 die <<EOT;
716b46c5
WD
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
f3721ed1
WD
97 mkdir /local/files-1
98 ln -s files-1 /local/files
716b46c5
WD
99 atomic-rsync -av host:/remote/files/ /local/files/
100
f3721ed1
WD
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.
716b46c5 111
f3721ed1 112In all likelihood, you do NOT want to specify this command:
716b46c5
WD
113
114 atomic-rsync -av host:/remote/files /local/
fa170b2e 115
716b46c5 116... UNLESS you want the entire /local dir to be swapped out!
fa170b2e
WD
117
118See the "rsync" command for its list of options. You may not use the
f3721ed1
WD
119--link-dest, --compare-dest, or --copy-dest options (since this script
120uses --link-dest to make the transfer efficient).
fa170b2e 121EOT
fa170b2e 122}