This patch adds the --preallocate option that asks rsync to preallocate the
copied files.  This slows down the copy, but should reduce fragmentation on
systems that need that.

To use this patch, run these commands for a successful build:

    patch -p1 <patches/preallocate.diff
    ./prepare-source
    ./configure
    make

diff --git a/configure.in b/configure.in
--- a/configure.in
+++ b/configure.in
@@ -552,7 +552,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
     strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
     setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
     strerror putenv iconv_open locale_charset nl_langinfo getxattr \
-    extattr_get_link sigaction sigprocmask setattrlist)
+    extattr_get_link sigaction sigprocmask setattrlist posix_fallocate)
 
 AC_CHECK_FUNCS(getpgrp tcgetpgrp)
 if test $ac_cv_func_getpgrp = yes; then
diff --git a/options.c b/options.c
--- a/options.c
+++ b/options.c
@@ -72,6 +72,7 @@ int remove_source_files = 0;
 int one_file_system = 0;
 int protocol_version = PROTOCOL_VERSION;
 int sparse_files = 0;
+int preallocate_files = 0;
 int do_compression = 0;
 int def_compress_level = Z_DEFAULT_COMPRESSION;
 int am_root = 0; /* 0 = normal, 1 = root, 2 = --super, -1 = --fake-super */
@@ -223,6 +224,7 @@ static void print_rsync_version(enum logcode f)
 	char const *links = "no ";
 	char const *iconv = "no ";
 	char const *ipv6 = "no ";
+	char const *preallocation = "no ";
 	STRUCT_STAT *dumstat;
 
 #if SUBPROTOCOL_VERSION != 0
@@ -252,6 +254,9 @@ static void print_rsync_version(enum logcode f)
 #ifdef ICONV_OPTION
 	iconv = "";
 #endif
+#ifdef SUPPORT_PREALLOCATION
+	preallocation = "";
+#endif
 
 	rprintf(f, "%s  version %s  protocol version %d%s\n",
 		RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
@@ -265,8 +270,8 @@ static void print_rsync_version(enum logcode f)
 		(int)(sizeof (int64) * 8));
 	rprintf(f, "    %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
 		got_socketpair, hardlinks, links, ipv6, have_inplace);
-	rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv\n",
-		have_inplace, acls, xattrs, iconv);
+	rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %spreallocation\n",
+		have_inplace, acls, xattrs, iconv, preallocation);
 
 #ifdef MAINTAINER_MODE
 	rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
@@ -353,6 +358,9 @@ void usage(enum logcode F)
   rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
 #endif
   rprintf(F," -S, --sparse                handle sparse files efficiently\n");
+#ifdef SUPPORT_PREALLOCATION
+  rprintf(F,"     --preallocate           posix_fallocate dest files before writing them\n");
+#endif
   rprintf(F," -n, --dry-run               perform a trial run with no changes made\n");
   rprintf(F," -W, --whole-file            copy files whole (without delta-xfer algorithm)\n");
   rprintf(F," -x, --one-file-system       don't cross filesystem boundaries\n");
@@ -531,6 +539,7 @@ static struct poptOption long_options[] = {
   {"max-size",         0,  POPT_ARG_STRING, &max_size_arg, OPT_MAX_SIZE, 0, 0 },
   {"min-size",         0,  POPT_ARG_STRING, &min_size_arg, OPT_MIN_SIZE, 0, 0 },
   {"sparse",          'S', POPT_ARG_NONE,   &sparse_files, 0, 0, 0 },
+  {"preallocate",      0,  POPT_ARG_NONE,   &preallocate_files, 0, 0, 0},
   {"inplace",          0,  POPT_ARG_NONE,   &inplace, 0, 0, 0 },
   {"append",           0,  POPT_ARG_NONE,   0, OPT_APPEND, 0, 0 },
   {"append-verify",    0,  POPT_ARG_VAL,    &append_mode, 2, 0, 0 },
@@ -1289,6 +1298,15 @@ int parse_arguments(int *argc_p, const char ***argv_p, int frommain)
 	}
 #endif
 
+#ifndef SUPPORT_PREALLOCATION
+	if (preallocate_files && !am_sender) {
+		snprintf(err_buf, sizeof err_buf,
+			 "preallocation is not supported on this %s\n",
+			 am_server ? "server" : "client");
+		return 0;
+	}
+#endif
+
 	if (write_batch && read_batch) {
 		snprintf(err_buf, sizeof err_buf,
 			"--write-batch and --read-batch can not be used together\n");
@@ -1999,6 +2017,9 @@ void server_options(char **args, int *argc_p)
 	else if (remove_source_files)
 		args[ac++] = "--remove-sent-files";
 
+	if (preallocate_files && am_sender)
+		args[ac++] = "--preallocate";
+
 	*argc_p = ac;
 	return;
 
diff --git a/receiver.c b/receiver.c
--- a/receiver.c
+++ b/receiver.c
@@ -45,6 +45,7 @@ extern int cleanup_got_literal;
 extern int remove_source_files;
 extern int append_mode;
 extern int sparse_files;
+extern int preallocate_files;
 extern int keep_partial;
 extern int checksum_seed;
 extern int inplace;
@@ -175,6 +176,19 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 	int32 i;
 	char *map = NULL;
 
+#ifdef SUPPORT_PREALLOCATION
+	OFF_T preallocated_len = 0;
+
+	if (preallocate_files && fd != -1 && total_size > 0) {
+		/* Preallocate enough space for file's eventual length if
+		 * possible; seems to reduce fragmentation on Windows. */
+		if (posix_fallocate(fd, 0, total_size) == 0)
+			preallocated_len = total_size;
+		else
+			rsyserr(FINFO, errno, "preallocate %s", full_fname(fname));
+	}
+#endif
+
 	read_sum_head(f_in, &sum);
 
 	if (fd_r >= 0 && size_r > 0) {
@@ -284,8 +298,18 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r,
 		goto report_write_error;
 
 #ifdef HAVE_FTRUNCATE
-	if (inplace && fd != -1)
-		ftruncate(fd, offset);
+	/* inplace: New data could be shorter than old data.
+	 * preallocate_files: total_size could have been an overestimate.
+	 *     Cut off any extra preallocated zeros from dest file. */
+	if ((inplace
+#ifdef SUPPORT_PREALLOCATION
+			|| preallocated_len > offset
+#endif
+		) && fd != -1)
+		if (ftruncate(fd, offset) < 0)
+			/* If we fail to truncate, the dest file may be wrong, so we
+			 * must trigger the "partial transfer" error. */
+			rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(fname));
 #endif
 
 	if (do_progress)
diff --git a/rsync.h b/rsync.h
--- a/rsync.h
+++ b/rsync.h
@@ -600,6 +600,10 @@ struct ht_int64_node {
 #define ACLS_NEED_MASK 1
 #endif
 
+#if defined HAVE_FTRUNCATE && defined HAVE_POSIX_FALLOCATE
+#define SUPPORT_PREALLOCATION 1
+#endif
+
 union file_extras {
 	int32 num;
 	uint32 unum;
diff --git a/rsync.yo b/rsync.yo
--- a/rsync.yo
+++ b/rsync.yo
@@ -352,6 +352,7 @@ to the detailed description below for a complete description.  verb(
      --super                 receiver attempts super-user activities
      --fake-super            store/recover privileged attrs using xattrs
  -S, --sparse                handle sparse files efficiently
+     --preallocate           posix_fallocate dest files before writing
  -n, --dry-run               perform a trial run with no changes made
  -W, --whole-file            copy files whole (w/o delta-xfer algorithm)
  -x, --one-file-system       don't cross filesystem boundaries
@@ -1023,6 +1024,19 @@ NOTE: Don't use this option when the destination is a Solaris "tmpfs"
 filesystem. It doesn't seem to handle seeks over null regions
 correctly and ends up corrupting the files.
 
+dit(bf(--preallocate)) This tells the receiver to allocate each destination
+file to its eventual size using bf(posix_fallocate)(3) before writing data
+to the file.  If the receiver is remote, this nonstandard option only works
+if the receiver also has the preallocation patch.  Furthermore, this option
+only works if the receiver found the bf(posix_fallocate)(3) call at
+configure time.
+
+Without this option on MS Windows, very large destination files tend to be
+broken into thousands of fragments; advising Windows ahead of time of the
+eventual file size using this option usually reduces the number of
+fragments to one.  However, on Linux, this option appears to just waste
+disk I/O.
+
 dit(bf(-n, --dry-run)) This makes rsync perform a trial run that doesn't
 make any changes (and produces mostly the same output as a real run).  It
 is most commonly used in combination with the bf(-v, --verbose) and/or
diff --git a/t_stub.c b/t_stub.c
--- a/t_stub.c
+++ b/t_stub.c
@@ -22,6 +22,7 @@
 #include "rsync.h"
 
 int modify_window = 0;
+int preallocate_files = 0;
 int module_id = -1;
 int relative_paths = 0;
 int human_readable = 0;
diff --git a/util.c b/util.c
--- a/util.c
+++ b/util.c
@@ -25,6 +25,7 @@
 
 extern int verbose;
 extern int dry_run;
+extern int preallocate_files;
 extern int module_id;
 extern int modify_window;
 extern int relative_paths;
@@ -272,6 +273,10 @@ int copy_file(const char *source, const char *dest, int ofd,
 	int ifd;
 	char buf[1024 * 8];
 	int len;   /* Number of bytes read into `buf'. */
+#ifdef SUPPORT_PREALLOCATION
+	OFF_T preallocated_len = 0;
+	OFF_T offset = 0;
+#endif
 
 	if ((ifd = do_open(source, O_RDONLY, 0)) < 0) {
 		rsyserr(FERROR_XFER, errno, "open %s", full_fname(source));
@@ -293,7 +298,27 @@ int copy_file(const char *source, const char *dest, int ofd,
 		}
 	}
 
+#ifdef SUPPORT_PREALLOCATION
+	if (preallocate_files) {
+		/* Preallocate enough space for file's eventual length if
+		 * possible; seems to reduce fragmentation on Windows. */
+		STRUCT_STAT srcst;
+		if (do_fstat(ifd, &srcst) == 0) {
+			if (srcst.st_size > 0) {
+				if (posix_fallocate(ofd, 0, srcst.st_size) == 0)
+					preallocated_len = srcst.st_size;
+				else
+					rsyserr(FINFO, errno, "posix_fallocate %s", full_fname(dest));
+			}
+		} else
+			rsyserr(FINFO, errno, "fstat %s", full_fname(source));
+	}
+#endif
+
 	while ((len = safe_read(ifd, buf, sizeof buf)) > 0) {
+#ifdef SUPPORT_PREALLOCATION
+		offset += len;
+#endif
 		if (full_write(ofd, buf, len) < 0) {
 			rsyserr(FERROR_XFER, errno, "write %s", full_fname(dest));
 			close(ifd);
@@ -314,6 +339,16 @@ int copy_file(const char *source, const char *dest, int ofd,
 			full_fname(source));
 	}
 
+#ifdef SUPPORT_PREALLOCATION
+	/* Source file might have shrunk since we fstatted it.
+	 * Cut off any extra preallocated zeros from dest file. */
+	if (preallocated_len > offset)
+		if (ftruncate(ofd, offset) < 0)
+			/* If we fail to truncate, the dest file may be wrong, so we
+			 * must trigger the "partial transfer" error. */
+			rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest));
+#endif
+
 	if (close(ofd) < 0) {
 		rsyserr(FERROR_XFER, errno, "close failed on %s",
 			full_fname(dest));
