This patch allows multiple --compare-dest or --link-dest options to be
used, making the transfer of some files more optimal.  Note that the
algorithm does NOT search for the best match -- it stops at the first
match and uses that as the basis file for the transfer, so be sure to
order your arguments appropriately (the args are searched in the order
they are suppled).

Before compiling, be sure to run "make proto".

--- generator.c	29 Jun 2004 19:19:00 -0000	1.92
+++ generator.c	30 Jun 2004 06:50:26 -0000
@@ -42,7 +42,7 @@ extern int size_only;
 extern int io_timeout;
 extern int protocol_version;
 extern int always_checksum;
-extern char *compare_dest;
+extern char *compare_dest[];
 extern int link_dest;
 extern int whole_file;
 extern int local_server;
@@ -80,13 +80,12 @@ static int skip_file(char *fname, struct
 	if (always_checksum && S_ISREG(st->st_mode)) {
 		char sum[MD4_SUM_LENGTH];
 		char fnamecmpdest[MAXPATHLEN];
+		int i;
 
-		if (compare_dest != NULL) {
-			if (access(fname, 0) != 0) {
-				pathjoin(fnamecmpdest, sizeof fnamecmpdest,
-					 compare_dest, fname);
-				fname = fnamecmpdest;
-			}
+		for (i = 0; compare_dest[i] != NULL && access(fname, 0) < 0; i++) {
+			pathjoin(fnamecmpdest, sizeof fnamecmpdest,
+					compare_dest[i], fname);
+			fname = fnamecmpdest;
 		}
 		file_checksum(fname,sum,st->st_size);
 		return memcmp(sum, file->u.sum, protocol_version < 21 ? 2
@@ -267,7 +266,7 @@ static void generate_and_send_sums(struc
  * out.  It might be wrong.
  */
 static void recv_generator(char *fname, struct file_struct *file, int i,
-			   int f_out)
+			   int f_out, int f_nameout)
 {
 	int fd;
 	STRUCT_STAT st;
@@ -424,15 +423,22 @@ static void recv_generator(char *fname, 
 
 	fnamecmp = fname;
 
-	if (statret == -1 && compare_dest != NULL) {
+	if (statret == -1 && compare_dest[0] != NULL) {
 		/* try the file at compare_dest instead */
 		int saveerrno = errno;
-		pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, compare_dest, fname);
-		statret = link_stat(fnamecmpbuf, &st, 0);
-		if (!S_ISREG(st.st_mode))
-			statret = -1;
-		if (statret == -1)
+		int i;
+		for (i = 0; compare_dest[i] != NULL; i++) {
+			pathjoin(fnamecmpbuf, sizeof fnamecmpbuf, compare_dest[i], fname);
+			if ((statret = link_stat(fnamecmpbuf, &st, 0)) == 0) {
+				if (S_ISREG(st.st_mode))
+					break;
+				statret = -1;
+			}
+		}
+		if (statret < 0) {
 			errno = saveerrno;
+			*fnamecmpbuf = '\0';
+		}
 #if HAVE_LINK
 		else if (link_dest && !dry_run) {
 			if (do_link(fnamecmpbuf, fname) != 0) {
@@ -440,18 +446,22 @@ static void recv_generator(char *fname, 
 					rsyserr(FINFO, errno, "link %s => %s",
 						fnamecmpbuf, fname);
 				}
-			}
-			fnamecmp = fnamecmpbuf;
+				fnamecmp = fnamecmpbuf;
+			} else
+				*fnamecmpbuf = '\0';
 		}
 #endif
 		else
 			fnamecmp = fnamecmpbuf;
-	}
+	} else
+		*fnamecmpbuf = '\0';
 
 	if (statret == -1) {
 		if (preserve_hard_links && hard_link_check(file, HL_SKIP))
 			return;
 		if (errno == ENOENT) {
+			if (f_nameout >= 0)
+				write(f_nameout, "", 1);
 			write_int(f_out,i);
 			if (!dry_run)
 				write_sum_head(f_out, NULL);
@@ -471,19 +481,21 @@ static void recv_generator(char *fname, 
 		/* now pretend the file didn't exist */
 		if (preserve_hard_links && hard_link_check(file, HL_SKIP))
 			return;
+		if (f_nameout >= 0)
+			write(f_nameout, "", 1);
 		write_int(f_out,i);
 		if (!dry_run)
 			write_sum_head(f_out, NULL);
 		return;
 	}
 
-	if (opt_ignore_existing && fnamecmp == fname) {
+	if (opt_ignore_existing && !*fnamecmpbuf) {
 		if (verbose > 1)
 			rprintf(FINFO,"%s exists\n",fname);
 		return;
 	}
 
-	if (update_only && fnamecmp == fname
+	if (update_only && !*fnamecmpbuf
 	    && cmp_modtime(st.st_mtime, file->modtime) > 0) {
 		if (verbose > 1)
 			rprintf(FINFO,"%s is newer\n",fname);
@@ -491,17 +503,21 @@ static void recv_generator(char *fname, 
 	}
 
 	if (skip_file(fname, file, &st)) {
-		if (fnamecmp == fname)
+		if (!*fnamecmpbuf)
 			set_perms(fname, file, &st, PERMS_REPORT);
 		return;
 	}
 
 	if (dry_run) {
+		if (f_nameout >= 0)
+			write(f_nameout, "", 1);
 		write_int(f_out,i);
 		return;
 	}
 
 	if (disable_deltas_p()) {
+		if (f_nameout >= 0)
+			write(f_nameout, "", 1);
 		write_int(f_out,i);
 		write_sum_head(f_out, NULL);
 		return;
@@ -516,6 +532,8 @@ static void recv_generator(char *fname, 
 		/* pretend the file didn't exist */
 		if (preserve_hard_links && hard_link_check(file, HL_SKIP))
 			return;
+		if (f_nameout >= 0)
+			write(f_nameout, "", 1);
 		write_int(f_out,i);
 		write_sum_head(f_out, NULL);
 		return;
@@ -534,6 +552,8 @@ static void recv_generator(char *fname, 
 	if (verbose > 2)
 		rprintf(FINFO, "generating and sending sums for %d\n", i);
 
+	if (f_nameout >= 0)
+		write(f_nameout, fnamecmpbuf, strlen(fnamecmpbuf) + 1);
 	write_int(f_out,i);
 	generate_and_send_sums(mapbuf, st.st_size, f_out);
 
@@ -543,10 +563,11 @@ static void recv_generator(char *fname, 
 }
 
 
-void generate_files(int f, struct file_list *flist, char *local_name)
+void generate_files(int f, struct file_list *flist, char *local_name,
+		    int f_nameout)
 {
 	int i;
-	int phase=0;
+	int phase = 0;
 	char fbuf[MAXPATHLEN];
 
 	if (verbose > 2) {
@@ -584,7 +605,7 @@ void generate_files(int f, struct file_l
 		}
 
 		recv_generator(local_name ? local_name : f_name_to(file, fbuf),
-			       file, i, f);
+			       file, i, f, f_nameout);
 	}
 
 	phase++;
@@ -601,7 +622,7 @@ void generate_files(int f, struct file_l
 	while ((i = get_redo_num()) != -1) {
 		struct file_struct *file = flist->files[i];
 		recv_generator(local_name ? local_name : f_name_to(file, fbuf),
-			       file, i, f);
+			       file, i, f, f_nameout);
 	}
 
 	phase++;
@@ -620,7 +641,7 @@ void generate_files(int f, struct file_l
 		if (!file->basename || !S_ISDIR(file->mode))
 			continue;
 		recv_generator(local_name ? local_name : f_name(file),
-			       file, i, -1);
+			       file, i, -1, -1);
 	}
 
 	if (verbose > 2)
--- main.c	28 Jun 2004 17:45:40 -0000	1.201
+++ main.c	30 Jun 2004 06:50:27 -0000
@@ -428,8 +428,8 @@ static void do_server_sender(int f_in, i
 static int do_recv(int f_in,int f_out,struct file_list *flist,char *local_name)
 {
 	int pid;
-	int status=0;
-	int error_pipe[2];
+	int status = 0;
+	int error_pipe[2], name_pipe[2];
 
 	if (preserve_hard_links)
 		init_hard_links(flist);
@@ -441,17 +441,19 @@ static int do_recv(int f_in,int f_out,st
 		}
 	}
 
-	if (fd_pair(error_pipe) < 0) {
-		rprintf(FERROR,"error pipe failed in do_recv\n");
+	if (fd_pair(error_pipe) < 0 || fd_pair(name_pipe) < 0) {
+		rprintf(FERROR, "fd_pair() failed in do_recv\n");
 		exit_cleanup(RERR_SOCKETIO);
 	}
 
 	io_flush(NORMAL_FLUSH);
 
-	if ((pid=do_fork()) == 0) {
+	if ((pid = do_fork()) == 0) {
 		close(error_pipe[0]);
+		close(name_pipe[1]);
 		if (f_in != f_out)
 			close(f_out);
+		set_blocking(name_pipe[0]);
 
 		/* we can't let two processes write to the socket at one time */
 		io_multiplexing_close();
@@ -459,7 +461,7 @@ static int do_recv(int f_in,int f_out,st
 		/* set place to send errors */
 		set_msg_fd_out(error_pipe[1]);
 
-		recv_files(f_in,flist,local_name);
+		recv_files(f_in, flist, local_name, name_pipe[0]);
 		io_flush(FULL_FLUSH);
 		report(f_in);
 
@@ -475,14 +477,16 @@ static int do_recv(int f_in,int f_out,st
 	am_generator = 1;
 
 	close(error_pipe[1]);
+	close(name_pipe[0]);
 	if (f_in != f_out)
 		close(f_in);
+	set_blocking(name_pipe[1]);
 
 	io_start_buffering_out(f_out);
 
 	set_msg_fd_in(error_pipe[0]);
 
-	generate_files(f_out, flist, local_name);
+	generate_files(f_out, flist, local_name, name_pipe[1]);
 
 	get_redo_num(); /* Read final MSG_DONE and any prior messages. */
 	report(-1);
--- options.c	20 Jun 2004 19:47:05 -0000	1.157
+++ options.c	30 Jun 2004 06:50:27 -0000
@@ -117,7 +117,8 @@ unsigned int backup_dir_remainder;
 
 char *backup_suffix = NULL;
 char *tmpdir = NULL;
-char *compare_dest = NULL;
+char *compare_dest[MAX_COMP_DEST+1];
+int num_comp_dest = 0;
 char *config_file = NULL;
 char *shell_cmd = NULL;
 char *log_format = NULL;
@@ -139,6 +140,7 @@ char *batch_prefix = NULL;
 
 static int daemon_opt;   /* sets am_daemon after option error-reporting */
 static int modify_window_set;
+static int saw_compare_dest = 0;
 
 /** Local address to bind.  As a character string because it's
  * interpreted by the IPv6 layer: should be a numeric IP4 or IP6
@@ -308,7 +310,7 @@ void usage(enum logcode F)
 }
 
 enum {OPT_VERSION = 1000, OPT_SENDER, OPT_EXCLUDE, OPT_EXCLUDE_FROM,
-      OPT_DELETE_AFTER, OPT_DELETE_EXCLUDED, OPT_LINK_DEST,
+      OPT_DELETE_AFTER, OPT_DELETE_EXCLUDED, OPT_COMPARE_DEST, OPT_LINK_DEST,
       OPT_INCLUDE, OPT_INCLUDE_FROM, OPT_MODIFY_WINDOW,
       OPT_READ_BATCH, OPT_WRITE_BATCH, OPT_TIMEOUT,
       OPT_REFUSED_BASE = 9000};
@@ -366,8 +368,8 @@ static struct poptOption long_options[] 
   {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
   {"timeout",          0,  POPT_ARG_INT,    &io_timeout, OPT_TIMEOUT, 0, 0 },
   {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
-  {"compare-dest",     0,  POPT_ARG_STRING, &compare_dest, 0, 0, 0 },
-  {"link-dest",        0,  POPT_ARG_STRING, &compare_dest,  OPT_LINK_DEST, 0, 0 },
+  {"compare-dest",     0,  POPT_ARG_STRING, 0,              OPT_COMPARE_DEST, 0, 0 },
+  {"link-dest",        0,  POPT_ARG_STRING, 0,              OPT_LINK_DEST, 0, 0 },
   /* TODO: Should this take an optional int giving the compression level? */
   {"compress",        'z', POPT_ARG_NONE,   &do_compression, 0, 0, 0 },
   {"daemon",           0,  POPT_ARG_NONE,   &daemon_opt, 0, 0, 0 },
@@ -585,8 +587,36 @@ int parse_arguments(int *argc, const cha
 				select_timeout = io_timeout;
 			break;
 
+		case OPT_COMPARE_DEST:
+#if HAVE_LINK
+			if (num_comp_dest >= MAX_COMP_DEST-1) {
+				rprintf(FERROR, "ERROR: %s\n", "too many --compare-dest args given");
+				return 0;
+			}
+			arg = poptGetOptArg(pc);
+			if (sanitize_paths)
+				arg = alloc_sanitize_path(arg, curr_dir);
+			compare_dest[num_comp_dest++] = (char *)arg;
+			saw_compare_dest = 1;
+			break;
+#else
+			snprintf(err_buf, sizeof err_buf,
+				 "hard links are not supported on this %s\n",
+				 am_server ? "server" : "client");
+			rprintf(FERROR, "ERROR: %s", err_buf);
+			return 0;
+#endif
+
 		case OPT_LINK_DEST:
 #if HAVE_LINK
+			if (num_comp_dest >= MAX_COMP_DEST-1) {
+				rprintf(FERROR, "ERROR: %s\n", "too many --link-dest args given");
+				return 0;
+			}
+			arg = poptGetOptArg(pc);
+			if (sanitize_paths)
+				arg = alloc_sanitize_path(arg, curr_dir);
+			compare_dest[num_comp_dest++] = (char *)arg;
 			link_dest = 1;
 			break;
 #else
@@ -661,6 +691,11 @@ int parse_arguments(int *argc, const cha
 		exit_cleanup(RERR_SYNTAX);
 	}
 
+	if (saw_compare_dest && link_dest) {
+		rprintf(FINFO,
+			"WARNING: promoting --compare-dest options to --link-dest.\n");
+	}
+
 	if (archive_mode) {
 		if (!files_from)
 			recurse = 1;
@@ -689,8 +724,6 @@ int parse_arguments(int *argc, const cha
 			(*argv)[i] = alloc_sanitize_path((*argv)[i], NULL);
 		if (tmpdir)
 			tmpdir = alloc_sanitize_path(tmpdir, curr_dir);
-		if (compare_dest)
-			compare_dest = alloc_sanitize_path(compare_dest, curr_dir);
 		if (backup_dir)
 			backup_dir = alloc_sanitize_path(backup_dir, curr_dir);
 		if (files_from)
@@ -785,8 +818,8 @@ int parse_arguments(int *argc, const cha
  **/
 void server_options(char **args,int *argc)
 {
+	static char argstr[50+MAX_COMP_DEST*2];
 	int ac = *argc;
-	static char argstr[50];
 	char *arg;
 
 	int i, x;
@@ -968,13 +1001,16 @@ void server_options(char **args,int *arg
 		args[ac++] = tmpdir;
 	}
 
-	if (compare_dest && am_sender) {
+	if (compare_dest[0] && am_sender) {
 		/* the server only needs this option if it is not the sender,
 		 *   and it may be an older version that doesn't know this
 		 *   option, so don't send it if client is the sender.
 		 */
-		args[ac++] = link_dest ? "--link-dest" : "--compare-dest";
-		args[ac++] = compare_dest;
+		int i;
+		for (i = 0; i < num_comp_dest; i++) {
+			args[ac++] = link_dest ? "--link-dest" : "--compare-dest";
+			args[ac++] = compare_dest[i];
+		}
 	}
 
 	if (files_from && (!am_sender || remote_filesfrom_file)) {
--- receiver.c	29 Jun 2004 15:12:01 -0000	1.83
+++ receiver.c	30 Jun 2004 06:50:27 -0000
@@ -36,7 +36,6 @@ extern int preserve_perms;
 extern int cvs_exclude;
 extern int io_error;
 extern char *tmpdir;
-extern char *compare_dest;
 extern int make_backups;
 extern int do_progress;
 extern char *backup_dir;
@@ -293,14 +292,15 @@ static int receive_data(int f_in,struct 
  * main routine for receiver process.
  *
  * Receiver process runs on the same host as the generator process. */
-int recv_files(int f_in,struct file_list *flist,char *local_name)
+int recv_files(int f_in, struct file_list *flist, char *local_name,
+	       int f_name)
 {
 	int fd1,fd2;
 	STRUCT_STAT st;
 	char *fname, fbuf[MAXPATHLEN];
 	char template[MAXPATHLEN];
 	char fnametmp[MAXPATHLEN];
-	char *fnamecmp;
+	char *fnamecmp, *cp;
 	char fnamecmpbuf[MAXPATHLEN];
 	struct map_struct *mapbuf;
 	struct file_struct *file;
@@ -364,19 +364,19 @@ int recv_files(int f_in,struct file_list
 		if (verbose > 2)
 			rprintf(FINFO,"recv_files(%s)\n",fname);
 
-		fnamecmp = fname;
+		for (cp = fnamecmpbuf; ; cp++) {
+			if (read(f_name, cp, 1) <= 0) {
+				rsyserr(FERROR, errno, "fname-pipe read failed");
+				exit_cleanup(RERR_PROTOCOL);
+			}
+			if (!*cp)
+				break;
+		}
+		fnamecmp = *fnamecmpbuf ? fnamecmpbuf : fname;
 
 		/* open the file */
 		fd1 = do_open(fnamecmp, O_RDONLY, 0);
 
-		if (fd1 == -1 && compare_dest != NULL) {
-			/* try the file at compare_dest instead */
-			pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
-				 compare_dest, fname);
-			fnamecmp = fnamecmpbuf;
-			fd1 = do_open(fnamecmp, O_RDONLY, 0);
-		}
-
 		if (fd1 != -1 && do_fstat(fd1,&st) != 0) {
 			rsyserr(FERROR, errno, "fstat %s failed",
 				full_fname(fnamecmp));
@@ -385,7 +385,7 @@ int recv_files(int f_in,struct file_list
 			continue;
 		}
 
-		if (fd1 != -1 && S_ISDIR(st.st_mode) && fnamecmp == fname) {
+		if (fd1 != -1 && S_ISDIR(st.st_mode) && !*fnamecmpbuf) {
 			/* this special handling for directories
 			 * wouldn't be necessary if robust_rename()
 			 * and the underlying robust_unlink could cope
--- rsync.h	16 May 2004 07:28:24 -0000	1.204
+++ rsync.h	30 Jun 2004 06:50:27 -0000
@@ -98,6 +98,8 @@
 
 #define MAX_ARGS 1000
 
+#define MAX_COMP_DEST 20
+
 #define MPLEX_BASE 7
 
 #define NO_EXCLUDES	0
