A simple patch that lets rsync use cached checksum values stored in
[rsync/rsync-patches.git] / checksum-xattr.diff
1 This patch is the start of storing/using checksum information from
2 extended attribute values.  The rsync code only reads the values
3 at the moment.  There is also a perl script that can create them.
4
5 To use this patch, run these commands for a successful build:
6
7     patch -p1 <patches/checksum-xattr.diff
8     ./configure                               (optional if already run)
9     make
10
11 --- old/flist.c
12 +++ new/flist.c
13 @@ -1193,7 +1193,8 @@ struct file_struct *make_file(const char
14         }
15  #endif
16  
17 -       if (always_checksum && am_sender && S_ISREG(st.st_mode))
18 +       if (always_checksum && am_sender && S_ISREG(st.st_mode)
19 +        && !get_sum_xattr(thisname, &st, tmp_sum))
20                 file_checksum(thisname, tmp_sum, st.st_size);
21  
22         F_PATHNAME(file) = pathname;
23 --- old/generator.c
24 +++ new/generator.c
25 @@ -627,7 +627,8 @@ int unchanged_file(char *fn, struct file
26            of the file time to determine whether to sync */
27         if (always_checksum > 0 && S_ISREG(st->st_mode)) {
28                 char sum[MAX_DIGEST_LEN];
29 -               file_checksum(fn, sum, st->st_size);
30 +               if (!get_sum_xattr(fn, st, sum))
31 +                       file_checksum(fn, sum, st->st_size);
32                 return memcmp(sum, F_SUM(file), checksum_len) == 0;
33         }
34  
35 --- old/rsync.h
36 +++ new/rsync.h
37 @@ -1070,6 +1070,12 @@ isDigit(const char *ptr)
38  }
39  
40  static inline int
41 +isXDigit(const char *ptr)
42 +{
43 +       return isxdigit(*(unsigned char *)ptr);
44 +}
45 +
46 +static inline int
47  isPrint(const char *ptr)
48  {
49         return isprint(*(unsigned char *)ptr);
50 --- old/support/xsums
51 +++ new/support/xsums
52 @@ -0,0 +1,113 @@
53 +#!/usr/bin/perl -w
54 +use strict;
55 +
56 +use Getopt::Long;
57 +use Cwd qw(abs_path cwd);
58 +use Digest::MD4;
59 +use Digest::MD5;
60 +use File::ExtAttr ':all';
61 +
62 +our($recurse_opt, $help_opt);
63 +our $verbosity = 0;
64 +
65 +&Getopt::Long::Configure('bundling');
66 +&usage if !&GetOptions(
67 +    'recurse|r' => \$recurse_opt,
68 +    'verbose|v+' => \$verbosity,
69 +    'help|h' => \$help_opt,
70 +) || $help_opt;
71 +
72 +my $start_dir = cwd();
73 +
74 +my @dirs = @ARGV;
75 +@dirs = '.' unless @dirs;
76 +foreach (@dirs) {
77 +    $_ = abs_path($_);
78 +}
79 +
80 +$| = 1;
81 +
82 +my $md4 = Digest::MD4->new;
83 +my $md5 = Digest::MD5->new;
84 +
85 +while (@dirs) {
86 +    my $dir = shift @dirs;
87 +
88 +    if (!chdir($dir)) {
89 +       warn "Unable to chdir to $dir: $!\n";
90 +       next;
91 +    }
92 +    if (!opendir(DP, '.')) {
93 +       warn "Unable to opendir $dir: $!\n";
94 +       next;
95 +    }
96 +
97 +    if ($verbosity) {
98 +       my $reldir = $dir;
99 +       $reldir =~ s#^$start_dir(/|$)# $1 ? '' : '.' #eo;
100 +       print "scanning $reldir\n";
101 +    }
102 +
103 +    my @subdirs;
104 +    while (defined(my $fn = readdir(DP))) {
105 +       next if $fn =~ /^\.\.?$/ || -l $fn;
106 +       if (-d _) {
107 +           push(@subdirs, "$dir/$fn");
108 +           next;
109 +       }
110 +       next unless -f _;
111 +
112 +       my($size,$mtime) = (stat(_))[7,9];
113 +
114 +       my $sum4 = getfattr($fn, 'rsync.%md4');
115 +       my $sum5 = getfattr($fn, 'rsync.%md5');
116 +
117 +       foreach ($sum4, $sum5) {
118 +           if (defined $_) {
119 +               my($sum, $sz, $mt) = /^([0-9a-f]{32}) (\d+) (\d+)$/;
120 +               if (!defined($mt) || $sz != $size || $mt != $mtime) {
121 +                   $_ = undef;
122 +               } else {
123 +                   $_ = $sum;
124 +               }
125 +           }
126 +       }
127 +       if (!defined($sum4) || !defined($sum5)) {
128 +           if (!open(IN, $fn)) {
129 +               print STDERR "Unable to read $fn: $!\n";
130 +               next;
131 +           }
132 +
133 +           while (sysread(IN, $_, 64*1024)) {
134 +               $md4->add($_);
135 +               $md5->add($_);
136 +           }
137 +           close IN;
138 +
139 +           $sum4 = $md4->hexdigest;
140 +           $sum5 = $md5->hexdigest;
141 +           print " $sum4 $sum5" if $verbosity > 2;
142 +           print " $fn\n" if $verbosity > 1;
143 +
144 +           setfattr($fn, 'rsync.%md4', "$sum4 $size $mtime");
145 +           setfattr($fn, 'rsync.%md5', "$sum5 $size $mtime");
146 +           #utime $mtime, $mtime, $fn; # Set mtime if it changes.
147 +       }
148 +    }
149 +
150 +    closedir DP;
151 +
152 +    unshift(@dirs, sort @subdirs) if $recurse_opt;
153 +}
154 +
155 +sub usage
156 +{
157 +    die <<EOT;
158 +Usage: rsyncsums [OPTIONS] [DIRS]
159 +
160 +Options:
161 + -r, --recurse     Update checksums in subdirectories too.
162 + -v, --verbose     Mention what we're doing.  Repeat for more info.
163 + -h, --help        Display this help message.
164 +EOT
165 +}
166 --- old/xattrs.c
167 +++ new/xattrs.c
168 @@ -31,6 +31,8 @@ extern int am_generator;
169  extern int read_only;
170  extern int list_only;
171  extern int checksum_seed;
172 +extern int checksum_len;
173 +extern int protocol_version;
174  
175  #define RSYNC_XAL_INITIAL 5
176  #define RSYNC_XAL_LIST_INITIAL 100
177 @@ -64,6 +66,9 @@ extern int checksum_seed;
178  #define XSTAT_ATTR RSYNC_PREFIX "%stat"
179  #define XSTAT_LEN ((int)sizeof XSTAT_ATTR - 1)
180  
181 +#define MD4_ATTR RSYNC_PREFIX "%md4"
182 +#define MD5_ATTR RSYNC_PREFIX "%md5"
183 +
184  typedef struct {
185         char *datum, *name;
186         size_t datum_len, name_len;
187 @@ -795,6 +800,71 @@ int set_xattr(const char *fname, const s
188         return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
189  }
190  
191 +int get_sum_xattr(const char *fname, STRUCT_STAT *stp, char *sum)
192 +{
193 +       const char *mdattr = protocol_version >= 30
194 +                          ? MD5_ATTR : MD4_ATTR;
195 +       char *cp, buf[256];
196 +       OFF_T file_length;
197 +       time_t mtime;
198 +       int i, len;
199 +       
200 +       len = sys_lgetxattr(fname, mdattr, buf, sizeof buf - 1);
201 +       if (len >= (int)sizeof buf) {
202 +               len = -1;
203 +               errno = ERANGE;
204 +       }
205 +       if (len < 0) {
206 +               if (errno == ENOTSUP || errno == ENOATTR)
207 +                       return 0;
208 +               rsyserr(FERROR, errno, "failed to read xattr %s for %s",
209 +                       mdattr, full_fname(fname));
210 +               return 0;
211 +       }
212 +       buf[len] = '\0';
213 +
214 +       for (i = 0, cp = buf; i < checksum_len*2; i++, cp++) {
215 +               int x;
216 +               if (isXDigit(cp)) {
217 +                       if (isDigit(cp))
218 +                               x = *cp - '0';
219 +                       else
220 +                               x = (*cp & 0xF) + 9;
221 +               } else {
222 +                       cp = "";
223 +                       break;
224 +               }
225 +               if (i & 1)
226 +                       sum[i/2] |= x;
227 +               else
228 +                       sum[i/2] = x << 4;
229 +       }
230 +       if (*cp++ != ' ')
231 +               goto parse_error;
232 +
233 +       file_length = 0;
234 +       while (isDigit(cp))
235 +               file_length = file_length * 10 + *cp++ - '0';
236 +       if (*cp++ != ' ')
237 +               goto parse_error;
238 +
239 +       mtime = 0;
240 +       while (isDigit(cp))
241 +               mtime = mtime * 10 + *cp++ - '0';
242 +       if (*cp)
243 +               goto parse_error;
244 +
245 +       if (stp->st_size != file_length || stp->st_mtime != mtime)
246 +               return 0;
247 +
248 +       return 1;
249 +
250 +  parse_error:
251 +       rprintf(FERROR, "Corrupt %s xattr attached to %s: \"%s\"\n",
252 +               mdattr, full_fname(fname), buf);
253 +       exit_cleanup(RERR_FILEIO);
254 +}
255 +
256  int get_stat_xattr(const char *fname, int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
257  {
258         int mode, rdev_major, rdev_minor, uid, gid, len;