49e6957a154159cfb7939b470ff52589da416e36
[rsync-patches.git] / checksum-reading.diff
1 Optimize the --checksum option using externally created .rsyncsums files.
2
3 This adds a new option, --sumfiles=MODE, that allows you to use a cache of
4 checksums when performing a --checksum transfer.  These checksum files
5 (.rsyncsums) must be created by some other process -- see the perl script,
6 rsyncsums, in the support dir for one way.
7
8 This option can be particularly helpful to a public mirror that wants to
9 pre-compute their .rsyncsums files, set the "checksum files = strict" option
10 in their daemon config file, and thus make it quite efficient for a client
11 rsync to make use of the --checksum option on their server.
12
13 To use this patch, run these commands for a successful build:
14
15     patch -p1 <patches/checksum-reading.diff
16     ./configure                               (optional if already run)
17     make
18
19 based-on: 63f91976112b8b2118cc17eb5fc8142175566f4f
20 diff --git a/checksum.c b/checksum.c
21 --- a/checksum.c
22 +++ b/checksum.c
23 @@ -98,7 +98,7 @@ void get_checksum2(char *buf, int32 len, char *sum)
24         }
25  }
26  
27 -void file_checksum(char *fname, char *sum, OFF_T size)
28 +void file_checksum(const char *fname, OFF_T size, char *sum)
29  {
30         struct map_struct *buf;
31         OFF_T i, len = size;
32 diff --git a/clientserver.c b/clientserver.c
33 --- a/clientserver.c
34 +++ b/clientserver.c
35 @@ -42,6 +42,8 @@ extern int numeric_ids;
36  extern int filesfrom_fd;
37  extern int remote_protocol;
38  extern int protocol_version;
39 +extern int always_checksum;
40 +extern int checksum_files;
41  extern int io_timeout;
42  extern int no_detach;
43  extern int write_batch;
44 @@ -908,6 +910,9 @@ static int rsync_module(int f_in, int f_out, int i, const char *addr, const char
45         } else if (am_root < 0) /* Treat --fake-super from client as --super. */
46                 am_root = 2;
47  
48 +       checksum_files = always_checksum ? lp_checksum_files(i)
49 +                                        : CSF_IGNORE_FILES;
50 +
51         if (filesfrom_fd == 0)
52                 filesfrom_fd = f_in;
53  
54 diff --git a/flist.c b/flist.c
55 --- a/flist.c
56 +++ b/flist.c
57 @@ -22,6 +22,7 @@
58  
59  #include "rsync.h"
60  #include "ifuncs.h"
61 +#include "itypes.h"
62  #include "rounding.h"
63  #include "inums.h"
64  #include "io.h"
65 @@ -33,6 +34,7 @@ extern int am_sender;
66  extern int am_generator;
67  extern int inc_recurse;
68  extern int always_checksum;
69 +extern int basis_dir_cnt;
70  extern int module_id;
71  extern int ignore_errors;
72  extern int numeric_ids;
73 @@ -58,6 +60,7 @@ extern int implied_dirs;
74  extern int ignore_perishable;
75  extern int non_perishable_cnt;
76  extern int prune_empty_dirs;
77 +extern int checksum_files;
78  extern int copy_links;
79  extern int copy_unsafe_links;
80  extern int protocol_version;
81 @@ -69,6 +72,7 @@ extern int sender_symlink_iconv;
82  extern int output_needs_newline;
83  extern int sender_keeps_checksum;
84  extern int unsort_ndx;
85 +extern char *basis_dir[];
86  extern uid_t our_uid;
87  extern struct stats stats;
88  extern char *filesfrom_host;
89 @@ -86,6 +90,20 @@ extern int filesfrom_convert;
90  extern iconv_t ic_send, ic_recv;
91  #endif
92  
93 +#ifdef HAVE_UTIMENSAT
94 +#ifdef HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
95 +#define ST_MTIME_NSEC st_mtim.tv_nsec
96 +#elif defined(HAVE_STRUCT_STAT_ST_MTIMENSEC)
97 +#define ST_MTIME_NSEC st_mtimensec
98 +#endif
99 +#endif
100 +
101 +#define RSYNCSUMS_FILE ".rsyncsums"
102 +#define RSYNCSUMS_LEN (sizeof RSYNCSUMS_FILE-1)
103 +
104 +#define CLEAN_STRIP_ROOT (1<<0)
105 +#define CLEAN_KEEP_LAST (1<<1)
106 +
107  #define PTR_SIZE (sizeof (struct file_struct *))
108  
109  int io_error;
110 @@ -128,7 +146,11 @@ static char tmp_sum[MAX_DIGEST_LEN];
111  static char empty_sum[MAX_DIGEST_LEN];
112  static int flist_count_offset; /* for --delete --progress */
113  
114 -static void flist_sort_and_clean(struct file_list *flist, int strip_root);
115 +static struct csum_cache {
116 +       struct file_list *flist;
117 +} *csum_cache = NULL;
118 +
119 +static void flist_sort_and_clean(struct file_list *flist, int flags);
120  static void output_flist(struct file_list *flist);
121  
122  void init_flist(void)
123 @@ -343,6 +365,238 @@ static void flist_done_allocating(struct file_list *flist)
124                 flist->pool_boundary = ptr;
125  }
126  
127 +void reset_checksum_cache()
128 +{
129 +       int slot, slots = am_sender ? 1 : basis_dir_cnt + 1;
130 +
131 +       if (!csum_cache) {
132 +               csum_cache = new_array0(struct csum_cache, slots);
133 +               if (!csum_cache)
134 +                       out_of_memory("reset_checksum_cache");
135 +       }
136 +
137 +       for (slot = 0; slot < slots; slot++) {
138 +               struct file_list *flist = csum_cache[slot].flist;
139 +
140 +               if (flist) {
141 +                       /* Reset the pool memory and empty the file-list array. */
142 +                       pool_free_old(flist->file_pool,
143 +                                     pool_boundary(flist->file_pool, 0));
144 +                       flist->used = 0;
145 +               } else
146 +                       flist = csum_cache[slot].flist = flist_new(FLIST_TEMP, "reset_checksum_cache");
147 +
148 +               flist->low = 0;
149 +               flist->high = -1;
150 +               flist->next = NULL;
151 +       }
152 +}
153 +
154 +/* The basename_len count is the length of the basename + 1 for the '\0'. */
155 +static int add_checksum(struct file_list *flist, const char *dirname,
156 +                       const char *basename, int basename_len, OFF_T file_length,
157 +                       time_t mtime, uint32 ctime, uint32 inode,
158 +                       const char *sum)
159 +{
160 +       struct file_struct *file;
161 +       int alloc_len, extra_len;
162 +       char *bp;
163 +
164 +       if (basename_len == RSYNCSUMS_LEN+1 && *basename == '.'
165 +        && strcmp(basename, RSYNCSUMS_FILE) == 0)
166 +               return 0;
167 +
168 +       /* "2" is for a 32-bit ctime num and an 32-bit inode num. */
169 +       extra_len = (file_extra_cnt + (file_length > 0xFFFFFFFFu) + SUM_EXTRA_CNT + 2)
170 +                 * EXTRA_LEN;
171 +#if EXTRA_ROUNDING > 0
172 +       if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
173 +               extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
174 +#endif
175 +       alloc_len = FILE_STRUCT_LEN + extra_len + basename_len;
176 +       bp = pool_alloc(flist->file_pool, alloc_len, "add_checksum");
177 +
178 +       memset(bp, 0, extra_len + FILE_STRUCT_LEN);
179 +       bp += extra_len;
180 +       file = (struct file_struct *)bp;
181 +       bp += FILE_STRUCT_LEN;
182 +
183 +       memcpy(bp, basename, basename_len);
184 +
185 +       file->mode = S_IFREG;
186 +       file->modtime = mtime;
187 +       file->len32 = (uint32)file_length;
188 +       if (file_length > 0xFFFFFFFFu) {
189 +               file->flags |= FLAG_LENGTH64;
190 +               OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32);
191 +       }
192 +       file->dirname = dirname;
193 +       F_CTIME(file) = ctime;
194 +       F_INODE(file) = inode;
195 +       bp = F_SUM(file);
196 +       memcpy(bp, sum, checksum_len);
197 +
198 +       flist_expand(flist, 1);
199 +       flist->files[flist->used++] = file;
200 +
201 +       flist->sorted = flist->files;
202 +
203 +       return 1;
204 +}
205 +
206 +/* The "dirname" arg's data must remain unchanged during the lifespan of
207 + * the created csum_cache[].flist object because we use it directly. */
208 +static void read_checksums(int slot, struct file_list *flist, const char *dirname)
209 +{
210 +       char line[MAXPATHLEN+1024], fbuf[MAXPATHLEN], sum[MAX_DIGEST_LEN];
211 +       FILE *fp;
212 +       char *cp;
213 +       int len, i;
214 +       time_t mtime;
215 +       OFF_T file_length;
216 +       uint32 ctime, inode;
217 +       int dlen = dirname ? strlcpy(fbuf, dirname, sizeof fbuf) : 0;
218 +
219 +       if (dlen >= (int)(sizeof fbuf - 1 - RSYNCSUMS_LEN))
220 +               return;
221 +       if (dlen)
222 +               fbuf[dlen++] = '/';
223 +       else
224 +               dirname = NULL;
225 +       strlcpy(fbuf+dlen, RSYNCSUMS_FILE, sizeof fbuf - dlen);
226 +       if (slot) {
227 +               pathjoin(line, sizeof line, basis_dir[slot-1], fbuf);
228 +               cp = line;
229 +       } else
230 +               cp = fbuf;
231 +       if (!(fp = fopen(cp, "r")))
232 +               return;
233 +
234 +       while (fgets(line, sizeof line, fp)) {
235 +               cp = line;
236 +               if (protocol_version >= 30) {
237 +                       char *alt_sum = cp;
238 +                       if (*cp == '=')
239 +                               while (*++cp == '=') {}
240 +                       else
241 +                               while (isXDigit(cp)) cp++;
242 +                       if (cp - alt_sum != MD4_DIGEST_LEN*2 || *cp != ' ')
243 +                               break;
244 +                       while (*++cp == ' ') {}
245 +               }
246 +
247 +               if (*cp == '=') {
248 +                       continue;
249 +               } else {
250 +                       for (i = 0; i < checksum_len*2; i++, cp++) {
251 +                               int x;
252 +                               if (isXDigit(cp)) {
253 +                                       if (isDigit(cp))
254 +                                               x = *cp - '0';
255 +                                       else
256 +                                               x = (*cp & 0xF) + 9;
257 +                               } else {
258 +                                       cp = "";
259 +                                       break;
260 +                               }
261 +                               if (i & 1)
262 +                                       sum[i/2] |= x;
263 +                               else
264 +                                       sum[i/2] = x << 4;
265 +                       }
266 +               }
267 +               if (*cp != ' ')
268 +                       break;
269 +               while (*++cp == ' ') {}
270 +
271 +               if (protocol_version < 30) {
272 +                       char *alt_sum = cp;
273 +                       if (*cp == '=')
274 +                               while (*++cp == '=') {}
275 +                       else
276 +                               while (isXDigit(cp)) cp++;
277 +                       if (cp - alt_sum != MD5_DIGEST_LEN*2 || *cp != ' ')
278 +                               break;
279 +                       while (*++cp == ' ') {}
280 +               }
281 +
282 +               file_length = 0;
283 +               while (isDigit(cp))
284 +                       file_length = file_length * 10 + *cp++ - '0';
285 +               if (*cp != ' ')
286 +                       break;
287 +               while (*++cp == ' ') {}
288 +
289 +               mtime = 0;
290 +               while (isDigit(cp))
291 +                       mtime = mtime * 10 + *cp++ - '0';
292 +               if (*cp != ' ')
293 +                       break;
294 +               while (*++cp == ' ') {}
295 +
296 +               ctime = 0;
297 +               while (isDigit(cp))
298 +                       ctime = ctime * 10 + *cp++ - '0';
299 +               if (*cp != ' ')
300 +                       break;
301 +               while (*++cp == ' ') {}
302 +
303 +               inode = 0;
304 +               while (isDigit(cp))
305 +                       inode = inode * 10 + *cp++ - '0';
306 +               if (*cp != ' ')
307 +                       break;
308 +               while (*++cp == ' ') {}
309 +
310 +               len = strlen(cp);
311 +               while (len && (cp[len-1] == '\n' || cp[len-1] == '\r'))
312 +                       len--;
313 +               if (!len)
314 +                       break;
315 +               cp[len++] = '\0'; /* len now counts the null */
316 +               if (strchr(cp, '/'))
317 +                       break;
318 +               if (len > MAXPATHLEN)
319 +                       continue;
320 +
321 +               strlcpy(fbuf+dlen, cp, sizeof fbuf - dlen);
322 +
323 +               add_checksum(flist, dirname, cp, len, file_length,
324 +                            mtime, ctime, inode,
325 +                            sum);
326 +       }
327 +       fclose(fp);
328 +
329 +       flist_sort_and_clean(flist, CLEAN_KEEP_LAST);
330 +}
331 +
332 +void get_cached_checksum(int slot, const char *fname, struct file_struct *file,
333 +                        STRUCT_STAT *stp, char *sum_buf)
334 +{
335 +       struct file_list *flist = csum_cache[slot].flist;
336 +       int j;
337 +
338 +       if (!flist->next) {
339 +               flist->next = cur_flist; /* next points from checksum flist to file flist */
340 +               read_checksums(slot, flist, file->dirname);
341 +       }
342 +
343 +       if ((j = flist_find(flist, file)) >= 0) {
344 +               struct file_struct *fp = flist->sorted[j];
345 +
346 +               if (F_LENGTH(fp) == stp->st_size
347 +                && fp->modtime == stp->st_mtime
348 +                && (checksum_files & CSF_LAX
349 +                 || (F_CTIME(fp) == (uint32)stp->st_ctime
350 +                  && F_INODE(fp) == (uint32)stp->st_ino))) {
351 +                       memcpy(sum_buf, F_SUM(fp), MAX_DIGEST_LEN);
352 +                       return;
353 +               }
354 +       }
355 +
356 +       file_checksum(fname, stp->st_size, sum_buf);
357 +}
358 +
359  /* Call this with EITHER (1) "file, NULL, 0" to chdir() to the file's
360   * F_PATHNAME(), or (2) "NULL, dir, dirlen" to chdir() to the supplied dir,
361   * with dir == NULL taken to be the starting directory, and dirlen < 0
362 @@ -1136,7 +1390,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
363                               STRUCT_STAT *stp, int flags, int filter_level)
364  {
365         static char *lastdir;
366 -       static int lastdir_len = -1;
367 +       static int lastdir_len = -2;
368         struct file_struct *file;
369         char thisname[MAXPATHLEN];
370         char linkname[MAXPATHLEN];
371 @@ -1282,9 +1536,16 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
372                         memcpy(lastdir, thisname, len);
373                         lastdir[len] = '\0';
374                         lastdir_len = len;
375 +                       if (checksum_files && am_sender && flist)
376 +                               reset_checksum_cache();
377                 }
378 -       } else
379 +       } else {
380                 basename = thisname;
381 +               if (checksum_files && am_sender && flist && lastdir_len == -2) {
382 +                       lastdir_len = -1;
383 +                       reset_checksum_cache();
384 +               }
385 +       }
386         basename_len = strlen(basename) + 1; /* count the '\0' */
387  
388  #ifdef SUPPORT_LINKS
389 @@ -1302,11 +1563,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
390                 extra_len += EXTRA_LEN;
391  #endif
392  
393 -       if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
394 -               file_checksum(thisname, tmp_sum, st.st_size);
395 -               if (sender_keeps_checksum)
396 -                       extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
397 -       }
398 +       if (sender_keeps_checksum && S_ISREG(st.st_mode))
399 +               extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
400  
401  #if EXTRA_ROUNDING > 0
402         if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
403 @@ -1391,8 +1649,14 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
404                 return NULL;
405         }
406  
407 -       if (sender_keeps_checksum && S_ISREG(st.st_mode))
408 -               memcpy(F_SUM(file), tmp_sum, checksum_len);
409 +       if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
410 +               if (flist && checksum_files)
411 +                       get_cached_checksum(0, thisname, file, &st, tmp_sum);
412 +               else
413 +                       file_checksum(thisname, st.st_size, tmp_sum);
414 +               if (sender_keeps_checksum)
415 +                       memcpy(F_SUM(file), tmp_sum, checksum_len);
416 +       }
417  
418         if (unsort_ndx)
419                 F_NDX(file) = stats.num_dirs;
420 @@ -2553,7 +2817,7 @@ struct file_list *recv_file_list(int f)
421                         rprintf(FINFO, "[%s] flist_eof=1\n", who_am_i());
422         }
423  
424 -       flist_sort_and_clean(flist, relative_paths);
425 +       flist_sort_and_clean(flist, relative_paths ? CLEAN_STRIP_ROOT : 0);
426  
427         if (protocol_version < 30) {
428                 /* Recv the io_error flag */
429 @@ -2776,7 +3040,7 @@ void flist_free(struct file_list *flist)
430  
431  /* This routine ensures we don't have any duplicate names in our file list.
432   * duplicate names can cause corruption because of the pipelining. */
433 -static void flist_sort_and_clean(struct file_list *flist, int strip_root)
434 +static void flist_sort_and_clean(struct file_list *flist, int flags)
435  {
436         char fbuf[MAXPATHLEN];
437         int i, prev_i;
438 @@ -2827,7 +3091,7 @@ static void flist_sort_and_clean(struct file_list *flist, int strip_root)
439                         /* If one is a dir and the other is not, we want to
440                          * keep the dir because it might have contents in the
441                          * list.  Otherwise keep the first one. */
442 -                       if (S_ISDIR(file->mode)) {
443 +                       if (S_ISDIR(file->mode) || flags & CLEAN_KEEP_LAST) {
444                                 struct file_struct *fp = flist->sorted[j];
445                                 if (!S_ISDIR(fp->mode))
446                                         keep = i, drop = j;
447 @@ -2843,8 +3107,8 @@ static void flist_sort_and_clean(struct file_list *flist, int strip_root)
448                         } else
449                                 keep = j, drop = i;
450  
451 -                       if (!am_sender) {
452 -                               if (DEBUG_GTE(DUP, 1)) {
453 +                       if (!am_sender || flags & CLEAN_KEEP_LAST) {
454 +                               if (DEBUG_GTE(DUP, 1) && !(flags & CLEAN_KEEP_LAST)) {
455                                         rprintf(FINFO,
456                                             "removing duplicate name %s from file list (%d)\n",
457                                             f_name(file, fbuf), drop + flist->ndx_start);
458 @@ -2866,7 +3130,7 @@ static void flist_sort_and_clean(struct file_list *flist, int strip_root)
459         }
460         flist->high = prev_i;
461  
462 -       if (strip_root) {
463 +       if (flags & CLEAN_STRIP_ROOT) {
464                 /* We need to strip off the leading slashes for relative
465                  * paths, but this must be done _after_ the sorting phase. */
466                 for (i = flist->low; i <= flist->high; i++) {
467 diff --git a/generator.c b/generator.c
468 --- a/generator.c
469 +++ b/generator.c
470 @@ -51,6 +51,7 @@ extern int delete_after;
471  extern int missing_args;
472  extern int msgdone_cnt;
473  extern int ignore_errors;
474 +extern int checksum_files;
475  extern int remove_source_files;
476  extern int delay_updates;
477  extern int update_only;
478 @@ -565,7 +566,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
479  
480  
481  /* Perform our quick-check heuristic for determining if a file is unchanged. */
482 -int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
483 +int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st, int slot)
484  {
485         if (st->st_size != F_LENGTH(file))
486                 return 0;
487 @@ -574,7 +575,10 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st)
488            of the file time to determine whether to sync */
489         if (always_checksum > 0 && S_ISREG(st->st_mode)) {
490                 char sum[MAX_DIGEST_LEN];
491 -               file_checksum(fn, sum, st->st_size);
492 +               if (checksum_files && slot >= 0)
493 +                       get_cached_checksum(slot, fn, file, st, sum);
494 +               else
495 +                       file_checksum(fn, st->st_size, sum);
496                 return memcmp(sum, F_SUM(file), checksum_len) == 0;
497         }
498  
499 @@ -867,7 +871,7 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
500                         match_level = 1;
501                         /* FALL THROUGH */
502                 case 1:
503 -                       if (!unchanged_file(cmpbuf, file, &sxp->st))
504 +                       if (!unchanged_file(cmpbuf, file, &sxp->st, j+1))
505                                 continue;
506                         best_match = j;
507                         match_level = 2;
508 @@ -1173,7 +1177,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
509          * --ignore-non-existing, daemon exclude, or mkdir failure. */
510         static struct file_struct *skip_dir = NULL;
511         static struct file_list *fuzzy_dirlist[MAX_BASIS_DIRS+1];
512 -       static int need_fuzzy_dirlist = 0;
513 +       static int need_new_dirscan = 0;
514         struct file_struct *fuzzy_file = NULL;
515         int fd = -1, f_copy = -1;
516         stat_x sx, real_sx;
517 @@ -1264,8 +1268,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
518                                                 fuzzy_dirlist[i] = NULL;
519                                         }
520                                 }
521 -                               need_fuzzy_dirlist = 1;
522 -                       }
523 +                               need_new_dirscan = 1;
524 +                       } else if (checksum_files)
525 +                               need_new_dirscan = 1;
526  #ifdef SUPPORT_ACLS
527                         if (!preserve_perms)
528                                 dflt_perms = default_perms_for_dir(dn);
529 @@ -1273,7 +1278,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
530                 }
531                 parent_dirname = dn;
532  
533 -               if (need_fuzzy_dirlist && S_ISREG(file->mode)) {
534 +               if (need_new_dirscan && S_ISREG(file->mode)) {
535                         int i;
536                         strlcpy(fnamecmpbuf, dn, sizeof fnamecmpbuf);
537                         for (i = 0; i < fuzzy_basis; i++) {
538 @@ -1285,7 +1290,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
539                                         fuzzy_dirlist[i] = NULL;
540                                 }
541                         }
542 -                       need_fuzzy_dirlist = 0;
543 +                       if (checksum_files) {
544 +                               reset_checksum_cache();
545 +                       }
546 +                       need_new_dirscan = 0;
547                 }
548  
549                 statret = link_stat(fname, &sx.st, keep_dirlinks && is_dir);
550 @@ -1724,7 +1732,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
551                 ;
552         else if (fnamecmp_type == FNAMECMP_FUZZY)
553                 ;
554 -       else if (unchanged_file(fnamecmp, file, &sx.st)) {
555 +       else if (unchanged_file(fnamecmp, file, &sx.st, fnamecmp_type == FNAMECMP_FNAME ? 0 : -1)) {
556                 if (partialptr) {
557                         do_unlink(partialptr);
558                         handle_partial_dir(partialptr, PDIR_DELETE);
559 diff --git a/hlink.c b/hlink.c
560 --- a/hlink.c
561 +++ b/hlink.c
562 @@ -410,7 +410,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname,
563                                 }
564                                 break;
565                         }
566 -                       if (!unchanged_file(cmpbuf, file, &alt_sx.st))
567 +                       if (!unchanged_file(cmpbuf, file, &alt_sx.st, j+1))
568                                 continue;
569                         statret = 1;
570                         if (unchanged_attrs(cmpbuf, file, &alt_sx))
571 diff --git a/itypes.h b/itypes.h
572 --- a/itypes.h
573 +++ b/itypes.h
574 @@ -23,6 +23,12 @@ isDigit(const char *ptr)
575  }
576  
577  static inline int
578 +isXDigit(const char *ptr)
579 +{
580 +       return isxdigit(*(unsigned char *)ptr);
581 +}
582 +
583 +static inline int
584  isPrint(const char *ptr)
585  {
586         return isprint(*(unsigned char *)ptr);
587 diff --git a/loadparm.c b/loadparm.c
588 --- a/loadparm.c
589 +++ b/loadparm.c
590 @@ -134,6 +134,7 @@ typedef struct {
591  /* NOTE: update this macro if the last char* variable changes! */
592  #define LOCAL_STRING_COUNT() (offsetof(local_vars, uid) / sizeof (char*) + 1)
593  
594 +       int checksum_files;
595         int max_connections;
596         int max_verbosity;
597         int syslog_facility;
598 @@ -208,6 +209,7 @@ static const all_vars Defaults = {
599   /* temp_dir; */               NULL,
600   /* uid; */                    NULL,
601  
602 + /* checksum_files; */         CSF_IGNORE_FILES,
603   /* max_connections; */                0,
604   /* max_verbosity; */          1,
605   /* syslog_facility; */                LOG_DAEMON,
606 @@ -310,6 +312,13 @@ static struct enum_list enum_facilities[] = {
607         { -1, NULL }
608  };
609  
610 +static struct enum_list enum_csum_modes[] = {
611 +       { CSF_IGNORE_FILES, "none" },
612 +       { CSF_LAX_MODE, "lax" },
613 +       { CSF_STRICT_MODE, "strict" },
614 +       { -1, NULL }
615 +};
616 +
617  static struct parm_struct parm_table[] =
618  {
619   {"address",           P_STRING, P_GLOBAL,&Vars.g.bind_address,        NULL,0},
620 @@ -321,6 +330,7 @@ static struct parm_struct parm_table[] =
621  
622   {"auth users",        P_STRING, P_LOCAL, &Vars.l.auth_users,          NULL,0},
623   {"charset",           P_STRING, P_LOCAL, &Vars.l.charset,             NULL,0},
624 + {"checksum files",    P_ENUM,   P_LOCAL, &Vars.l.checksum_files,      enum_csum_modes,0},
625   {"comment",           P_STRING, P_LOCAL, &Vars.l.comment,             NULL,0},
626   {"dont compress",     P_STRING, P_LOCAL, &Vars.l.dont_compress,       NULL,0},
627   {"exclude from",      P_STRING, P_LOCAL, &Vars.l.exclude_from,        NULL,0},
628 @@ -477,6 +487,7 @@ FN_LOCAL_STRING(lp_secrets_file, secrets_file)
629  FN_LOCAL_STRING(lp_temp_dir, temp_dir)
630  FN_LOCAL_STRING(lp_uid, uid)
631  
632 +FN_LOCAL_INTEGER(lp_checksum_files, checksum_files)
633  FN_LOCAL_INTEGER(lp_max_connections, max_connections)
634  FN_LOCAL_INTEGER(lp_max_verbosity, max_verbosity)
635  FN_LOCAL_INTEGER(lp_syslog_facility, syslog_facility)
636 diff --git a/options.c b/options.c
637 --- a/options.c
638 +++ b/options.c
639 @@ -113,6 +113,7 @@ size_t bwlimit_writemax = 0;
640  int ignore_existing = 0;
641  int ignore_non_existing = 0;
642  int need_messages_from_generator = 0;
643 +int checksum_files = CSF_IGNORE_FILES;
644  int max_delete = INT_MIN;
645  OFF_T max_size = -1;
646  OFF_T min_size = -1;
647 @@ -671,6 +672,7 @@ void usage(enum logcode F)
648    rprintf(F," -q, --quiet                 suppress non-error messages\n");
649    rprintf(F,"     --no-motd               suppress daemon-mode MOTD (see manpage caveat)\n");
650    rprintf(F," -c, --checksum              skip based on checksum, not mod-time & size\n");
651 +  rprintf(F,"     --sumfiles=MODE         use .rsyncsums to speedup --checksum mode\n");
652    rprintf(F," -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)\n");
653    rprintf(F,"     --no-OPTION             turn off an implied OPTION (e.g. --no-D)\n");
654    rprintf(F," -r, --recursive             recurse into directories\n");
655 @@ -817,7 +819,7 @@ enum {OPT_VERSION = 1000, OPT_DAEMON, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
656        OPT_FILTER, OPT_COMPARE_DEST, OPT_COPY_DEST, OPT_LINK_DEST, OPT_HELP,
657        OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW, OPT_MIN_SIZE, OPT_CHMOD,
658        OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_ONLY_WRITE_BATCH, OPT_MAX_SIZE,
659 -      OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG,
660 +      OPT_NO_D, OPT_APPEND, OPT_NO_ICONV, OPT_INFO, OPT_DEBUG, OPT_SUMFILES,
661        OPT_USERMAP, OPT_GROUPMAP, OPT_CHOWN, OPT_BWLIMIT,
662        OPT_SERVER, OPT_REFUSED_BASE = 9000};
663  
664 @@ -957,6 +959,7 @@ static struct poptOption long_options[] = {
665    {"checksum",        'c', POPT_ARG_VAL,    &always_checksum, 1, 0, 0 },
666    {"no-checksum",      0,  POPT_ARG_VAL,    &always_checksum, 0, 0, 0 },
667    {"no-c",             0,  POPT_ARG_VAL,    &always_checksum, 0, 0, 0 },
668 +  {"sumfiles",         0,  POPT_ARG_STRING, 0, OPT_SUMFILES, 0, 0 },
669    {"block-size",      'B', POPT_ARG_LONG,   &block_size, 0, 0, 0 },
670    {"compare-dest",     0,  POPT_ARG_STRING, 0, OPT_COMPARE_DEST, 0, 0 },
671    {"copy-dest",        0,  POPT_ARG_STRING, 0, OPT_COPY_DEST, 0, 0 },
672 @@ -1682,6 +1685,23 @@ int parse_arguments(int *argc_p, const char ***argv_p)
673                         }
674                         break;
675  
676 +               case OPT_SUMFILES:
677 +                       arg = poptGetOptArg(pc);
678 +                       checksum_files = 0;
679 +                       if (strcmp(arg, "lax") == 0)
680 +                               checksum_files |= CSF_LAX_MODE;
681 +                       else if (strcmp(arg, "strict") == 0)
682 +                               checksum_files |= CSF_STRICT_MODE;
683 +                       else if (strcmp(arg, "none") == 0)
684 +                               checksum_files = CSF_IGNORE_FILES;
685 +                       else {
686 +                               snprintf(err_buf, sizeof err_buf,
687 +                                   "Invalid argument passed to --sumfiles (%s)\n",
688 +                                   arg);
689 +                               return 0;
690 +                       }
691 +                       break;
692 +
693                 case OPT_INFO:
694                         arg = poptGetOptArg(pc);
695                         parse_output_words(info_words, info_levels, arg, USER_PRIORITY);
696 @@ -1923,6 +1943,9 @@ int parse_arguments(int *argc_p, const char ***argv_p)
697         }
698  #endif
699  
700 +       if (!always_checksum)
701 +               checksum_files = CSF_IGNORE_FILES;
702 +
703         if (block_size > MAX_BLOCK_SIZE) {
704                 snprintf(err_buf, sizeof err_buf,
705                          "--block-size=%lu is too large (max: %u)\n", block_size, MAX_BLOCK_SIZE);
706 diff --git a/rsync.h b/rsync.h
707 --- a/rsync.h
708 +++ b/rsync.h
709 @@ -771,6 +771,10 @@ extern int xattrs_ndx;
710  #define F_SUM(f) ((char*)OPT_EXTRA(f, START_BUMP(f) + HLINK_BUMP(f) \
711                                     + SUM_EXTRA_CNT - 1))
712  
713 +/* These are only valid on an entry read from a checksum file. */
714 +#define F_CTIME(f) OPT_EXTRA(f, LEN64_BUMP(f) + SUM_EXTRA_CNT)->unum
715 +#define F_INODE(f) OPT_EXTRA(f, LEN64_BUMP(f) + SUM_EXTRA_CNT + 1)->unum
716 +
717  /* Some utility defines: */
718  #define F_IS_ACTIVE(f) (f)->basename[0]
719  #define F_IS_HLINKED(f) ((f)->flags & FLAG_HLINKED)
720 @@ -967,6 +971,13 @@ typedef struct {
721         char fname[1]; /* has variable size */
722  } relnamecache;
723  
724 +#define CSF_ENABLE (1<<1)
725 +#define CSF_LAX (1<<2)
726 +
727 +#define CSF_IGNORE_FILES 0
728 +#define CSF_LAX_MODE (CSF_ENABLE|CSF_LAX)
729 +#define CSF_STRICT_MODE (CSF_ENABLE)
730 +
731  #include "byteorder.h"
732  #include "lib/mdigest.h"
733  #include "lib/wildmatch.h"
734 diff --git a/rsync.yo b/rsync.yo
735 --- a/rsync.yo
736 +++ b/rsync.yo
737 @@ -337,6 +337,7 @@ to the detailed description below for a complete description.  verb(
738   -q, --quiet                 suppress non-error messages
739       --no-motd               suppress daemon-mode MOTD (see caveat)
740   -c, --checksum              skip based on checksum, not mod-time & size
741 +     --sumfiles=MODE         use .rsyncsums to speedup --checksum mode
742   -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
743       --no-OPTION             turn off an implied OPTION (e.g. --no-D)
744   -r, --recursive             recurse into directories
745 @@ -606,9 +607,9 @@ uses a "quick check" that (by default) checks if each file's size and time
746  of last modification match between the sender and receiver.  This option
747  changes this to compare a 128-bit checksum for each file that has a
748  matching size.  Generating the checksums means that both sides will expend
749 -a lot of disk I/O reading all the data in the files in the transfer (and
750 -this is prior to any reading that will be done to transfer changed files),
751 -so this can slow things down significantly.
752 +a lot of disk I/O reading the data in all the files in the transfer, so
753 +this can slow things down significantly (and this is prior to any reading
754 +that will be done to transfer the files that have changed).
755  
756  The sending side generates its checksums while it is doing the file-system
757  scan that builds the list of the available files.  The receiver generates
758 @@ -616,6 +617,8 @@ its checksums when it is scanning for changed files, and will checksum any
759  file that has the same size as the corresponding sender's file:  files with
760  either a changed size or a changed checksum are selected for transfer.
761  
762 +See also the bf(--sumfiles) option for a way to use cached checksum data.
763 +
764  Note that rsync always verifies that each em(transferred) file was
765  correctly reconstructed on the receiving side by checking a whole-file
766  checksum that is generated as the file is transferred, but that
767 @@ -625,6 +628,36 @@ option's before-the-transfer "Does this file need to be updated?" check.
768  For protocol 30 and beyond (first supported in 3.0.0), the checksum used is
769  MD5.  For older protocols, the checksum used is MD4.
770  
771 +dit(bf(--sumfiles=MODE)) This option tells rsync to make use of any cached
772 +checksum information it finds in per-directory .rsyncsums files when the
773 +current transfer is using the bf(--checksum) option.  If the checksum data
774 +is up-to-date, it is used instead of recomputing it, saving both disk I/O
775 +and CPU time.  If the checksum data is missing or outdated, the checksum is
776 +computed just as it would be if bf(--sumfiles) was not specified.
777 +
778 +The MODE value is either "lax", for relaxed checking (which compares size
779 +and mtime), "strict" (which also compares ctime and inode), or "none" to
780 +ignore any .rsyncsums files ("none" is the default).  Rsync does not create
781 +or update these files, but there is a perl script in the support directory
782 +named "rsyncsums" that can be used for that.
783 +
784 +This option has no effect unless bf(--checksum, -c) was also specified.  It
785 +also only affects the current side of the transfer, so if you want the
786 +remote side to parse its own .rsyncsums files, specify the option via the
787 +bf(--rsync-path) option (e.g. "--rsync-path="rsync --sumfiles=lax").
788 +
789 +To avoid transferring the system's checksum files, you can use an exclude
790 +(e.g. bf(--exclude=.rsyncsums)).  To make this easier to type, you can use
791 +a popt alias.  For instance, adding the following line in your ~/.popt file
792 +defines a bf(--cc) option that enables lax checksum files and excludes the
793 +checksum files:
794 +
795 +verb(  rsync alias --cc -c --sumfiles=lax --exclude=.rsyncsums)
796 +
797 +An rsync daemon does not allow the client to control this setting, so see
798 +the "checksum files" daemon parameter for information on how to make a
799 +daemon use cached checksum data.
800 +
801  dit(bf(-a, --archive)) This is equivalent to bf(-rlptgoD). It is a quick
802  way of saying you want recursion and want to preserve almost
803  everything (with -H being a notable omission).
804 diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
805 --- a/rsyncd.conf.yo
806 +++ b/rsyncd.conf.yo
807 @@ -324,6 +324,17 @@ locking on this file to ensure that the max connections limit is not
808  exceeded for the modules sharing the lock file.
809  The default is tt(/var/run/rsyncd.lock).
810  
811 +dit(bf(checksum files)) This parameter tells rsync to make use of any cached
812 +checksum information it finds in per-directory .rsyncsums files when the
813 +current transfer is using the bf(--checksum) option.  The value can be set
814 +to either "lax", "strict", or "none" -- see the client's bf(--sumfiles)
815 +option for what these choices do.
816 +
817 +Note also that the client's command-line option, bf(--sumfiles), has no
818 +effect on a daemon.  A daemon will only access checksum files if this
819 +config option tells it to.  See also the bf(exclude) directive for a way
820 +to hide the .rsyncsums files from the user.
821 +
822  dit(bf(read only)) This parameter determines whether clients
823  will be able to upload files or not. If "read only" is true then any
824  attempted uploads will fail. If "read only" is false then uploads will
825 diff --git a/support/rsyncsums b/support/rsyncsums
826 new file mode 100755
827 --- /dev/null
828 +++ b/support/rsyncsums
829 @@ -0,0 +1,201 @@
830 +#!/usr/bin/perl -w
831 +use strict;
832 +
833 +use Getopt::Long;
834 +use Cwd qw(abs_path cwd);
835 +use Digest::MD4;
836 +use Digest::MD5;
837 +
838 +our $SUMS_FILE = '.rsyncsums';
839 +
840 +&Getopt::Long::Configure('bundling');
841 +&usage if !&GetOptions(
842 +    'recurse|r' => \( my $recurse_opt ),
843 +    'mode|m=s' => \( my $cmp_mode = 'strict' ),
844 +    'check|c' => \( my $check_opt ),
845 +    'verbose|v+' => \( my $verbosity = 0 ),
846 +    'help|h' => \( my $help_opt ),
847 +);
848 +&usage if $help_opt || $cmp_mode !~ /^(lax|strict)$/;
849 +
850 +my $ignore_ctime_and_inode = $cmp_mode eq 'lax' ? 0 : 1;
851 +
852 +my $start_dir = cwd();
853 +
854 +my @dirs = @ARGV;
855 +@dirs = '.' unless @dirs;
856 +foreach (@dirs) {
857 +    $_ = abs_path($_);
858 +}
859 +
860 +$| = 1;
861 +
862 +my $exit_code = 0;
863 +
864 +my $md4 = Digest::MD4->new;
865 +my $md5 = Digest::MD5->new;
866 +
867 +while (@dirs) {
868 +    my $dir = shift @dirs;
869 +
870 +    if (!chdir($dir)) {
871 +       warn "Unable to chdir to $dir: $!\n";
872 +       next;
873 +    }
874 +    if (!opendir(DP, '.')) {
875 +       warn "Unable to opendir $dir: $!\n";
876 +       next;
877 +    }
878 +
879 +    my $reldir = $dir;
880 +    $reldir =~ s#^$start_dir(/|$)# $1 ? '' : '.' #eo;
881 +    if ($verbosity) {
882 +       print "$reldir ... ";
883 +       print "\n" if $check_opt;
884 +    }
885 +
886 +    my %cache;
887 +    my $f_cnt = 0;
888 +    if (open(FP, '<', $SUMS_FILE)) {
889 +       while (<FP>) {
890 +           chomp;
891 +           my($sum4, $sum5, $size, $mtime, $ctime, $inode, $fn) = split(' ', $_, 7);
892 +           $cache{$fn} = [ 0, $sum4, $sum5, $size, $mtime, $ctime & 0xFFFFFFFF, $inode & 0xFFFFFFFF ];
893 +           $f_cnt++;
894 +       }
895 +       close FP;
896 +    }
897 +
898 +    my @subdirs;
899 +    my $d_cnt = 0;
900 +    my $update_cnt = 0;
901 +    while (defined(my $fn = readdir(DP))) {
902 +       next if $fn =~ /^\.\.?$/ || $fn =~ /^\Q$SUMS_FILE\E$/o || -l $fn;
903 +       if (-d _) {
904 +           push(@subdirs, "$dir/$fn") unless $fn =~ /^(CVS|\.svn|\.git|\.bzr)$/;
905 +           next;
906 +       }
907 +       next unless -f _;
908 +
909 +       my($size,$mtime,$ctime,$inode) = (stat(_))[7,9,10,1];
910 +       $ctime &= 0xFFFFFFFF;
911 +       $inode &= 0xFFFFFFFF;
912 +       my $ref = $cache{$fn};
913 +       $d_cnt++;
914 +
915 +       if (!$check_opt) {
916 +           if (defined $ref) {
917 +               $$ref[0] = 1;
918 +               if ($$ref[3] == $size
919 +                && $$ref[4] == $mtime
920 +                && ($ignore_ctime_and_inode || ($$ref[5] == $ctime && $$ref[6] == $inode))
921 +                && $$ref[1] !~ /=/ && $$ref[2] !~ /=/) {
922 +                   next;
923 +               }
924 +           }
925 +           if (!$update_cnt++) {
926 +               print "UPDATING\n" if $verbosity;
927 +           }
928 +       }
929 +
930 +       if (!open(IN, $fn)) {
931 +           print STDERR "Unable to read $fn: $!\n";
932 +           if (defined $ref) {
933 +               delete $cache{$fn};
934 +               $f_cnt--;
935 +           }
936 +           next;
937 +       }
938 +
939 +       my($sum4, $sum5);
940 +       while (1) {
941 +           while (sysread(IN, $_, 64*1024)) {
942 +               $md4->add($_);
943 +               $md5->add($_);
944 +           }
945 +           $sum4 = $md4->hexdigest;
946 +           $sum5 = $md5->hexdigest;
947 +           print " $sum4 $sum5" if $verbosity > 2;
948 +           print " $fn" if $verbosity > 1;
949 +           my($size2,$mtime2,$ctime2,$inode2) = (stat(IN))[7,9,10,1];
950 +           $ctime2 &= 0xFFFFFFFF;
951 +           $inode2 &= 0xFFFFFFFF;
952 +           last if $size == $size2 && $mtime == $mtime2
953 +            && ($ignore_ctime_and_inode || ($ctime == $ctime2 && $inode == $inode2));
954 +           $size = $size2;
955 +           $mtime = $mtime2;
956 +           $ctime = $ctime2;
957 +           $inode = $inode2;
958 +           sysseek(IN, 0, 0);
959 +           print " REREADING\n" if $verbosity > 1;
960 +       }
961 +
962 +       close IN;
963 +
964 +       if ($check_opt) {
965 +           my $dif;
966 +           if (!defined $ref) {
967 +               $dif = 'MISSING';
968 +           } elsif ($sum4 ne $$ref[1] || $sum5 ne $$ref[2]) {
969 +               $dif = 'FAILED';
970 +           } else {
971 +               print " OK\n" if $verbosity > 1;
972 +               next;
973 +           }
974 +           if ($verbosity < 2) {
975 +               print $verbosity ? ' ' : "$reldir/";
976 +               print $fn;
977 +           }
978 +           print " $dif\n";
979 +           $exit_code = 1;
980 +       } else {
981 +           print "\n" if $verbosity > 1;
982 +           $cache{$fn} = [ 1, $sum4, $sum5, $size, $mtime, $ctime, $inode ];
983 +       }
984 +    }
985 +
986 +    closedir DP;
987 +
988 +    unshift(@dirs, sort @subdirs) if $recurse_opt;
989 +
990 +    if ($check_opt) {
991 +       ;
992 +    } elsif ($d_cnt == 0) {
993 +       if ($f_cnt) {
994 +           print "(removed $SUMS_FILE) " if $verbosity;
995 +           unlink($SUMS_FILE);
996 +       }
997 +       print "empty\n" if $verbosity;
998 +    } elsif ($update_cnt || $d_cnt != $f_cnt) {
999 +       print "UPDATING\n" if $verbosity && !$update_cnt;
1000 +       open(FP, '>', $SUMS_FILE) or die "Unable to write $dir/$SUMS_FILE: $!\n";
1001 +
1002 +       foreach my $fn (sort keys %cache) {
1003 +           my $ref = $cache{$fn};
1004 +           my($found, $sum4, $sum5, $size, $mtime, $ctime, $inode) = @$ref;
1005 +           next unless $found;
1006 +           printf FP '%s %s %10d %10d %10d %10d %s' . "\n", $sum4, $sum5, $size, $mtime, $ctime, $inode, $fn;
1007 +       }
1008 +       close FP;
1009 +    } else {
1010 +       print "ok\n" if $verbosity;
1011 +    }
1012 +}
1013 +
1014 +exit $exit_code;
1015 +
1016 +sub usage
1017 +{
1018 +    die <<EOT;
1019 +Usage: rsyncsums [OPTIONS] [DIRS]
1020 +
1021 +Options:
1022 + -r, --recurse     Update $SUMS_FILE files in subdirectories too.
1023 + -m, --mode=MODE   Compare entries in either "lax" or "strict" mode.  Using
1024 +                   "lax" compares size and mtime, while "strict" additionally
1025 +                   compares ctime and inode.  Default:  strict.
1026 + -c, --check       Check if the checksums are right (doesn't update).
1027 + -v, --verbose     Mention what we're doing.  Repeat for more info.
1028 + -h, --help        Display this help message.
1029 +EOT
1030 +}