A perl script to effect an atomic transfer of a set of files.
[rsync/rsync.git] / support / atomic-rsync
1 #!/usr/bin/perl
2
3 use strict;
4 use Cwd 'abs_path';
5
6 my $RSYNC = '/usr/bin/rsync';
7
8 my $dest_dir = $ARGV[-1];
9 usage(1) if $dest_dir eq '' || $dest_dir =~ /^--/;
10
11 if (!-d $dest_dir) {
12     print STDERR "$dest_dir is not a directory.\n\n";
13     usage(1);
14 }
15
16 if (@_ = grep(/^--(link|compare)-dest/, @ARGV)) {
17     $_ = join(' or ', @_);
18     print STDERR "You may not use $_ as an rsync option.\n\n";
19     usage(1);
20 }
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
28 my $old_dir = "$dest_dir~old~";
29 my $new_dir = $ARGV[-1] = "$dest_dir~new~";
30
31 if (-d $old_dir) {
32     rename($old_dir, $new_dir) or die "Unable to rename $old_dir to $new_dir: $!";
33 }
34
35 if (system($RSYNC, "--link-dest=$dest_dir", @ARGV)) {
36     if ($? == -1) {
37         print "failed to execute $RSYNC: $!\n";
38     } elsif ($? & 127) {
39         printf "child died with signal %d, %s coredump\n",
40             ($? & 127),  ($? & 128) ? 'with' : 'without';
41     } else {
42         printf "child exited with value %d\n", $? >> 8;
43     }
44     exit $?;
45 }
46
47 rename($dest_dir, $old_dir) or die "Unable to rename $new_dir to $old_dir: $!";
48 rename($new_dir, $dest_dir) or die "Unable to rename $new_dir to $dest_dir: $!";
49
50 exit;
51
52
53 sub usage
54 {
55     my($ret) = @_;
56     my $fh = $ret ? *STDERR : *STDOUT;
57     print $fh <<EOT;
58 Usage: atomic-rsync [RSYNC-OPTIONS] HOST:SOURCE DEST
59
60 This script allows you to pull some files into DEST on the local system
61 (which must exist) in an atomic manner.  It does this by first pulling
62 files to DEST~new~ (using hard-links to unchanged files in order to keep
63 the space requirements down), and then, at the end of the transfer, it
64 renames DEST to DEST~old~ and renames DEST~new~ to DEST to effect the
65 atomic update.  The DEST~old~ hierarchy will be preserved until the next
66 run of this script, at which point it will be renamed to DEST~new~ and
67 used in the copy.
68
69 See the "rsync" command for its list of options.  You may not use the
70 --link-dest or --compare-dest options (since this script uses --link-dest
71 to effect the atomic transfer).  Also, DEST cannot be "/".
72 EOT
73     exit $ret;
74 }