The patches for 3.0.1pre1.
[rsync/rsync-patches.git] / checksum-updating.diff
... / ...
CommitLineData
1This builds on the checksum-reading patch and adds the ability to
2create and/or update the .rsyncsums files using extended mode args to
3the --sumfiles=MODE option and the "checksum files = MODE" daemon
4parameter.
5
6CAUTION: This patch is only lightly tested. If you're interested
7in using it, please help out.
8
9To 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
16TODO:
17
18 - Fix the code that removes .rsyncsums files when a dir becomes empty.
19
20diff --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@@ -94,6 +95,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@@ -133,8 +137,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@@ -363,7 +372,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@@ -377,6 +458,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@@ -387,6 +471,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@@ -394,7 +482,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@@ -411,7 +499,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@@ -420,7 +508,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@@ -449,10 +544,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 - 1 - RSYNCSUMS_LEN))
203@@ -473,7 +569,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@@ -484,7 +580,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@@ -502,13 +605,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@@ -558,24 +662,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@@ -587,12 +779,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@@ -1378,6 +1589,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@@ -1424,13 +1637,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@@ -1512,7 +1725,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@@ -1804,6 +2017,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@@ -2389,6 +2605,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 }
446diff --git a/generator.c b/generator.c
447--- a/generator.c
448+++ b/generator.c
449@@ -114,6 +114,7 @@ static int dir_tweaking;
450 static int symlink_timeset_failed_flags;
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@@ -717,7 +718,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@@ -1350,7 +1351,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@@ -1488,9 +1490,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
481- && BITS_SETnUNSET(file->flags, FLAG_CONTENT_DIR, FLAG_MISSING_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 && !(file->flags & FLAG_MISSING_DIR))
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@@ -1783,6 +1788,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@@ -2195,6 +2202,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@@ -2275,6 +2283,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);
519diff --git a/io.c b/io.c
520--- a/io.c
521+++ b/io.c
522@@ -50,6 +50,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@@ -205,6 +206,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:
540diff --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
554diff --git a/options.c b/options.c
555--- a/options.c
556+++ b/options.c
557@@ -1233,7 +1233,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
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)
574diff --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;
603diff --git a/rsync.h b/rsync.h
604--- a/rsync.h
605+++ b/rsync.h
606@@ -866,6 +866,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)
615diff --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
635diff --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