Commit | Line | Data |
---|---|---|
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 | |
8 | use strict; | |
9 | use Cwd 'abs_path'; | |
10 | ||
716b46c5 WD |
11 | my $RSYNC_PROG = '/usr/bin/rsync'; |
12 | my $RM_PROG = '/bin/rm'; | |
fa170b2e WD |
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 | ||
716b46c5 | 37 | system($RM_PROG, '-rf', $old_dir) if -d $old_dir; |
fa170b2e | 38 | |
716b46c5 | 39 | if (system($RSYNC_PROG, "--link-dest=$dest_dir", @ARGV)) { |
fa170b2e | 40 | if ($? == -1) { |
716b46c5 | 41 | print "failed to execute $RSYNC_PROG: $!\n"; |
fa170b2e WD |
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; | |
716b46c5 WD |
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/ | |
fa170b2e | 81 | |
716b46c5 | 82 | ... UNLESS you want the entire /local dir to be swapped out! |
fa170b2e WD |
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 | |
716b46c5 WD |
86 | to make the transfer efficient). Also, the destination directory cannot |
87 | be "/". | |
fa170b2e WD |
88 | EOT |
89 | exit $ret; | |
90 | } |