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