Delay the renaming of all the temp files until the end of the transfer.

--- orig/options.c	2005-01-17 23:11:45
+++ options.c	2005-01-17 23:15:13
@@ -97,6 +97,7 @@ int modify_window = 0;
 int blocking_io = -1;
 int checksum_seed = 0;
 int inplace = 0;
+int delay_renames = 0;
 long block_size = 0; /* "long" because popt can't set an int32. */
 
 
@@ -278,6 +279,7 @@ void usage(enum logcode F)
   rprintf(F,"     --max-size=SIZE         don't transfer any file larger than SIZE\n");
   rprintf(F,"     --partial               keep partially transferred files\n");
   rprintf(F,"     --partial-dir=DIR       put a partially transferred file into DIR\n");
+  rprintf(F,"     --delay-renames         renames transferred files into place at end\n");
   rprintf(F,"     --force                 force deletion of directories even if not empty\n");
   rprintf(F,"     --numeric-ids           don't map uid/gid values by user/group name\n");
   rprintf(F,"     --timeout=TIME          set I/O timeout in seconds\n");
@@ -390,6 +392,7 @@ static struct poptOption long_options[] 
   {"progress",         0,  POPT_ARG_NONE,   &do_progress, 0, 0, 0 },
   {"partial",          0,  POPT_ARG_NONE,   &keep_partial, 0, 0, 0 },
   {"partial-dir",      0,  POPT_ARG_STRING, &partial_dir, 0, 0, 0 },
+  {"delay-renames",    0,  POPT_ARG_NONE,   &delay_renames, 0, 0, 0 },
   {"ignore-errors",    0,  POPT_ARG_NONE,   &ignore_errors, 0, 0, 0 },
   {"blocking-io",      0,  POPT_ARG_VAL,    &blocking_io, 1, 0, 0 },
   {"no-blocking-io",   0,  POPT_ARG_VAL,    &blocking_io, 0, 0, 0 },
@@ -944,11 +947,15 @@ int parse_arguments(int *argc, const cha
 			bwlimit_writemax = 512;
 	}
 
+	if (delay_renames && !partial_dir)
+		partial_dir = ".~tmp~";
+
 	if (inplace) {
 #if HAVE_FTRUNCATE
-		if (partial_dir) {
+		if (partial_dir || delay_renames) {
 			snprintf(err_buf, sizeof err_buf,
-				 "--inplace cannot be used with --partial-dir\n");
+				 "--inplace cannot be used with --%s\n",
+				 delay_renames ? "delay-renames" : "partial-dir");
 			return 0;
 		}
 		keep_partial = 0;
@@ -1179,6 +1186,8 @@ void server_options(char **args,int *arg
 	if (partial_dir && am_sender) {
 		args[ac++] = "--partial-dir";
 		args[ac++] = partial_dir;
+		if (delay_renames)
+			args[ac++] = "--delay-renames";
 	} else if (keep_partial)
 		args[ac++] = "--partial";
 
--- orig/receiver.c	2005-01-17 23:11:45
+++ receiver.c	2005-01-10 10:16:54
@@ -53,6 +53,7 @@ extern int orig_umask;
 extern int keep_partial;
 extern int checksum_seed;
 extern int inplace;
+extern int delay_renames;
 
 extern struct exclude_list_struct server_exclude_list;
 
@@ -345,6 +346,7 @@ int recv_files(int f_in, struct file_lis
 	char fnametmp[MAXPATHLEN];
 	char *fnamecmp, *partialptr;
 	char fnamecmpbuf[MAXPATHLEN];
+	uchar *delayed_bits = NULL;
 	struct file_struct *file;
 	struct stats initial_stats;
 	int save_make_backups = make_backups;
@@ -358,6 +360,12 @@ int recv_files(int f_in, struct file_lis
 		flist->hlink_pool = NULL;
 	}
 
+	if (delay_renames) {
+		if (!(delayed_bits = new_array(char, (flist->count + 7) / 8)))
+			out_of_memory("recv_files");
+		memset(delayed_bits, 0, (flist->count + 7) / 8);
+	}
+
 	while (1) {
 		cleanup_disable();
 
@@ -572,7 +580,7 @@ int recv_files(int f_in, struct file_lis
 			exit_cleanup(RERR_FILEIO);
 		}
 
-		if (recv_ok || inplace) {
+		if ((recv_ok && !delay_renames) || inplace) {
 			finish_transfer(fname, fnametmp, file, recv_ok, 1);
 			if (partialptr != fname && fnamecmp == partialptr) {
 				do_unlink(partialptr);
@@ -582,6 +590,8 @@ int recv_files(int f_in, struct file_lis
 		    && handle_partial_dir(partialptr, PDIR_CREATE)) {
 			finish_transfer(partialptr, fnametmp, file, recv_ok,
 					!partial_dir);
+			if (delay_renames && recv_ok)
+				delayed_bits[i/8] |= 1 << (i % 8);
 		} else {
 			partialptr = NULL;
 			do_unlink(fnametmp);
@@ -621,6 +631,33 @@ int recv_files(int f_in, struct file_lis
 	}
 	make_backups = save_make_backups;
 
+	if (delay_renames) {
+		for (i = 0; i < flist->count; i++) {
+			struct file_struct *file = flist->files[i];
+			if (!file->basename
+			 || !(delayed_bits[i/8] & (1 << (i % 8))))
+				continue;
+			fname = local_name ? local_name : f_name(file);
+			partialptr = partial_dir_fname(fname);
+			if (partialptr) {
+				if (make_backups && !make_backup(fname))
+					continue;
+				if (verbose > 2) {
+					rprintf(FINFO, "renaming %s to %s\n",
+						partialptr, fname);
+				}
+				if (do_rename(partialptr, fname) < 0) {
+					rsyserr(FERROR, errno,
+						"rename failed for %s (from %s)",
+						fname, partialptr);
+				} else {
+					handle_partial_dir(partialptr,
+							   PDIR_DELETE);
+				}
+			}
+		}
+	}
+
 	if (delete_after && recurse && !local_name && flist->count > 0)
 		delete_files(flist);
 
--- orig/rsync.yo	2005-01-17 23:11:46
+++ rsync.yo	2005-01-17 23:13:23
@@ -348,6 +348,7 @@ verb(
      --max-size=SIZE         don't transfer any file larger than SIZE
      --partial               keep partially transferred files
      --partial-dir=DIR       put a partially transferred file into DIR
+     --delay-renames         renames transferred files into place at end
      --force                 force deletion of dirs even if not empty
      --numeric-ids           don't map uid/gid values by user/group name
      --timeout=TIME          set I/O timeout in seconds
@@ -546,9 +547,9 @@ or appended data, and also on systems th
 bound.
 
 The option implies --partial (since an interrupted transfer does not delete
-the file), but conflicts with --partial-dir.  Prior to rsync 2.6.4
---inplace was also incompatible with --compare-dest, --copy-dest, and
---link-dest.
+the file), but conflicts with --partial-dir and --delay-renames.
+Prior to rsync 2.6.4 --inplace was also incompatible with --compare-dest,
+--copy-dest, and --link-dest.
 
 WARNING: The file's data will be in an inconsistent state during the
 transfer (and possibly afterward if the transfer gets interrupted), so you
@@ -978,6 +979,17 @@ environment and then just use the -P opt
 does not look for this environment value is when --inplace was also
 specified (since --inplace conflicts with --partial-dir).
 
+dit(bf(--delay-renames)) This option puts the temporary file from each
+updated file into the file's partial-dir (see above) until the end of the
+transfer, at which time all the files are renamed into place in rapid
+succession.  This attempts to make the updating of the files a little more
+atomic.  If you don't specify the --partial-dir option, this option will
+cause it to default to ".~tmp~" (RSYNC_PARTIAL_DIR is not consulted for
+this value).  Conflicts with --inplace.
+
+See also the "atomic-rsync" perl script in the "support" subdir for an
+update algorithm that is even more atomic (it uses --link-dest).
+
 dit(bf(--progress)) This option tells rsync to print information
 showing the progress of the transfer. This gives a bored user
 something to watch.
