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