This patch to rsync adds a %C log escape that expands to the sender's
post-transfer checksum of a file for protocol 30 or above.  This way, if
you need the MD5 checksums of transferred files, you can have rsync log
them instead of spending extra processor time on a separate command to
compute them.

-- Matt McCutchen <hashproduct@gmail.com>

diff --git a/log.c b/log.c
--- a/log.c
+++ b/log.c
@@ -57,6 +57,9 @@ extern char curr_dir[];
 extern char *module_dir;
 extern unsigned int module_dirlen;
 
+extern char sender_file_sum[MAX_DIGEST_LEN];
+extern int file_sum_len;
+
 static int log_initialised;
 static int logfile_was_closed;
 static FILE *logfile_fp;
@@ -622,6 +625,19 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
 			snprintf(buf2, sizeof buf2, fmt, (double)b);
 			n = buf2;
 			break;
+		case 'C':
+			if (iflags & ITEM_TRANSFER && protocol_version >= 30) {
+				int i;
+				for (i = 0; i < file_sum_len; i++)
+					snprintf(buf2 + i*2, 3, "%02x", (int)CVAL(sender_file_sum,i));
+			} else {
+				int i;
+				for (i = 0; i < file_sum_len*2; i++)
+					buf2[i] = '?';
+				buf2[i] = '\0';
+			}
+			n = buf2;
+			break;
 		case 'i':
 			if (iflags & ITEM_DELETED) {
 				n = "*deleting  ";
diff --git a/match.c b/match.c
--- a/match.c
+++ b/match.c
@@ -312,6 +312,10 @@ static void hash_search(int f,struct sum_struct *s,
 	map_ptr(buf, len-1, 1);
 }
 
+/* Global variables to make the sender's checksum of a transferred file
+ * available to the code for log escape %C. */
+char sender_file_sum[MAX_DIGEST_LEN];
+int file_sum_len = MD5_DIGEST_LEN;
 
 /**
  * Scan through a origin file, looking for sections that match
@@ -329,9 +333,6 @@ static void hash_search(int f,struct sum_struct *s,
  **/
 void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
 {
-	char file_sum[MAX_DIGEST_LEN];
-	int sum_len;
-
 	last_match = 0;
 	false_alarms = 0;
 	hash_hits = 0;
@@ -379,14 +380,26 @@ void match_sums(int f, struct sum_struct *s, struct map_struct *buf, OFF_T len)
 		matched(f, s, buf, len, -1);
 	}
 
-	sum_len = sum_end(file_sum);
-	/* If we had a read error, send a bad checksum. */
-	if (buf && buf->status != 0)
-		file_sum[0]++;
+	file_sum_len = sum_end(sender_file_sum);
+
+	/* If we had a read error, send a bad checksum.  We use all bits
+	 * off or all bits on so that a user logging checksums with %C
+	 * can recognize a bad checksum. */
+	if (buf && buf->status != 0) {
+		int i;
+		for (i = 0; i < file_sum_len; i++) {
+			if (sender_file_sum[i])
+				break;
+		}
+		if (i < file_sum_len)
+			memset(sender_file_sum, 0, file_sum_len);
+		else
+			memset(sender_file_sum, 0xFF, file_sum_len);
+	}
 
 	if (verbose > 2)
 		rprintf(FINFO,"sending file_sum\n");
-	write_buf(f, file_sum, sum_len);
+	write_buf(f, sender_file_sum, file_sum_len);
 
 	if (verbose > 2)
 		rprintf(FINFO, "false_alarms=%d hash_hits=%d matches=%d\n",
diff --git a/options.c b/options.c
--- a/options.c
+++ b/options.c
@@ -1498,7 +1498,8 @@ int parse_arguments(int *argc_p, const char ***argv_p, int frommain)
 		else if (log_format_has(stdout_format, 'i'))
 			stdout_format_has_i = itemize_changes | 1;
 		if (!log_format_has(stdout_format, 'b')
-		 && !log_format_has(stdout_format, 'c'))
+		 && !log_format_has(stdout_format, 'c')
+		 && !log_format_has(stdout_format, 'C'))
 			log_before_transfer = !am_server;
 	} else if (itemize_changes) {
 		stdout_format = "%i %n%L";
diff --git a/receiver.c b/receiver.c
--- a/receiver.c
+++ b/receiver.c
@@ -62,6 +62,9 @@ static int phase = 0, redoing = 0;
 /* We're either updating the basis file or an identical copy: */
 static int updating_basis_or_equiv;
 
+extern char sender_file_sum[MAX_DIGEST_LEN];
+extern int file_sum_len;
+
 /*
  * get_tmpname() - create a tmp filename for a given filename
  *
@@ -165,10 +168,9 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 			const char *fname, int fd, OFF_T total_size)
 {
 	static char file_sum1[MAX_DIGEST_LEN];
-	static char file_sum2[MAX_DIGEST_LEN];
 	struct map_struct *mapbuf;
 	struct sum_struct sum;
-	int32 len, sum_len;
+	int32 len;
 	OFF_T offset = 0;
 	OFF_T offset2;
 	char *data;
@@ -298,15 +300,15 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 		exit_cleanup(RERR_FILEIO);
 	}
 
-	sum_len = sum_end(file_sum1);
+	file_sum_len = sum_end(file_sum1);
 
 	if (mapbuf)
 		unmap_file(mapbuf);
 
-	read_buf(f_in, file_sum2, sum_len);
+	read_buf(f_in, sender_file_sum, file_sum_len);
 	if (verbose > 2)
 		rprintf(FINFO,"got file_sum\n");
-	if (fd != -1 && memcmp(file_sum1, file_sum2, sum_len) != 0)
+	if (fd != -1 && memcmp(file_sum1, sender_file_sum, file_sum_len) != 0)
 		return 0;
 	return 1;
 }
diff --git a/rsync.yo b/rsync.yo
--- a/rsync.yo
+++ b/rsync.yo
@@ -2077,7 +2077,7 @@ by the server and defaults to the current code(time()).  This option
 is used to set a specific checksum seed, which is useful for
 applications that want repeatable block and file checksums, or
 in the case where the user wants a more random checksum seed.
-Note that setting NUM to 0 causes rsync to use the default of code(time())
+Setting NUM to 0 causes rsync to use the default of code(time())
 for checksum seed.
 enddit()
 
diff --git a/rsyncd.conf.yo b/rsyncd.conf.yo
--- a/rsyncd.conf.yo
+++ b/rsyncd.conf.yo
@@ -511,7 +511,8 @@ quote(itemization(
   it() %a the remote IP address
   it() %b the number of bytes actually transferred
   it() %B the permission bits of the file (e.g. rwxrwxrwt)
-  it() %c the checksum bytes received for this file (only when sending)
+  it() %c the total size of the block checksums received for the basis file (only when sending)
+  it() %C the full-file MD5 checksum of a transferred file (only for protocol 30 or above).
   it() %f the filename (long form on sender; no trailing "/")
   it() %G the gid of the file (decimal) or "DEFAULT"
   it() %h the remote host name
