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