++ if (checksum_files & CSF_UPDATE) {
++ if (basename_len < 0)
++ basename_len = strlen(file->basename) + 1;
++ csum_cache[slot].checksum_updates +=
++ add_checksum(flist, file->dirname, file->basename, basename_len,
++ stp->st_size, stp->st_mtime, (uint32)stp->st_ctime,
++ (uint32)stp->st_ino, sum_buf, NULL, FLAG_SUM_KEEP);
++ }
+ }
+
+ /* Call this with EITHER (1) "file, NULL, 0" to chdir() to the file's
+@@ -1364,6 +1575,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
+ if (excl_ret) {
+ if (ignore_perishable)
+ non_perishable_cnt++;
++ if (S_ISREG(st.st_mode))
++ REGULAR_SKIPPED(flist)++;
+ return NULL;
+ }
+
+@@ -1410,13 +1623,13 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
+ lastdir[len] = '\0';
+ lastdir_len = len;
+ if (checksum_files && am_sender && flist)
+- reset_checksum_cache();
++ reset_checksum_cache(0);
+ }
+ } else {
+ basename = thisname;
+ if (checksum_files && am_sender && flist && lastdir_len == -2) {
+ lastdir_len = -1;
+- reset_checksum_cache();
++ reset_checksum_cache(0);
+ }
+ }
+ basename_len = strlen(basename) + 1; /* count the '\0' */
+@@ -1498,7 +1711,7 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
+
+ if (always_checksum && am_sender && S_ISREG(st.st_mode)) {
+ if (flist && checksum_files)
+- get_cached_checksum(0, thisname, file, &st, tmp_sum);
++ get_cached_checksum(0, thisname, file, basename_len, &st, tmp_sum);
+ else
+ file_checksum(thisname, st.st_size, tmp_sum);
+ }
+@@ -1824,6 +2037,9 @@ static void send_directory(int f, struct file_list *flist, char *fbuf, int len,
+
+ closedir(d);
+
++ if (checksum_files & CSF_UPDATE && am_sender && f >= 0)
++ reset_checksum_cache(1);
++
+ if (f >= 0 && recurse && !divert_dirs) {
+ int i, end = flist->used - 1;
+ /* send_if_directory() bumps flist->used, so use "end". */
+@@ -2417,6 +2633,9 @@ struct file_list *send_file_list(int f, int argc, char *argv[])
+ }
+ } else
+ flist_eof = 1;
++
++ if (checksum_files & CSF_UPDATE && flist_eof)
++ reset_checksum_cache(0); /* writes any last updates */
+
+ return flist;
+ }
+diff --git a/generator.c b/generator.c
+--- a/generator.c
++++ b/generator.c
+@@ -114,6 +114,7 @@ static int dir_tweaking;
+ static int symlink_timeset_failed_flags;
+ static int need_retouch_dir_times;
+ static int need_retouch_dir_perms;
++static int started_whole_dir, upcoming_whole_dir;
+ static const char *solo_file = NULL;
+
+ /* For calling delete_item() and delete_dir_contents(). */
+@@ -720,7 +721,7 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st, int slot
+ if (always_checksum > 0 && S_ISREG(st->st_mode)) {
+ char sum[MAX_DIGEST_LEN];
+ if (checksum_files && slot >= 0)
+- get_cached_checksum(slot, fn, file, st, sum);
++ get_cached_checksum(slot, fn, file, -1, st, sum);
+ else
+ file_checksum(fn, st->st_size, sum);
+ return memcmp(sum, F_SUM(file), checksum_len) == 0;
+@@ -1353,7 +1354,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ fuzzy_dirlist = get_dirlist(fnamecmpbuf, -1, 1);
+ }
+ if (checksum_files) {
+- reset_checksum_cache();
++ reset_checksum_cache(started_whole_dir);
++ started_whole_dir = upcoming_whole_dir;
+ }
+ need_new_dirscan = 0;
+ }
+@@ -1498,6 +1500,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ else
+ change_local_filter_dir(fname, strlen(fname), F_DEPTH(file));
+ }
++ upcoming_whole_dir = file->flags & FLAG_CONTENT_DIR && f_out != -1 ? 1 : 0;
+ goto cleanup;
+ }
+
+@@ -1790,6 +1793,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ handle_partial_dir(partialptr, PDIR_DELETE);
+ }
+ set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT);
++ if (checksum_files & CSF_UPDATE)
++ set_cached_checksum(cur_flist, file);
+ if (itemizing)
+ itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL);
+ #ifdef SUPPORT_HARD_LINKS
+@@ -2217,6 +2222,7 @@ void generate_files(int f_out, const char *local_name)
+ } else
+ change_local_filter_dir(fbuf, strlen(fbuf), F_DEPTH(fp));
+ }
++ upcoming_whole_dir = fp->flags & FLAG_CONTENT_DIR ? 1 : 0;
+ }
+ for (i = cur_flist->low; i <= cur_flist->high; i++) {
+ struct file_struct *file = cur_flist->sorted[i];
+@@ -2297,6 +2303,9 @@ void generate_files(int f_out, const char *local_name)
+ wait_for_receiver();
+ }
+
++ if (checksum_files)
++ reset_checksum_cache(started_whole_dir);
++
+ do_progress = save_do_progress;
+ if (delete_during == 2)
+ do_delayed_deletions(fbuf);
+diff --git a/io.c b/io.c
+--- a/io.c
++++ b/io.c
+@@ -50,6 +50,7 @@ extern int read_batch;
+ extern int csum_length;
+ extern int protect_args;
+ extern int checksum_seed;
++extern int checksum_files;
+ extern int protocol_version;
+ extern int remove_source_files;
+ extern int preserve_hard_links;
+@@ -205,6 +206,9 @@ static void got_flist_entry_status(enum festatus status, const char *buf)
+ flist_ndx_push(&hlink_list, ndx);
+ flist->in_progress++;
+ }
++ } else if (checksum_files & CSF_UPDATE) {
++ struct file_struct *file = flist->files[ndx - flist->ndx_start];
++ set_cached_checksum(flist, file);
+ }
+ break;
+ case FES_REDO:
+diff --git a/loadparm.c b/loadparm.c
+--- a/loadparm.c
++++ b/loadparm.c
+@@ -300,6 +300,10 @@ static struct enum_list enum_csum_modes[] = {
+ { CSF_IGNORE_FILES, "none" },
+ { CSF_LAX_MODE, "lax" },
+ { CSF_STRICT_MODE, "strict" },
++ { CSF_LAX_MODE|CSF_UPDATE, "+lax" },
++ { CSF_STRICT_MODE|CSF_UPDATE, "+strict" },
++ { CSF_LAX_MODE|CSF_UPDATE|CSF_AFFECT_DRYRUN, "++lax" },
++ { CSF_STRICT_MODE|CSF_UPDATE|CSF_AFFECT_DRYRUN, "++strict" },
+ { -1, NULL }
+ };
+
+diff --git a/options.c b/options.c
+--- a/options.c
++++ b/options.c
+@@ -1233,7 +1233,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
+
+ case OPT_SUMFILES:
+ arg = poptGetOptArg(pc);
+- checksum_files = 0;
++ if (*arg == '+') {
++ arg++;
++ checksum_files = CSF_UPDATE;
++ if (*arg == '+') {
++ arg++;
++ checksum_files |= CSF_AFFECT_DRYRUN;
++ }
++ } else
++ checksum_files = 0;
+ if (strcmp(arg, "lax") == 0)
+ checksum_files |= CSF_LAX_MODE;
+ else if (strcmp(arg, "strict") == 0)
+diff --git a/receiver.c b/receiver.c
+--- a/receiver.c
++++ b/receiver.c
+@@ -47,6 +47,7 @@ extern int append_mode;
+ extern int sparse_files;
+ extern int keep_partial;
+ extern int checksum_seed;
++extern int checksum_files;
+ extern int inplace;
+ extern int delay_updates;
+ extern mode_t orig_umask;
+@@ -339,7 +340,7 @@ static void handle_delayed_updates(char *local_name)
+ "rename failed for %s (from %s)",
+ full_fname(fname), partialptr);
+ } else {
+- if (remove_source_files
++ if (remove_source_files || checksum_files & CSF_UPDATE
+ || (preserve_hard_links && F_IS_HLINKED(file)))
+ send_msg_int(MSG_SUCCESS, ndx);
+ handle_partial_dir(partialptr, PDIR_DELETE);
+@@ -716,7 +717,7 @@ int recv_files(int f_in, char *local_name)
+
+ switch (recv_ok) {
+ case 1:
+- if (remove_source_files || inc_recurse
++ if (remove_source_files || inc_recurse || checksum_files & CSF_UPDATE
+ || (preserve_hard_links && F_IS_HLINKED(file)))
+ send_msg_int(MSG_SUCCESS, ndx);
+ break;
+diff --git a/rsync.h b/rsync.h
+--- a/rsync.h
++++ b/rsync.h
+@@ -870,6 +870,8 @@ typedef struct {
+
+ #define CSF_ENABLE (1<<1)
+ #define CSF_LAX (1<<2)
++#define CSF_UPDATE (1<<3)
++#define CSF_AFFECT_DRYRUN (1<<4)
+
+ #define CSF_IGNORE_FILES 0
+ #define CSF_LAX_MODE (CSF_ENABLE|CSF_LAX)
+diff --git a/rsync.yo b/rsync.yo
+--- a/rsync.yo
++++ b/rsync.yo
+@@ -544,9 +544,13 @@ computed just as it would be if bf(--sumfiles) was not specified.
+
+ The MODE value is either "lax", for relaxed checking (which compares size
+ and mtime), "strict" (which also compares ctime and inode), or "none" to
+-ignore any .rsyncsums files ("none" is the default). Rsync does not create
+-or update these files, but there is a perl script in the support directory
+-named "rsyncsums" that can be used for that.
++ignore any .rsyncsums files ("none" is the default).
++If you want rsync to create and/or update these files, specify a prefixed
++plus ("+lax" or "+strict").
++Adding a second prefixed '+' causes the checksum-file updates to happen
++even when the transfer is in bf(--dry-run) mode ("++lax" or "++strict").
++There is also a perl script in the support directory named "rsyncsums"
++that can be used to update the .rsyncsums files.
+
+ This option has no effect unless bf(--checksum, -c) was also specified. It
+ also only affects the current side of the transfer, so if you want the
+diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
+--- a/rsyncd.conf.yo
++++ b/rsyncd.conf.yo
+@@ -284,13 +284,15 @@ The default is tt(/var/run/rsyncd.lock).
+ dit(bf(checksum files)) This parameter tells rsync to make use of any cached
+ checksum information it finds in per-directory .rsyncsums files when the
+ current transfer is using the bf(--checksum) option. The value can be set
+-to either "lax", "strict", or "none" -- see the client's bf(--sumfiles)
+-option for what these choices do.
++to either "lax", "strict", "+lax", "+strict", "++lax", "++strict", or
++"none". See the client's bf(--sumfiles) option for what these choices do.
+
+ Note also that the client's command-line option, bf(--sumfiles), has no
+ effect on a daemon. A daemon will only access checksum files if this
+-config option tells it to. See also the bf(exclude) directive for a way
+-to hide the .rsyncsums files from the user.
++config option tells it to. You can configure updating of the .rsyncsums
++files even if the module itself is configured to be read-only. See also
++the bf(exclude) directive for a way to hide the .rsyncsums files from the
++user.
+
+ dit(bf(read only)) This parameter determines whether clients
+ will be able to upload files or not. If "read only" is true then any