1This patch adds support for HFS+ compression.
2
3Written by Mike Bombich.  Taken from http://www.bombich.com/rsync.html
4
5Modified by Wayne to fix some issues and tweak the implementation a bit.
6This compiles on OS X and passes the testsuite, but otherwise UNTESTED!
7
8To use this patch, run these commands for a successful build:
9
10    patch -p1 <patches/fileflags.diff
11    patch -p1 <patches/hfs-compression.diff
12    ./prepare-source
13    ./configure
14    make
15
16TODO:
17 - Should rsync try to treat the compressed data as file data and use the
18   rsync algorithm on the data transfer?
19
20based-on: patch/master/fileflags
21diff --git a/flist.c b/flist.c
22--- a/flist.c
23+++ b/flist.c
24@@ -1629,6 +1629,9 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
25 #ifdef SUPPORT_XATTRS
26 		if (preserve_xattrs) {
27 			sx.st.st_mode = file->mode;
28+			if (preserve_fileflags)
29+				sx.st.st_flags = F_FFLAGS(file);
30+			sx.st.st_mtime = file->modtime; /* get_xattr needs mtime for decmpfs xattrs */
31 			if (get_xattr(fname, &sx) < 0) {
32 				io_error |= IOERR_GENERAL;
33 				return NULL;
34diff --git a/generator.c b/generator.c
35--- a/generator.c
36+++ b/generator.c
37@@ -37,6 +37,7 @@ extern int implied_dirs;
38 extern int keep_dirlinks;
39 extern int preserve_acls;
40 extern int preserve_xattrs;
41+extern int preserve_hfs_compression;
42 extern int preserve_links;
43 extern int preserve_devices;
44 extern int write_devices;
45@@ -1798,6 +1799,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
46 					fname, fnamecmpbuf);
47 			}
48 			sx.st.st_size = F_LENGTH(fuzzy_file);
49+#ifdef SUPPORT_HFS_COMPRESSION
50+			if (sx.st.st_flags & UF_COMPRESSED) {
51+				if (preserve_hfs_compression)
52+					sx.st.st_size = 0;
53+				else
54+					sx.st.st_flags &= ~UF_COMPRESSED;
55+			}
56+#endif
57 			statret = 0;
58 			fnamecmp = fnamecmpbuf;
59 		}
60@@ -1965,6 +1974,18 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
61 	if (read_batch)
62 		goto cleanup;
63
64+#ifdef SUPPORT_HFS_COMPRESSION
65+	if (F_FFLAGS(file) & UF_COMPRESSED) {
66+		/* At this point the attrs have already been copied, we don't need to transfer a data fork
67+		 * If my filesystem doesn't support HFS compression, the existing file's content
68+		 * will not be automatically truncated, so we'll do that manually here */
69+		if (preserve_hfs_compression && sx.st.st_size > 0) {
70+			if (ftruncate(fd, 0) == 0)
71+				sx.st.st_size = 0;
72+		}
73+	}
74+#endif
75+
76 	if (statret != 0 || whole_file)
77 		write_sum_head(f_out, NULL);
78 	else if (sx.st.st_size <= 0) {
79diff --git a/lib/sysxattrs.c b/lib/sysxattrs.c
80--- a/lib/sysxattrs.c
81+++ b/lib/sysxattrs.c
82@@ -22,10 +22,17 @@
83 #include "rsync.h"
84 #include "sysxattrs.h"
85
86+extern int preserve_hfs_compression;
87+
88 #ifdef SUPPORT_XATTRS
89
90 #ifdef HAVE_OSX_XATTRS
91+#ifndef XATTR_SHOWCOMPRESSION
92+#define XATTR_SHOWCOMPRESSION 0x0020
93+#endif
94 #define GETXATTR_FETCH_LIMIT (64*1024*1024)
95+
96+int xattr_options = XATTR_NOFOLLOW;
97 #endif
98
99 #if defined HAVE_LINUX_XATTRS
100@@ -59,7 +66,12 @@ ssize_t sys_llistxattr(const char *path, char *list, size_t size)
101
102 ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
103 {
104-	ssize_t len = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
105+	ssize_t len;
106+
107+	if (preserve_hfs_compression)
108+		xattr_options |= XATTR_SHOWCOMPRESSION;
109+
110+	len = getxattr(path, name, value, size, 0, xattr_options);
111
112 	/* If we're retrieving data, handle resource forks > 64MB specially */
113 	if (value != NULL && len == GETXATTR_FETCH_LIMIT && (size_t)len < size) {
114@@ -67,7 +79,7 @@ ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t si
115 		u_int32_t offset = len;
116 		size_t data_retrieved = len;
117 		while (data_retrieved < size) {
118-			len = getxattr(path, name, value + offset, size - data_retrieved, offset, XATTR_NOFOLLOW);
119+			len = getxattr(path, name, value + offset, size - data_retrieved, offset, xattr_options);
120 			if (len <= 0)
121 				break;
122 			data_retrieved += len;
123@@ -91,12 +103,16 @@ int sys_lsetxattr(const char *path, const char *name, const void *value, size_t
124
125 int sys_lremovexattr(const char *path, const char *name)
126 {
127-	return removexattr(path, name, XATTR_NOFOLLOW);
128+	if (preserve_hfs_compression)
129+		xattr_options |= XATTR_SHOWCOMPRESSION;
130+	return removexattr(path, name, xattr_options);
131 }
132
133 ssize_t sys_llistxattr(const char *path, char *list, size_t size)
134 {
135-	return listxattr(path, list, size, XATTR_NOFOLLOW);
136+	if (preserve_hfs_compression)
137+		xattr_options |= XATTR_SHOWCOMPRESSION;
138+	return listxattr(path, list, size, xattr_options);
139 }
140
141 #elif HAVE_FREEBSD_XATTRS
142diff --git a/main.c b/main.c
143--- a/main.c
144+++ b/main.c
145@@ -34,6 +34,10 @@
146 #ifdef SUPPORT_FORCE_CHANGE
147 #include <sys/sysctl.h>
148 #endif
149+#ifdef SUPPORT_HFS_COMPRESSION
150+#include <sys/attr.h> /* For getattrlist() */
151+#include <sys/mount.h> /* For statfs() */
152+#endif
153
154 extern int dry_run;
155 extern int list_only;
156@@ -60,6 +64,7 @@ extern int copy_dirlinks;
157 extern int copy_unsafe_links;
158 extern int keep_dirlinks;
159 extern int preserve_hard_links;
160+extern int preserve_hfs_compression;
161 extern int protocol_version;
162 extern int mkpath_dest_arg;
163 extern int file_total;
164@@ -117,6 +122,7 @@ int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = d
165 mode_t orig_umask = 0;
166 int batch_gen_fd = -1;
167 int sender_keeps_checksum = 0;
168+int fs_supports_hfs_compression = 0;
169 int raw_argc, cooked_argc;
170 char **raw_argv, **cooked_argv;
171
172@@ -671,6 +677,43 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
173 	return pid;
174 }
175
176+#ifdef SUPPORT_HFS_COMPRESSION
177+static void hfs_receiver_check(void)
178+{
179+	struct statfs fsb;
180+	struct attrlist attrs;
181+	struct {
182+		int32_t len;
183+		vol_capabilities_set_t caps;
184+	} attrData;
185+
186+	if (preserve_hfs_compression != 1)
187+		return; /* Nothing to check if --hfs-compression option isn't enabled. */
188+
189+	if (statfs(".", &fsb) < 0) {
190+		rsyserr(FERROR, errno, "statfs %s failed", curr_dir);
191+		exit_cleanup(RERR_FILESELECT);
192+	}
193+
194+	bzero(&attrs, sizeof attrs);
195+	attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
196+	attrs.volattr = ATTR_VOL_CAPABILITIES;
197+
198+	bzero(&attrData, sizeof attrData);
199+	attrData.len = sizeof attrData;
200+
201+	if (getattrlist(fsb.f_mntonname, &attrs, &attrData, sizeof attrData, 0) < 0) {
202+		rsyserr(FERROR, errno, "getattrlist %s failed", curr_dir);
203+		exit_cleanup(RERR_FILESELECT);
204+	}
205+
206+	if (!(attrData.caps[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_DECMPFS_COMPRESSION)) {
207+		rprintf(FERROR, "The destination filesystem does not support HFS+ compression.\n");
208+		exit_cleanup(RERR_UNSUPPORTED);
209+	}
210+}
211+#endif
212+
213 /* The receiving side operates in one of two modes:
214  *
215  * 1. it receives any number of files into a destination directory,
216@@ -748,6 +791,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
217 				exit_cleanup(RERR_FILESELECT);
218 			}
219 			filesystem_dev = st.st_dev; /* ensures --force works right w/-x */
220+#ifdef SUPPORT_HFS_COMPRESSION
221+			hfs_receiver_check();
222+#endif
223 			return NULL;
224 		}
225 		if (file_total > 1) {
226@@ -805,7 +851,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
227 				full_fname(dest_path));
228 			exit_cleanup(RERR_FILESELECT);
229 		}
230-
231+#ifdef SUPPORT_HFS_COMPRESSION
232+		hfs_receiver_check();
233+#endif
234 		return NULL;
235 	}
236
237@@ -825,6 +873,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
238 			full_fname(dest_path));
239 		exit_cleanup(RERR_FILESELECT);
240 	}
241+#ifdef SUPPORT_HFS_COMPRESSION
242+	hfs_receiver_check();
243+#endif
244 	*cp = '/';
245
246 	return cp + 1;
247diff --git a/options.c b/options.c
248--- a/options.c
249+++ b/options.c
250@@ -52,6 +52,7 @@ int preserve_links = 0;
251 int preserve_hard_links = 0;
252 int preserve_acls = 0;
253 int preserve_xattrs = 0;
254+int preserve_hfs_compression = 0;
255 int preserve_perms = 0;
256 int preserve_fileflags = 0;
257 int preserve_executability = 0;
258@@ -721,6 +722,10 @@ static struct poptOption long_options[] = {
259   {"no-force-change",  0,  POPT_ARG_VAL,    &force_change, 0, 0, 0 },
260   {"force-uchange",    0,  POPT_ARG_VAL,    &force_change, USR_IMMUTABLE, 0, 0 },
261   {"force-schange",    0,  POPT_ARG_VAL,    &force_change, SYS_IMMUTABLE, 0, 0 },
262+  {"hfs-compression",  0,  POPT_ARG_VAL,    &preserve_hfs_compression, 1, 0, 0 },
263+  {"no-hfs-compression",0, POPT_ARG_VAL,    &preserve_hfs_compression, 0, 0, 0 },
264+  {"protect-decmpfs",  0,  POPT_ARG_VAL,    &preserve_hfs_compression, 2, 0, 0 },
265+  {"no-protect-decmpfs",0, POPT_ARG_VAL,    &preserve_hfs_compression, 0, 0, 0 },
266   {"ignore-errors",    0,  POPT_ARG_VAL,    &ignore_errors, 1, 0, 0 },
267   {"no-ignore-errors", 0,  POPT_ARG_VAL,    &ignore_errors, 0, 0, 0 },
268   {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
269@@ -1016,6 +1021,10 @@ static void set_refuse_options(void)
270 	parse_one_refuse_match(0, "force-uchange", list_end);
271 	parse_one_refuse_match(0, "force-schange", list_end);
272 #endif
273+#ifndef SUPPORT_HFS_COMPRESSION
274+	parse_one_refuse_match(0, "hfs-compression", list_end);
275+	parse_one_refuse_match(0, "protect-decmpfs", list_end);
276+#endif
277
278 	/* Now we use the descrip values to actually mark the options for refusal. */
279 	for (op = long_options; op != list_end; op++) {
280@@ -2070,6 +2079,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
281 	}
282 #endif
283
284+#ifdef SUPPORT_HFS_COMPRESSION
285+	if (preserve_hfs_compression) {
286+		if (!preserve_xattrs)
287+			preserve_xattrs = 1;
288+		if (!preserve_fileflags)
289+			preserve_fileflags = 1;
290+	}
291+#endif
292+
293 	if (write_batch && read_batch) {
294 		snprintf(err_buf, sizeof err_buf,
295 			"--write-batch and --read-batch can not be used together\n");
296@@ -2667,6 +2685,11 @@ void server_options(char **args, int *argc_p)
297 	if (preserve_fileflags)
298 		args[ac++] = "--fileflags";
299
300+#ifdef SUPPORT_HFS_COMPRESSION
301+	if (preserve_hfs_compression)
302+		args[ac++] = preserve_hfs_compression == 1 ? "--hfs-compression" : "--protect-decmpfs";
303+#endif
304+
305 	if (do_compression && do_compression_level != CLVL_NOT_SPECIFIED) {
306 		if (asprintf(&arg, "--compress-level=%d", do_compression_level) < 0)
307 			goto oom;
308diff --git a/rsync.1.md b/rsync.1.md
309--- a/rsync.1.md
310+++ b/rsync.1.md
311@@ -366,6 +366,8 @@ detailed description below for a complete description.
312 --chmod=CHMOD            affect file and/or directory permissions
313 --acls, -A               preserve ACLs (implies --perms)
314 --xattrs, -X             preserve extended attributes
315+--hfs-compression        preserve HFS compression if supported
316+--protect-decmpfs        preserve HFS compression as xattrs
317 --owner, -o              preserve owner (super-user only)
318 --group, -g              preserve group
319 --devices                preserve device files (super-user only)
320@@ -1291,6 +1293,47 @@ your home directory (remove the '=' for that).
321     receiving side.  It does not try to affect user flags.  This option
322     overrides `--force-change` and `--force-uchange`.
323
324+0.  `--hfs-compression`
325+
326+    This option causes rsync to preserve HFS+ compression if the destination
327+    filesystem supports it.  If the destination does not support it, rsync will
328+    exit with an error.
329+
330+    Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
331+    is compressed has no data in its data fork. Rather, the compressed data is
332+    stored in an extended attribute named com.apple.decmpfs and a file flag is
333+    set to indicate that the file is compressed (UF_COMPRESSED). HFS+
334+    decompresses this data "on-the-fly" and presents it to the operating system
335+    as a normal file.  Normal attempts to copy compressed files (e.g. in the
336+    Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
337+    remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
338+    extended attribute. This option will preserve the data in the
339+    com.apple.decmpfs extended attribute and ignore the synthesized data in the
340+    file contents.
341+
342+    This option implies both `--fileflags` and (--xattrs).
343+
344+0.  `--protect-decmpfs`
345+
346+    The com.apple.decmpfs extended attribute is hidden by default from list/get
347+    xattr calls, therefore normal attempts to copy compressed files will
348+    functionally decompress those files. While this is desirable behavior when
349+    copying files to filesystems that do not support HFS+ compression, it has
350+    serious performance and capacity impacts when backing up or restoring the
351+    Mac OS X filesystem.
352+
353+    This option will transfer the com.apple.decmpfs extended attribute
354+    regardless of support on the destination. If a source file is compressed
355+    and an existing file on the destination is not compressed, the data fork of
356+    the destination file will be truncated and the com.apple.decmpfs xattr will
357+    be transferred instead. Note that compressed files will not be readable to
358+    the operating system of the destination if that operating system does not
359+    support HFS+ compression. Once restored (with or without this option) to an
360+    operating system that supports HFS+ compression, however, these files will
361+    be accessible as usual.
362+
363+    This option implies `--fileflags` and `--xattrs`.
364+
365 0.  `--chmod=CHMOD`
366
367     This option tells rsync to apply one or more comma-separated "chmod" modes
368diff --git a/rsync.c b/rsync.c
369--- a/rsync.c
370+++ b/rsync.c
371@@ -606,8 +606,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
372 #ifdef SUPPORT_XATTRS
373 	if (am_root < 0)
374 		set_stat_xattr(fname, file, new_mode);
375-	if (preserve_xattrs && fnamecmp)
376+	if (preserve_xattrs && fnamecmp) {
377+		uint32 tmpflags = sxp->st.st_flags;
378+		sxp->st.st_flags = F_FFLAGS(file); /* set_xattr() needs to check UF_COMPRESSED */
379 		set_xattr(fname, file, fnamecmp, sxp);
380+		sxp->st.st_flags = tmpflags;
381+		if (S_ISDIR(sxp->st.st_mode))
382+			link_stat(fname, &sx2.st, 0);
383+	}
384 #endif
385
386 	if (!preserve_times
387@@ -621,7 +627,11 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
388 	/* Don't set the creation date on the root folder of an HFS+ volume. */
389 	if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
390 		flags |= ATTRS_SKIP_CRTIME;
391-	if (!(flags & ATTRS_SKIP_MTIME) && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
392+	if (!(flags & ATTRS_SKIP_MTIME)
393+#ifdef SUPPORT_HFS_COMPRESSION
394+	 && !(sxp->st.st_flags & UF_COMPRESSED) /* setting this alters mtime, so defer to after set_fileflags */
395+#endif
396+	 && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
397 		sx2.st.st_mtime = file->modtime;
398 #ifdef ST_MTIME_NSEC
399 		sx2.st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
400@@ -698,6 +708,16 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
401 		 && !set_fileflags(fname, fileflags))
402 			goto cleanup;
403 		updated = 1;
404+#ifdef SUPPORT_HFS_COMPRESSION
405+		int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), new_mode, fileflags);
406+		if (ret < 0) {
407+			rsyserr(FERROR_XFER, errno, "failed to set times on %s",
408+				full_fname(fname));
409+			goto cleanup;
410+		}
411+		if (ret != 0)
412+			file->flags |= FLAG_TIME_FAILED;
413+#endif
414 	}
415 #endif
416
417diff --git a/rsync.h b/rsync.h
418--- a/rsync.h
419+++ b/rsync.h
420@@ -572,6 +572,17 @@ typedef unsigned int size_t;
421 #endif
422 #endif
423
424+#ifndef UF_COMPRESSED
425+#define UF_COMPRESSED 0x00000020
426+#endif
427+#ifndef VOL_CAP_FMT_DECMPFS_COMPRESSION
428+#define VOL_CAP_FMT_DECMPFS_COMPRESSION 0x00010000
429+#endif
430+
431+#if defined SUPPORT_XATTRS && defined SUPPORT_FILEFLAGS
432+#define SUPPORT_HFS_COMPRESSION 1
433+#endif
434+
435 #ifndef __APPLE__ /* Do we need a configure check for this? */
436 #define SUPPORT_ATIMES 1
437 #endif
438diff --git a/t_stub.c b/t_stub.c
439--- a/t_stub.c
440+++ b/t_stub.c
441@@ -34,6 +34,7 @@ int preserve_times = 0;
442 int preserve_xattrs = 0;
443 int preserve_perms = 0;
444 int preserve_executability = 0;
445+int preserve_hfs_compression = 0;
446 int open_noatime = 0;
447 size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
448 char *partial_dir;
449diff --git a/xattrs.c b/xattrs.c
450--- a/xattrs.c
451+++ b/xattrs.c
452@@ -33,6 +33,7 @@ extern int am_generator;
453 extern int read_only;
454 extern int list_only;
455 extern int preserve_xattrs;
456+extern int preserve_hfs_compression;
457 extern int preserve_links;
458 extern int preserve_devices;
459 extern int preserve_specials;
460@@ -42,6 +43,10 @@ extern int saw_xattr_filter;
461 #define RSYNC_XAL_INITIAL 5
462 #define RSYNC_XAL_LIST_INITIAL 100
463
464+#define GXD_NO_MISSING_ERROR (1<<0)
465+#define GXD_OMIT_COMPRESSED (1<<1)
466+#define GXD_FILE_IS_COMPRESSED (1<<2)
467+
468 #define MAX_FULL_DATUM 32
469
470 #define HAS_PREFIX(str, prfx) (*(str) == *(prfx) && strncmp(str, prfx, sizeof (prfx) - 1) == 0)
471@@ -73,6 +78,17 @@ extern int saw_xattr_filter;
472 #define XDEF_ACL_SUFFIX "dacl"
473 #define XDEF_ACL_ATTR RSYNC_PREFIX "%" XDEF_ACL_SUFFIX
474
475+#define APPLE_PREFIX "com.apple."
476+#define APLPRE_LEN ((int)sizeof APPLE_PREFIX - 1)
477+#define DECMPFS_SUFFIX "decmpfs"
478+#define RESOURCEFORK_SUFFIX "ResourceFork"
479+
480+#define UNREAD_DATA ((char *)1)
481+
482+#if MAX_DIGEST_LEN < SIZEOF_TIME_T
483+#error MAX_DIGEST_LEN is too small to hold an mtime
484+#endif
485+
486 typedef struct {
487 	char *datum, *name;
488 	size_t datum_len, name_len;
489@@ -180,7 +196,7 @@ static ssize_t get_xattr_names(const char *fname)
490 /* On entry, the *len_ptr parameter contains the size of the extra space we
491  * should allocate when we create a buffer for the data.  On exit, it contains
492  * the length of the datum. */
493-static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, int no_missing_error)
494+static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, int flags)
495 {
496 	size_t datum_len = sys_lgetxattr(fname, name, NULL, 0);
497 	size_t extra_len = *len_ptr;
498@@ -189,7 +205,7 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
499 	*len_ptr = datum_len;
500
501 	if (datum_len == (size_t)-1) {
502-		if (errno == ENOTSUP || no_missing_error)
503+		if (errno == ENOTSUP || flags & GXD_NO_MISSING_ERROR)
504 			return NULL;
505 		rsyserr(FERROR_XFER, errno,
506 			"get_xattr_data: lgetxattr(%s,\"%s\",0) failed",
507@@ -197,6 +213,15 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
508 		return NULL;
509 	}
510
511+	if (flags & GXD_OMIT_COMPRESSED && datum_len > MAX_FULL_DATUM
512+	 && HAS_PREFIX(name, APPLE_PREFIX)
513+	 && (strcmp(name+APLPRE_LEN, DECMPFS_SUFFIX) == 0
514+	  || (flags & GXD_FILE_IS_COMPRESSED && strcmp(name+APLPRE_LEN, RESOURCEFORK_SUFFIX) == 0))) {
515+		/* If we are omitting compress-file-related data, we don't want to
516+		 * actually read this data. */
517+		return UNREAD_DATA;
518+	}
519+
520 	if (!datum_len && !extra_len)
521 		extra_len = 1; /* request non-zero amount of memory */
522 	if (SIZE_MAX - datum_len < extra_len)
523@@ -224,7 +249,31 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
524 	return ptr;
525 }
526
527-static int rsync_xal_get(const char *fname, item_list *xalp)
528+static void checksum_xattr_data(char *sum, const char *datum, size_t datum_len, stat_x *sxp)
529+{
530+	if (datum == UNREAD_DATA) {
531+		/* For abbreviated compressed data, we store the file's mtime as the checksum. */
532+		SIVAL(sum, 0, sxp->st.st_mtime);
533+#if SIZEOF_TIME_T > 4
534+		SIVAL(sum, 4, sxp->st.st_mtime >> 32);
535+#if MAX_DIGEST_LEN > 8
536+		memset(sum + 8, 0, MAX_DIGEST_LEN - 8);
537+#endif
538+#else
539+#if MAX_DIGEST_LEN > 4
540+		memset(sum + 4, 0, MAX_DIGEST_LEN - 4);
541+#endif
542+#endif
543+	} else {
544+		sum_init(-1, checksum_seed);
545+		sum_update(datum, datum_len);
546+		sum_end(sum);
547+	}
548+}
549+
550+$$$ERROR$$$ the old patch needs reworking since rsync_xal_get() has totally changed!
551+
552+static int rsync_xal_get(const char *fname, stat_x *sxp)
553 {
554 	ssize_t list_len, name_len;
555 	size_t datum_len, name_offset;
556@@ -233,7 +282,8 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
557 	int user_only = am_sender ? 0 : !am_root;
558 #endif
559 	rsync_xa *rxa;
560-	int count;
561+	int count, flags;
562+	item_list *xalp = sxp->xattr;
563
564 	/* This puts the name list into the "namebuf" buffer. */
565 	if ((list_len = get_xattr_names(fname)) < 0)
566@@ -264,11 +314,15 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
567 		}
568
569 		datum_len = name_len; /* Pass extra size to get_xattr_data() */
570-		if (!(ptr = get_xattr_data(fname, name, &datum_len, 0)))
571+		flags = GXD_OMIT_COMPRESSED;
572+		if (preserve_hfs_compression && sxp->st.st_flags & UF_COMPRESSED)
573+			flags |= GXD_FILE_IS_COMPRESSED;
574+		if (!(ptr = get_xattr_data(fname, name, &datum_len, flags)))
575 			return -1;
576
577 		if (datum_len > MAX_FULL_DATUM) {
578 			/* For large datums, we store a flag and a checksum. */
579+			char *datum = ptr;
580 			name_offset = 1 + MAX_DIGEST_LEN;
581 			sum_init(-1, checksum_seed);
582 			sum_update(ptr, datum_len);
583@@ -276,7 +330,9 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
584
585 			ptr = new_array(char, name_offset + name_len);
586 			*ptr = XSTATE_ABBREV;
587-			sum_end(ptr + 1);
588+			checksum_xattr_data(ptr+1, datum, datum_len, sxp);
589+			if (datum != UNREAD_DATA)
590+				free(datum);
591 		} else
592 			name_offset = datum_len;
593
594@@ -322,7 +378,7 @@ int get_xattr(const char *fname, stat_x *sxp)
595 	} else if (IS_MISSING_FILE(sxp->st))
596 		return 0;
597
598-	if (rsync_xal_get(fname, sxp->xattr) < 0) {
599+	if (rsync_xal_get(fname, sxp) < 0) {
600 		free_xattr(sxp);
601 		return -1;
602 	}
603@@ -359,6 +415,8 @@ int copy_xattrs(const char *source, const char *dest)
604 		datum_len = 0;
605 		if (!(ptr = get_xattr_data(source, name, &datum_len, 0)))
606 			return -1;
607+		if (ptr == UNREAD_DATA)
608+			continue; /* XXX Is this right? */
609 		if (sys_lsetxattr(dest, name, ptr, datum_len) < 0) {
610 			int save_errno = errno ? errno : EINVAL;
611 			rsyserr(FERROR_XFER, errno,
612@@ -449,6 +507,7 @@ static int find_matching_xattr(const item_list *xalp)
613 	}
614
615 	return -1;
616+#endif
617 }
618
619 /* Store *xalp on the end of rsync_xal_l */
620@@ -663,11 +722,13 @@ void send_xattr_request(const char *fname, struct file_struct *file, int f_out)
621
622 			/* Re-read the long datum. */
623 			if (!(ptr = get_xattr_data(fname, rxa->name, &len, 0))) {
624-				rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname);
625+				if (errno != ENOTSUP && errno != ENOATTR)
626+					rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname);
627 				write_varint(f_out, 0);
628 				continue;
629 			}
630
631+			assert(ptr != UNREAD_DATA);
632 			write_varint(f_out, len); /* length might have changed! */
633 			write_bigbuf(f_out, ptr, len);
634 			free(ptr);
635@@ -948,7 +1009,7 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
636 	int user_only = am_root <= 0;
637 #endif
638 	size_t name_len;
639-	int ret = 0;
640+	int flags, ret = 0;
641
642 	/* This puts the current name list into the "namebuf" buffer. */
643 	if ((list_len = get_xattr_names(fname)) < 0)
644@@ -961,7 +1022,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
645 			int sum_len;
646 			/* See if the fnamecmp version is identical. */
647 			len = name_len = rxas[i].name_len;
648-			if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) {
649+			flags = GXD_OMIT_COMPRESSED | GXD_NO_MISSING_ERROR;
650+			if (preserve_hfs_compression && sxp->st.st_flags & UF_COMPRESSED)
651+				flags |= GXD_FILE_IS_COMPRESSED;
652+			if ((ptr = get_xattr_data(fnamecmp, name, &len, flags)) == NULL) {
653 			  still_abbrev:
654 				if (am_generator)
655 					continue;
656@@ -970,6 +1034,8 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
657 				ret = -1;
658 				continue;
659 			}
660+			if (ptr == UNREAD_DATA)
661+				continue; /* XXX Is this right? */
662 			if (len != rxas[i].datum_len) {
663 				free(ptr);
664 				goto still_abbrev;
665@@ -1047,6 +1113,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
666 		}
667 	}
668
669+#ifdef HAVE_OSX_XATTRS
670+	rsync_xal_free(xalp); /* Free this because we aren't using find_matching_xattr(). */
671+#endif
672+
673 	return ret;
674 }
675
676@@ -1095,7 +1165,7 @@ char *get_xattr_acl(const char *fname, int is_access_acl, size_t *len_p)
677 {
678 	const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR;
679 	*len_p = 0; /* no extra data alloc needed from get_xattr_data() */
680-	return get_xattr_data(fname, name, len_p, 1);
681+	return get_xattr_data(fname, name, len_p, GXD_NO_MISSING_ERROR);
682 }
683
684 int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len)
685@@ -1238,11 +1308,33 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
686 	return 0;
687 }
688
689+#ifdef SUPPORT_HFS_COMPRESSION
690+static inline void hfs_compress_tweaks(STRUCT_STAT *fst)
691+{
692+	if (fst->st_flags & UF_COMPRESSED) {
693+		if (preserve_hfs_compression) {
694+			/* We're sending the compression xattr, not the decompressed data fork.
695+			 * Setting rsync's idea of the file size to 0 effectively prevents the
696+			 * transfer of the data fork. */
697+			fst->st_size = 0;
698+		} else {
699+			/* If the sender's filesystem supports compression, then we'll be able
700+			 * to send the decompressed data fork and the decmpfs xattr will be
701+			 * hidden (not sent). As such, we need to strip the compression flag. */
702+			fst->st_flags &= ~UF_COMPRESSED;
703+		}
704+	}
705+}
706+#endif
707+
708 int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
709 {
710 	int ret = do_stat(fname, fst);
711 	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
712 		xst->st_mode = 0;
713+#ifdef SUPPORT_HFS_COMPRESSION
714+	hfs_compress_tweaks(fst);
715+#endif
716 	return ret;
717 }
718
719@@ -1251,6 +1343,9 @@ int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
720 	int ret = do_lstat(fname, fst);
721 	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
722 		xst->st_mode = 0;
723+#ifdef SUPPORT_HFS_COMPRESSION
724+	hfs_compress_tweaks(fst);
725+#endif
726 	return ret;
727 }
728
729@@ -1259,6 +1354,9 @@ int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
730 	int ret = do_fstat(fd, fst);
731 	if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
732 		xst->st_mode = 0;
733+#ifdef SUPPORT_HFS_COMPRESSION
734+	hfs_compress_tweaks(fst);
735+#endif
736 	return ret;
737 }
738
739diff -Nurp a/rsync.1 b/rsync.1
740--- a/rsync.1
741+++ b/rsync.1
742@@ -442,6 +442,8 @@ detailed description below for a complet
743 --chmod=CHMOD            affect file and/or directory permissions
744 --acls, -A               preserve ACLs (implies --perms)
745 --xattrs, -X             preserve extended attributes
746+--hfs-compression        preserve HFS compression if supported
747+--protect-decmpfs        preserve HFS compression as xattrs
748 --owner, -o              preserve owner (super-user only)
749 --group, -g              preserve group
750 --devices                preserve device files (super-user only)
751@@ -1373,6 +1375,43 @@ side.  It does not try to affect system
752 flags on files and directories that are being updated or deleted on the
753 receiving side.  It does not try to affect user flags.  This option
754 overrides \fB\-\-force-change\fP and \fB\-\-force-uchange\fP."
755+.IP "\fB\-\-hfs-compression\fP"
756+This option causes rsync to preserve HFS+ compression if the destination
757+filesystem supports it.  If the destination does not support it, rsync will
758+exit with an error.
759+.IP
760+Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
761+is compressed has no data in its data fork. Rather, the compressed data is
762+stored in an extended attribute named com.apple.decmpfs and a file flag is
763+set to indicate that the file is compressed (UF_COMPRESSED). HFS+
764+decompresses this data "on-the-fly" and presents it to the operating system
765+as a normal file.  Normal attempts to copy compressed files (e.g. in the
766+Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
767+remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
768+extended attribute. This option will preserve the data in the
769+com.apple.decmpfs extended attribute and ignore the synthesized data in the
770+file contents.
771+.IP
772+This option implies both \fB\-\-fileflags\fP and (\-\-xattrs).
773+.IP "\fB\-\-protect-decmpfs\fP"
774+The com.apple.decmpfs extended attribute is hidden by default from list/get
775+xattr calls, therefore normal attempts to copy compressed files will
776+functionally decompress those files. While this is desirable behavior when
777+copying files to filesystems that do not support HFS+ compression, it has
778+serious performance and capacity impacts when backing up or restoring the
779+Mac OS X filesystem.
780+.IP
781+This option will transfer the com.apple.decmpfs extended attribute
782+regardless of support on the destination. If a source file is compressed
783+and an existing file on the destination is not compressed, the data fork of
784+the destination file will be truncated and the com.apple.decmpfs xattr will
785+be transferred instead. Note that compressed files will not be readable to
786+the operating system of the destination if that operating system does not
787+support HFS+ compression. Once restored (with or without this option) to an
788+operating system that supports HFS+ compression, however, these files will
789+be accessible as usual.
790+.IP
791+This option implies \fB\-\-fileflags\fP and \fB\-\-xattrs\fP.
792 .IP "\fB\-\-chmod=CHMOD\fP"
793 This option tells rsync to apply one or more comma-separated "chmod" modes
794 to the permission of the files in the transfer.  The resulting value is
795diff -Nurp a/rsync.1.html b/rsync.1.html
796--- a/rsync.1.html
797+++ b/rsync.1.html
798@@ -357,6 +357,8 @@ detailed description below for a complet
799 --chmod=CHMOD            affect file and/or directory permissions
800 --acls, -A               preserve ACLs (implies --perms)
801 --xattrs, -X             preserve extended attributes
802+--hfs-compression        preserve HFS compression if supported
803+--protect-decmpfs        preserve HFS compression as xattrs
804 --owner, -o              preserve owner (super-user only)
805 --group, -g              preserve group
806 --devices                preserve device files (super-user only)
807@@ -1252,6 +1254,43 @@ receiving side.  It does not try to affe
808 overrides <code>--force-change</code> and <code>--force-uchange</code>.</dt><dd>
809 </dd>
810
811+<dt><code>--hfs-compression</code></dt><dd>
812+<p>This option causes rsync to preserve HFS+ compression if the destination
813+filesystem supports it.  If the destination does not support it, rsync will
814+exit with an error.</p>
815+<p>Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
816+is compressed has no data in its data fork. Rather, the compressed data is
817+stored in an extended attribute named com.apple.decmpfs and a file flag is
818+set to indicate that the file is compressed (UF_COMPRESSED). HFS+
819+decompresses this data &quot;on-the-fly&quot; and presents it to the operating system
820+as a normal file.  Normal attempts to copy compressed files (e.g. in the
821+Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
822+remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
823+extended attribute. This option will preserve the data in the
824+com.apple.decmpfs extended attribute and ignore the synthesized data in the
825+file contents.</p>
826+<p>This option implies both <code>--fileflags</code> and (-&#8288;-&#8288;xattrs).</p>
827+</dd>
828+
829+<dt><code>--protect-decmpfs</code></dt><dd>
830+<p>The com.apple.decmpfs extended attribute is hidden by default from list/get
831+xattr calls, therefore normal attempts to copy compressed files will
832+functionally decompress those files. While this is desirable behavior when
833+copying files to filesystems that do not support HFS+ compression, it has
834+serious performance and capacity impacts when backing up or restoring the
835+Mac OS X filesystem.</p>
836+<p>This option will transfer the com.apple.decmpfs extended attribute
837+regardless of support on the destination. If a source file is compressed
838+and an existing file on the destination is not compressed, the data fork of
839+the destination file will be truncated and the com.apple.decmpfs xattr will
840+be transferred instead. Note that compressed files will not be readable to
841+the operating system of the destination if that operating system does not
842+support HFS+ compression. Once restored (with or without this option) to an
843+operating system that supports HFS+ compression, however, these files will
844+be accessible as usual.</p>
845+<p>This option implies <code>--fileflags</code> and <code>--xattrs</code>.</p>
846+</dd>
847+
848 <dt><code>--chmod=CHMOD</code></dt><dd>
849 <p>This option tells rsync to apply one or more comma-separated &quot;chmod&quot; modes
850 to the permission of the files in the transfer.  The resulting value is
851