8a7a5ca849f8977b4ef13ade3ff23830e770e304
[rsync-patches.git] / checksum-updating.diff
1 This builds on the checksum-reading patch and adds the ability to
2 create and/or update the .rsyncsums files using extended mode args to
3 the --sumfiles=MODE option and the "checksum files = MODE" daemon
4 parameter.
5
6 CAUTION:  This patch is only lightly tested.  If you're interested
7 in using it, please help out.
8
9 To use this patch, run these commands for a successful build:
10
11     patch -p1 <patches/checksum-reading.diff
12     patch -p1 <patches/checksum-updating.diff
13     ./configure                               (optional if already run)
14     make
15
16 TODO:
17
18  - Fix the code that removes .rsyncsums files when a dir becomes empty.
19
20 diff --git a/flist.c b/flist.c
21 --- a/flist.c
22 +++ b/flist.c
23 @@ -26,6 +26,7 @@
24  #include "io.h"
25  
26  extern int verbose;
27 +extern int dry_run;
28  extern int am_root;
29  extern int am_server;
30  extern int am_daemon;
31 @@ -93,6 +94,9 @@ extern iconv_t ic_send, ic_recv;
32  
33  #define PTR_SIZE (sizeof (struct file_struct *))
34  
35 +#define FLAG_SUM_MISSING (1<<1) /* F_SUM() data is undefined */
36 +#define FLAG_SUM_KEEP (1<<2) /* keep entry when rewriting */
37 +
38  int io_error;
39  int checksum_len;
40  dev_t filesystem_dev; /* used to implement -x */
41 @@ -132,8 +136,13 @@ static char empty_sum[MAX_DIGEST_LEN];
42  static int flist_count_offset; /* for --delete --progress */
43  static int dir_count = 0;
44  
45 +#define REGULAR_SKIPPED(flist) ((flist)->to_redo)
46 +
47  static struct csum_cache {
48         struct file_list *flist;
49 +       const char *dirname;
50 +       int checksum_matches;
51 +       int checksum_updates;
52  } *csum_cache = NULL;
53  
54  static void flist_sort_and_clean(struct file_list *flist, int flags);
55 @@ -327,7 +336,79 @@ static void flist_done_allocating(struct file_list *flist)
56                 flist->pool_boundary = ptr;
57  }
58  
59 -void reset_checksum_cache()
60 +static void checksum_filename(int slot, const char *dirname, char *fbuf)
61 +{
62 +       if (dirname && *dirname) {
63 +               unsigned int len;
64 +               if (slot) {
65 +                       len = strlcpy(fbuf, basis_dir[slot-1], MAXPATHLEN);
66 +                       if (len >= MAXPATHLEN)
67 +                               return;
68 +               } else
69 +                       len = 0;
70 +               if (pathjoin(fbuf+len, MAXPATHLEN-len, dirname, RSYNCSUMS_FILE) >= MAXPATHLEN-len)
71 +                       return;
72 +       } else
73 +               strlcpy(fbuf, RSYNCSUMS_FILE, MAXPATHLEN);
74 +}
75 +
76 +static void write_checksums(int slot, struct file_list *flist, int whole_dir)
77 +{
78 +       int i;
79 +       FILE *out_fp;
80 +       char fbuf[MAXPATHLEN];
81 +       int new_entries = csum_cache[slot].checksum_updates != 0;
82 +       int counts_match = flist->used == csum_cache[slot].checksum_matches;
83 +       int no_skipped = whole_dir && REGULAR_SKIPPED(flist) == 0;
84 +       const char *dirname = csum_cache[slot].dirname;
85 +
86 +       flist_sort_and_clean(flist, 0);
87 +
88 +       if (dry_run && !(checksum_files & CSF_AFFECT_DRYRUN))
89 +               return;
90 +
91 +       checksum_filename(slot, dirname, fbuf);
92 +
93 +       if (flist->high - flist->low < 0 && no_skipped) {
94 +               unlink(fbuf);
95 +               return;
96 +       }
97 +
98 +       if (!new_entries && (counts_match || !whole_dir))
99 +               return;
100 +
101 +       if (!(out_fp = fopen(fbuf, "w")))
102 +               return;
103 +
104 +       for (i = flist->low; i <= flist->high; i++) {
105 +               struct file_struct *file = flist->sorted[i];
106 +               const char *cp = F_SUM(file);
107 +               const char *end = cp + checksum_len;
108 +               const char *alt_sum = file->basename + strlen(file->basename) + 1;
109 +               if (whole_dir && !(file->flags & FLAG_SUM_KEEP))
110 +                       continue;
111 +               if (protocol_version >= 30)
112 +                       fprintf(out_fp, "%s ", alt_sum);
113 +               if (file->flags & FLAG_SUM_MISSING) {
114 +                       do {
115 +                               fputs("==", out_fp);
116 +                       } while (++cp != end);
117 +               } else {
118 +                       do {
119 +                               fprintf(out_fp, "%02x", (int)CVAL(cp, 0));
120 +                       } while (++cp != end);
121 +               }
122 +               if (protocol_version < 30)
123 +                       fprintf(out_fp, " %s", alt_sum);
124 +               fprintf(out_fp, " %10.0f %10.0f %10lu %10lu %s\n",
125 +                       (double)F_LENGTH(file), (double)file->modtime,
126 +                       (long)F_CTIME(file), (long)F_INODE(file), file->basename);
127 +       }
128 +
129 +       fclose(out_fp);
130 +}
131 +
132 +void reset_checksum_cache(int whole_dir)
133  {
134         int slot, slots = am_sender ? 1 : basis_dir_cnt + 1;
135  
136 @@ -341,6 +422,9 @@ void reset_checksum_cache()
137                 struct file_list *flist = csum_cache[slot].flist;
138  
139                 if (flist) {
140 +                       if (checksum_files & CSF_UPDATE && flist->next)
141 +                               write_checksums(slot, flist, whole_dir);
142 +
143                         /* Reset the pool memory and empty the file-list array. */
144                         pool_free_old(flist->file_pool,
145                                       pool_boundary(flist->file_pool, 0));
146 @@ -351,6 +435,10 @@ void reset_checksum_cache()
147                 flist->low = 0;
148                 flist->high = -1;
149                 flist->next = NULL;
150 +
151 +               csum_cache[slot].checksum_matches = 0;
152 +               csum_cache[slot].checksum_updates = 0;
153 +               REGULAR_SKIPPED(flist) = 0;
154         }
155  }
156  
157 @@ -359,7 +447,7 @@ void reset_checksum_cache()
158  static int add_checksum(struct file_list *flist, const char *dirname,
159                         const char *basename, int basename_len, OFF_T file_length,
160                         time_t mtime, uint32 ctime, uint32 inode,
161 -                       const char *sum)
162 +                       const char *sum, const char *alt_sum, int flags)
163  {
164         struct file_struct *file;
165         int alloc_len, extra_len;
166 @@ -375,7 +463,7 @@ static int add_checksum(struct file_list *flist, const char *dirname,
167         if (extra_len & (EXTRA_ROUNDING * EXTRA_LEN))
168                 extra_len = (extra_len | (EXTRA_ROUNDING * EXTRA_LEN)) + EXTRA_LEN;
169  #endif
170 -       alloc_len = FILE_STRUCT_LEN + extra_len + basename_len;
171 +       alloc_len = FILE_STRUCT_LEN + extra_len + basename_len + checksum_len*2 + 1;
172         bp = pool_alloc(flist->file_pool, alloc_len, "add_checksum");
173  
174         memset(bp, 0, extra_len + FILE_STRUCT_LEN);
175 @@ -384,7 +472,14 @@ static int add_checksum(struct file_list *flist, const char *dirname,
176         bp += FILE_STRUCT_LEN;
177  
178         memcpy(bp, basename, basename_len);
179 +       if (alt_sum)
180 +               strlcpy(bp+basename_len, alt_sum, checksum_len*2 + 1);
181 +       else {
182 +               memset(bp+basename_len, '=', checksum_len*2);
183 +               bp[basename_len+checksum_len*2] = '\0';
184 +       }
185  
186 +       file->flags = flags;
187         file->mode = S_IFREG;
188         file->modtime = mtime;
189         file->len32 = (uint32)file_length;
190 @@ -413,10 +508,11 @@ static void read_checksums(int slot, struct file_list *flist, const char *dirnam
191         char line[MAXPATHLEN+1024], fbuf[MAXPATHLEN], sum[MAX_DIGEST_LEN];
192         FILE *fp;
193         char *cp;
194 -       int len, i;
195         time_t mtime;
196 +       int len, i, flags;
197         OFF_T file_length;
198         uint32 ctime, inode;
199 +       const char *alt_sum = NULL;
200         int dlen = dirname ? strlcpy(fbuf, dirname, sizeof fbuf) : 0;
201  
202         if (dlen >= (int)(sizeof fbuf - sizeof RSYNCSUMS_FILE))
203 @@ -437,7 +533,7 @@ static void read_checksums(int slot, struct file_list *flist, const char *dirnam
204         while (fgets(line, sizeof line, fp)) {
205                 cp = line;
206                 if (protocol_version >= 30) {
207 -                       char *alt_sum = cp;
208 +                       alt_sum = cp;
209                         if (*cp == '=')
210                                 while (*++cp == '=') {}
211                         else
212 @@ -448,7 +544,14 @@ static void read_checksums(int slot, struct file_list *flist, const char *dirnam
213                 }
214  
215                 if (*cp == '=') {
216 -                       continue;
217 +                       for (i = 0; i < checksum_len*2; i++, cp++) {
218 +                               if (*cp != '=') {
219 +                                       cp = "";
220 +                                       break;
221 +                               }
222 +                       }
223 +                       memset(sum, 0, checksum_len);
224 +                       flags = FLAG_SUM_MISSING;
225                 } else {
226                         for (i = 0; i < checksum_len*2; i++, cp++) {
227                                 int x;
228 @@ -466,13 +569,14 @@ static void read_checksums(int slot, struct file_list *flist, const char *dirnam
229                                 else
230                                         sum[i/2] = x << 4;
231                         }
232 +                       flags = 0;
233                 }
234                 if (*cp != ' ')
235                         break;
236                 while (*++cp == ' ') {}
237  
238                 if (protocol_version < 30) {
239 -                       char *alt_sum = cp;
240 +                       alt_sum = cp;
241                         if (*cp == '=')
242                                 while (*++cp == '=') {}
243                         else
244 @@ -522,24 +626,112 @@ static void read_checksums(int slot, struct file_list *flist, const char *dirnam
245                         continue;
246  
247                 strlcpy(fbuf+dlen, cp, sizeof fbuf - dlen);
248 +               if (is_excluded(fbuf, 0, ALL_FILTERS)) {
249 +                       flags |= FLAG_SUM_KEEP;
250 +                       csum_cache[slot].checksum_matches++;
251 +               }
252  
253                 add_checksum(flist, dirname, cp, len, file_length,
254                              mtime, ctime, inode,
255 -                            sum);
256 +                            sum, alt_sum, flags);
257         }
258         fclose(fp);
259  
260         flist_sort_and_clean(flist, CLEAN_KEEP_LAST);
261  }
262  
263 +void set_cached_checksum(struct file_list *file_flist, struct file_struct *file)
264 +{
265 +       int j;
266 +       FILE *out_fp;
267 +       STRUCT_STAT st;
268 +       char fbuf[MAXPATHLEN];
269 +       const char *fn = f_name(file, NULL);
270 +       struct file_list *flist = csum_cache[0].flist;
271 +
272 +       if (dry_run && !(checksum_files & CSF_AFFECT_DRYRUN))
273 +               return;
274 +
275 +       if (stat(fn, &st) < 0)
276 +               return;
277 +
278 +       checksum_filename(0, file->dirname, fbuf);
279 +
280 +       if (file_flist != flist->next) {
281 +               const char *cp = F_SUM(file);
282 +               const char *end = cp + checksum_len;
283 +
284 +               if (!(out_fp = fopen(fbuf, "a")))
285 +                       return;
286 +
287 +               if (protocol_version >= 30) {
288 +                       for (j = 0; j < checksum_len; j++)
289 +                               fputs("==", out_fp);
290 +                       fputc(' ', out_fp);
291 +               }
292 +               do {
293 +                       fprintf(out_fp, "%02x", (int)CVAL(cp, 0));
294 +               } while (++cp != end);
295 +               if (protocol_version < 30) {
296 +                       fputc(' ', out_fp);
297 +                       for (j = 0; j < checksum_len; j++)
298 +                               fputs("==", out_fp);
299 +               }
300 +               fprintf(out_fp, " %10.0f %10.0f %10lu %10lu %s\n",
301 +                       (double)st.st_size, (double)st.st_mtime,
302 +                       (long)(uint32)st.st_ctime, (long)(uint32)st.st_ino,
303 +                       file->basename);
304 +
305 +               fclose(out_fp);
306 +               return;
307 +       }
308 +
309 +       if ((j = flist_find(flist, file)) >= 0) {
310 +               struct file_struct *fp = flist->sorted[j];
311 +               int inc = 0;
312 +               if (F_LENGTH(fp) != st.st_size) {
313 +                       fp->len32 = (uint32)st.st_size;
314 +                       if (st.st_size > 0xFFFFFFFFu) {
315 +                               OPT_EXTRA(fp, 0)->unum = (uint32)(st.st_size >> 32);
316 +                               fp->flags |= FLAG_LENGTH64;
317 +                       } else
318 +                               fp->flags &= FLAG_LENGTH64;
319 +                       inc = 1;
320 +               }
321 +               if (fp->modtime != st.st_mtime) {
322 +                       fp->modtime = st.st_mtime;
323 +                       inc = 1;
324 +               }
325 +               if (F_CTIME(fp) != (uint32)st.st_ctime) {
326 +                       F_CTIME(fp) = (uint32)st.st_ctime;
327 +                       inc = 1;
328 +               }
329 +               if (F_INODE(fp) != (uint32)st.st_ino) {
330 +                       F_INODE(fp) = (uint32)st.st_ino;
331 +                       inc = 1;
332 +               }
333 +               memcpy(F_SUM(fp), F_SUM(file), MAX_DIGEST_LEN);
334 +               csum_cache[0].checksum_updates += inc;
335 +               fp->flags &= ~FLAG_SUM_MISSING;
336 +               fp->flags |= FLAG_SUM_KEEP;
337 +               return;
338 +       }
339 +
340 +       csum_cache[0].checksum_updates +=
341 +           add_checksum(flist, file->dirname, file->basename, strlen(file->basename) + 1,
342 +                        st.st_size, (uint32)st.st_mtime, (uint32)st.st_ctime,
343 +                        st.st_ino, F_SUM(file), NULL, FLAG_SUM_KEEP);
344 +}
345 +
346  void get_cached_checksum(int slot, const char *fname, struct file_struct *file,
347 -                        STRUCT_STAT *stp, char *sum_buf)
348 +                        int basename_len, STRUCT_STAT *stp, char *sum_buf)
349  {
350         struct file_list *flist = csum_cache[slot].flist;
351         int j;
352  
353         if (!flist->next) {
354                 flist->next = cur_flist; /* next points from checksum flist to file flist */
355 +               csum_cache[slot].dirname = file->dirname;
356                 read_checksums(slot, flist, file->dirname);
357         }
358  
359 @@ -551,12 +743,31 @@ void get_cached_checksum(int slot, const char *fname, struct file_struct *file,
360                  && (checksum_files & CSF_LAX
361                   || (F_CTIME(fp) == (uint32)stp->st_ctime
362                    && F_INODE(fp) == (uint32)stp->st_ino))) {
363 -                       memcpy(sum_buf, F_SUM(fp), MAX_DIGEST_LEN);
364 +                       if (fp->flags & FLAG_SUM_MISSING) {
365 +                               fp->flags &= ~FLAG_SUM_MISSING;
366 +                               csum_cache[slot].checksum_updates++;
367 +                               file_checksum(fname, stp->st_size, sum_buf);
368 +                               memcpy(F_SUM(fp), sum_buf, MAX_DIGEST_LEN);
369 +                       } else {
370 +                               csum_cache[slot].checksum_matches++;
371 +                               memcpy(sum_buf, F_SUM(fp), MAX_DIGEST_LEN);
372 +                       }
373 +                       fp->flags |= FLAG_SUM_KEEP;
374                         return;
375                 }
376 +               clear_file(fp);
377         }
378  
379         file_checksum(fname, stp->st_size, sum_buf);
380 +
381 +       if (checksum_files & CSF_UPDATE) {
382 +               if (basename_len < 0)
383 +                       basename_len = strlen(file->basename) + 1;
384 +               csum_cache[slot].checksum_updates +=
385 +                   add_checksum(flist, file->dirname, file->basename, basename_len,
386 +                                stp->st_size, stp->st_mtime, (uint32)stp->st_ctime,
387 +                                (uint32)stp->st_ino, sum_buf, NULL, FLAG_SUM_KEEP);
388 +       }
389  }
390  
391  int push_pathname(const char *dir, int len)
392 @@ -1343,6 +1554,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
393         if (is_excluded(thisname, S_ISDIR(st.st_mode) != 0, filter_level)) {
394                 if (ignore_perishable)
395                         non_perishable_cnt++;
396 +               if (S_ISREG(st.st_mode))
397 +                       REGULAR_SKIPPED(flist)++;
398                 return NULL;
399         }
400  
401 @@ -1383,13 +1596,13 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
402                         lastdir[len] = '\0';
403                         lastdir_len = len;
404                         if (checksum_files && am_sender && flist)
405 -                               reset_checksum_cache();
406 +                               reset_checksum_cache(0);
407                 }
408         } else {
409                 basename = thisname;
410                 if (checksum_files && am_sender && flist && lastdir_len == -2) {
411                         lastdir_len = -1;
412 -                       reset_checksum_cache();
413 +                       reset_checksum_cache(0);
414                 }
415         }
416         basename_len = strlen(basename) + 1; /* count the '\0' */
417 @@ -1471,7 +1684,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
418  
419         if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
420                 if (flist && checksum_files)
421 -                       get_cached_checksum(0, thisname, file, &st, tmp_sum);
422 +                       get_cached_checksum(0, thisname, file, basename_len, &st, tmp_sum);
423                 else
424                         file_checksum(thisname, st.st_size, tmp_sum);
425         }
426 @@ -1768,6 +1981,9 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
427  
428         closedir(d);
429  
430 +       if (checksum_files & CSF_UPDATE && am_sender && f >= 0)
431 +               reset_checksum_cache(1);
432 +
433         if (f >= 0 && recurse && !divert_dirs) {
434                 int i, end = flist->used - 1;
435                 /* send_if_directory() bumps flist->used, so use "end". */
436 @@ -2333,6 +2549,9 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
437                 }
438         } else
439                 flist_eof = 1;
440 +       
441 +       if (checksum_files & CSF_UPDATE && flist_eof)
442 +               reset_checksum_cache(0); /* writes any last updates */
443  
444         return flist;
445  }
446 diff --git a/generator.c b/generator.c
447 --- a/generator.c
448 +++ b/generator.c
449 @@ -114,6 +114,7 @@ static int lull_mod;
450  static int dir_tweaking;
451  static int need_retouch_dir_times;
452  static int need_retouch_dir_perms;
453 +static int started_whole_dir, upcoming_whole_dir;
454  static const char *solo_file = NULL;
455  
456  /* For calling delete_item() and delete_dir_contents(). */
457 @@ -705,7 +706,7 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st, int slot
458         if (always_checksum > 0 && S_ISREG(st->st_mode)) {
459                 char sum[MAX_DIGEST_LEN];
460                 if (checksum_files && slot >= 0)
461 -                       get_cached_checksum(slot, fn, file, st, sum);
462 +                       get_cached_checksum(slot, fn, file, -1, st, sum);
463                 else
464                         file_checksum(fn, st->st_size, sum);
465                 return memcmp(sum, F_SUM(file), checksum_len) == 0;
466 @@ -1328,7 +1329,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
467                                 fuzzy_dirlist = get_dirlist(fnamecmpbuf, -1, 1);
468                         }
469                         if (checksum_files) {
470 -                               reset_checksum_cache();
471 +                               reset_checksum_cache(started_whole_dir);
472 +                               started_whole_dir = upcoming_whole_dir;
473                         }
474                         need_new_dirscan = 0;
475                 }
476 @@ -1457,9 +1459,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
477                                 DEV_MINOR(devp) = minor(real_sx.st.st_dev);
478                         }
479                 }
480 -               else if (delete_during && f_out != -1 && !phase && dry_run < 2
481 -                   && (file->flags & FLAG_CONTENT_DIR))
482 -                       delete_in_dir(fname, file, &real_sx.st.st_dev);
483 +               else if (file->flags & FLAG_CONTENT_DIR && f_out != -1) {
484 +                       if (delete_during && !phase && dry_run < 2)
485 +                               delete_in_dir(fname, file, &real_sx.st.st_dev);
486 +                       upcoming_whole_dir = 1;
487 +               } else
488 +                       upcoming_whole_dir = 0;
489                 goto cleanup;
490         }
491  
492 @@ -1757,6 +1762,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
493                         handle_partial_dir(partialptr, PDIR_DELETE);
494                 }
495                 set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
496 +               if (checksum_files & CSF_UPDATE)
497 +                       set_cached_checksum(cur_flist, file);
498                 if (itemizing)
499                         itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL);
500  #ifdef SUPPORT_HARD_LINKS
501 @@ -2132,6 +2139,7 @@ void generate_files(int f_out, const char *local_name)
502                                         delete_in_dir(f_name(fp, fbuf), fp, &dirdev);
503                                 }
504                         }
505 +                       upcoming_whole_dir = fp->flags & FLAG_CONTENT_DIR ? 1 : 0;
506                 }
507                 for (i = cur_flist->low; i <= cur_flist->high; i++) {
508                         struct file_struct *file = cur_flist->sorted[i];
509 @@ -2212,6 +2220,9 @@ void generate_files(int f_out, const char *local_name)
510                         wait_for_receiver();
511         }
512  
513 +       if (checksum_files)
514 +               reset_checksum_cache(started_whole_dir);
515 +
516         do_progress = save_do_progress;
517         if (delete_during == 2)
518                 do_delayed_deletions(fbuf);
519 diff --git a/io.c b/io.c
520 --- a/io.c
521 +++ b/io.c
522 @@ -49,6 +49,7 @@ extern int read_batch;
523  extern int csum_length;
524  extern int protect_args;
525  extern int checksum_seed;
526 +extern int checksum_files;
527  extern int protocol_version;
528  extern int remove_source_files;
529  extern int preserve_hard_links;
530 @@ -200,6 +201,9 @@ static void got_flist_entry_status(enum festatus status, const char *buf)
531                                 flist_ndx_push(&hlink_list, ndx);
532                                 flist->in_progress++;
533                         }
534 +               } else if (checksum_files & CSF_UPDATE) {
535 +                       struct file_struct *file = flist->files[ndx - flist->ndx_start];
536 +                       set_cached_checksum(flist, file);
537                 }
538                 break;
539         case FES_REDO:
540 diff --git a/loadparm.c b/loadparm.c
541 --- a/loadparm.c
542 +++ b/loadparm.c
543 @@ -300,6 +300,10 @@ static struct enum_list enum_csum_modes[] = {
544         { CSF_IGNORE_FILES, "none" },
545         { CSF_LAX_MODE, "lax" },
546         { CSF_STRICT_MODE, "strict" },
547 +       { CSF_LAX_MODE|CSF_UPDATE, "+lax" },
548 +       { CSF_STRICT_MODE|CSF_UPDATE, "+strict" },
549 +       { CSF_LAX_MODE|CSF_UPDATE|CSF_AFFECT_DRYRUN, "++lax" },
550 +       { CSF_STRICT_MODE|CSF_UPDATE|CSF_AFFECT_DRYRUN, "++strict" },
551         { -1, NULL }
552  };
553  
554 diff --git a/options.c b/options.c
555 --- a/options.c
556 +++ b/options.c
557 @@ -1217,7 +1217,15 @@ int parse_arguments(int *argc_p, const char ***argv_p, int frommain)
558  
559                 case OPT_SUMFILES:
560                         arg = poptGetOptArg(pc);
561 -                       checksum_files = 0;
562 +                       if (*arg == '+') {
563 +                               arg++;
564 +                               checksum_files = CSF_UPDATE;
565 +                               if (*arg == '+') {
566 +                                       arg++;
567 +                                       checksum_files |= CSF_AFFECT_DRYRUN;
568 +                               }
569 +                       } else
570 +                               checksum_files = 0;
571                         if (strcmp(arg, "lax") == 0)
572                                 checksum_files |= CSF_LAX_MODE;
573                         else if (strcmp(arg, "strict") == 0)
574 diff --git a/receiver.c b/receiver.c
575 --- a/receiver.c
576 +++ b/receiver.c
577 @@ -47,6 +47,7 @@ extern int append_mode;
578  extern int sparse_files;
579  extern int keep_partial;
580  extern int checksum_seed;
581 +extern int checksum_files;
582  extern int inplace;
583  extern int delay_updates;
584  extern mode_t orig_umask;
585 @@ -339,7 +340,7 @@ static void handle_delayed_updates(char *local_name)
586                                         "rename failed for %s (from %s)",
587                                         full_fname(fname), partialptr);
588                         } else {
589 -                               if (remove_source_files
590 +                               if (remove_source_files || checksum_files & CSF_UPDATE
591                                  || (preserve_hard_links && F_IS_HLINKED(file)))
592                                         send_msg_int(MSG_SUCCESS, ndx);
593                                 handle_partial_dir(partialptr, PDIR_DELETE);
594 @@ -716,7 +717,7 @@ int recv_files(int f_in, char *local_name)
595  
596                 switch (recv_ok) {
597                 case 1:
598 -                       if (remove_source_files || inc_recurse
599 +                       if (remove_source_files || inc_recurse || checksum_files & CSF_UPDATE
600                          || (preserve_hard_links && F_IS_HLINKED(file)))
601                                 send_msg_int(MSG_SUCCESS, ndx);
602                         break;
603 diff --git a/rsync.h b/rsync.h
604 --- a/rsync.h
605 +++ b/rsync.h
606 @@ -864,6 +864,8 @@ typedef struct {
607  
608  #define CSF_ENABLE (1<<1)
609  #define CSF_LAX (1<<2)
610 +#define CSF_UPDATE (1<<3)
611 +#define CSF_AFFECT_DRYRUN (1<<4)
612  
613  #define CSF_IGNORE_FILES 0
614  #define CSF_LAX_MODE (CSF_ENABLE|CSF_LAX)
615 diff --git a/rsync.yo b/rsync.yo
616 --- a/rsync.yo
617 +++ b/rsync.yo
618 @@ -544,9 +544,13 @@ computed just as it would be if bf(--sumfiles) was not specified.
619  
620  The MODE value is either "lax", for relaxed checking (which compares size
621  and mtime), "strict" (which also compares ctime and inode), or "none" to
622 -ignore any .rsyncsums files ("none" is the default).  Rsync does not create
623 -or update these files, but there is a perl script in the support directory
624 -named "rsyncsums" that can be used for that.
625 +ignore any .rsyncsums files ("none" is the default).
626 +If you want rsync to create and/or update these files, specify a prefixed
627 +plus ("+lax" or "+strict").
628 +Adding a second prefixed '+' causes the checksum-file updates to happen
629 +even when the transfer is in bf(--dry-run) mode ("++lax" or "++strict").
630 +There is also a perl script in the support directory named "rsyncsums"
631 +that can be used to update the .rsyncsums files.
632  
633  This option has no effect unless bf(--checksum, -c) was also specified.  It
634  also only affects the current side of the transfer, so if you want the
635 diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
636 --- a/rsyncd.conf.yo
637 +++ b/rsyncd.conf.yo
638 @@ -284,13 +284,15 @@ The default is tt(/var/run/rsyncd.lock).
639  dit(bf(checksum files)) This option tells rsync to make use of any cached
640  checksum information it finds in per-directory .rsyncsums files when the
641  current transfer is using the bf(--checksum) option.  The value can be set
642 -to either "lax", "strict", or "none" -- see the client's bf(--sumfiles)
643 -option for what these choices do.
644 +to either "lax", "strict", "+lax", "+strict", "++lax", "++strict", or
645 +"none".  See the client's bf(--sumfiles) option for what these choices do.
646  
647  Note also that the client's command-line option, bf(--sumfiles), has no
648  effect on a daemon.  A daemon will only access checksum files if this
649 -config option tells it to.  See also the bf(exclude) directive for a way
650 -to hide the .rsyncsums files from the user.
651 +config option tells it to.  You can configure updating of the .rsyncsums
652 +files even if the module itself is configured to be read-only.  See also
653 +the bf(exclude) directive for a way to hide the .rsyncsums files from the
654 +user.
655  
656  dit(bf(read only)) The "read only" option determines whether clients
657  will be able to upload files or not. If "read only" is true then any