+--- old/support/rsyncsums
++++ new/support/rsyncsums
+@@ -0,0 +1,175 @@
++#!/usr/bin/perl -w
++use strict;
++
++use Getopt::Long;
++use Cwd qw(abs_path cwd);
++use Digest::MD4;
++use Digest::MD5;
++
++our $SUMS_FILE = '.rsyncsums';
++
++our($recurse_opt, $help_opt);
++our $verbosity = 0;
++
++&Getopt::Long::Configure('bundling');
++&usage if !&GetOptions(
++ 'recurse|r' => \$recurse_opt,
++ 'verbose|v+' => \$verbosity,
++ 'help|h' => \$help_opt,
++) || $help_opt;
++
++my $start_dir = cwd();
++
++my @dirs = @ARGV;
++@dirs = '.' unless @dirs;
++foreach (@dirs) {
++ $_ = abs_path($_);
++}
++
++$| = 1;
++
++my $md4 = Digest::MD4->new;
++my $md5 = Digest::MD5->new;
++
++while (@dirs) {
++ my $dir = shift @dirs;
++
++ if (!chdir($dir)) {
++ warn "Unable to chdir to $dir: $!\n";
++ next;
++ }
++ if (!opendir(DP, '.')) {
++ warn "Unable to opendir $dir: $!\n";
++ next;
++ }
++
++ if ($verbosity) {
++ my $reldir = $dir;
++ $reldir =~ s#^$start_dir(/|$)# $1 ? '' : '.' #eo;
++ print "$reldir ... ";
++ }
++
++ my $sums_mtime = (stat($SUMS_FILE))[9];
++ my %cache;
++ my @subdirs;
++ my $cnt = 0;
++ while (defined(my $fn = readdir(DP))) {
++ next if $fn =~ /^\.\.?$/ || $fn =~ /^\Q$SUMS_FILE\E$/o || -l $fn;
++ if (-d _) {
++ push(@subdirs, "$dir/$fn");
++ next;
++ }
++ next unless -f _;
++
++ my($size,$mtime,$ctime,$inode) = (stat(_))[7,9,10,1];
++ next if $size == 0;
++
++ $cache{$fn} = [ $size, $mtime, $ctime & 0xFFFFFFFF, $inode & 0xFFFFFFFF ];
++ $cnt++;
++ }
++
++ closedir DP;
++
++ unshift(@dirs, sort @subdirs) if $recurse_opt;
++
++ if (!$cnt) {
++ if (defined $sums_mtime) {
++ print "(removed $SUMS_FILE) " if $verbosity;
++ unlink($SUMS_FILE);
++ }
++ print "empty\n" if $verbosity;
++ next;
++ }
++
++ if (open(FP, '+<', $SUMS_FILE)) {
++ while (<FP>) {
++ chomp;
++ my($sum4, $sum5, $size, $mtime, $ctime, $inode, $fn) = split(' ', $_, 7);
++ my $ref = $cache{$fn};
++ if (defined $ref) {
++ if ($$ref[0] == $size
++ && $$ref[1] == $mtime
++ && $$ref[2] == $ctime
++ && $$ref[3] == $inode
++ && $sum4 !~ /=/ && $sum5 !~ /=/) {
++ $$ref[4] = $sum4;
++ $$ref[5] = $sum5;
++ $cnt--;
++ } else {
++ $$ref[4] = $$ref[5] = undef;
++ }
++ } else {
++ $cnt = -1; # Force rewrite due to removed line.
++ }
++ }
++ } else {
++ open(FP, '>', $SUMS_FILE) or die "Unable to write $dir/$SUMS_FILE: $!\n";
++ $cnt = -1;
++ }
++
++ if ($cnt) {
++ print "UPDATING\n" if $verbosity;
++ while (my($fn, $ref) = each %cache) {
++ next if defined $$ref[3] && defined $$ref[4];
++ if (!open(IN, $fn)) {
++ print STDERR "Unable to read $fn: $!\n";
++ delete $cache{$fn};
++ next;
++ }
++
++ my($size,$mtime,$ctime,$inode) = (stat(IN))[7,9,10,1];
++ if ($size == 0) {
++ close IN;
++ next;
++ }
++
++ my($sum4, $sum5);
++ while (1) {
++ while (sysread(IN, $_, 64*1024)) {
++ $md4->add($_);
++ $md5->add($_);
++ }
++ $sum4 = $md4->hexdigest;
++ $sum5 = $md5->hexdigest;
++ print " $sum4 $sum5" if $verbosity > 2;
++ print " $fn\n" if $verbosity > 1;
++ my($size2,$mtime2,$ctime2,$inode2) = (stat(IN))[7,9,10,1];
++ last if $size == $size2 && $mtime == $mtime2
++ && $ctime == $ctime2 && $inode == $inode2;
++ $size = $size2;
++ $mtime = $mtime2;
++ $ctime = $ctime2;
++ $inode = $inode2;
++ sysseek(IN, 0, 0);
++ }
++
++ close IN;
++
++ $cache{$fn} = [ $size, $mtime, $ctime, $inode, $sum4, $sum5 ];
++ }
++
++ seek(FP, 0, 0);
++ foreach my $fn (sort keys %cache) {
++ my $ref = $cache{$fn};
++ my($size, $mtime, $ctime, $inode, $sum4, $sum5) = @$ref;
++ printf FP '%s %s %10d %10d %10d %10d %s' . "\n", $sum4, $sum5, $size, $mtime, $ctime, $inode, $fn;
++ }
++ truncate(FP, tell(FP));
++ } else {
++ print "ok\n" if $verbosity;
++ }
++
++ close FP;
++}
++
++sub usage
++{
++ die <<EOT;
++Usage: rsyncsums [OPTIONS] [DIRS]
++
++Options:
++ -r, --recurse Update $SUMS_FILE files in subdirectories too.
++ -v, --verbose Mention what we're doing. Repeat for more info.
++ -h, --help Display this help message.
++EOT
++}