File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / rsync / patches / hfs-compression.diff
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:32:36 2021 UTC (3 years, 2 months ago) by misho
Branches: rsync, MAIN
CVS tags: v3_2_3, HEAD
rsync 3.2.3

This patch adds support for HFS+ compression.

Written by Mike Bombich.  Taken from http://www.bombich.com/rsync.html

Modified by Wayne to fix some issues and tweak the implementation a bit.
This compiles on OS X and passes the testsuite, but otherwise UNTESTED!

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

    patch -p1 <patches/fileflags.diff
    patch -p1 <patches/hfs-compression.diff
    ./prepare-source
    ./configure
    make

TODO:
 - Should rsync try to treat the compressed data as file data and use the
   rsync algorithm on the data transfer?

based-on: patch/master/fileflags
diff --git a/flist.c b/flist.c
--- a/flist.c
+++ b/flist.c
@@ -1629,6 +1629,9 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
 #ifdef SUPPORT_XATTRS
 		if (preserve_xattrs) {
 			sx.st.st_mode = file->mode;
+			if (preserve_fileflags)
+				sx.st.st_flags = F_FFLAGS(file);
+			sx.st.st_mtime = file->modtime; /* get_xattr needs mtime for decmpfs xattrs */
 			if (get_xattr(fname, &sx) < 0) {
 				io_error |= IOERR_GENERAL;
 				return NULL;
diff --git a/generator.c b/generator.c
--- a/generator.c
+++ b/generator.c
@@ -37,6 +37,7 @@ extern int implied_dirs;
 extern int keep_dirlinks;
 extern int preserve_acls;
 extern int preserve_xattrs;
+extern int preserve_hfs_compression;
 extern int preserve_links;
 extern int preserve_devices;
 extern int write_devices;
@@ -1798,6 +1799,14 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
 					fname, fnamecmpbuf);
 			}
 			sx.st.st_size = F_LENGTH(fuzzy_file);
+#ifdef SUPPORT_HFS_COMPRESSION
+			if (sx.st.st_flags & UF_COMPRESSED) {
+				if (preserve_hfs_compression)
+					sx.st.st_size = 0;
+				else
+					sx.st.st_flags &= ~UF_COMPRESSED;
+			}
+#endif
 			statret = 0;
 			fnamecmp = fnamecmpbuf;
 		}
@@ -1965,6 +1974,18 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
 	if (read_batch)
 		goto cleanup;
 
+#ifdef SUPPORT_HFS_COMPRESSION
+	if (F_FFLAGS(file) & UF_COMPRESSED) {
+		/* At this point the attrs have already been copied, we don't need to transfer a data fork
+		 * If my filesystem doesn't support HFS compression, the existing file's content
+		 * will not be automatically truncated, so we'll do that manually here */
+		if (preserve_hfs_compression && sx.st.st_size > 0) {
+			if (ftruncate(fd, 0) == 0)
+				sx.st.st_size = 0;
+		}
+	}
+#endif
+
 	if (statret != 0 || whole_file)
 		write_sum_head(f_out, NULL);
 	else if (sx.st.st_size <= 0) {
diff --git a/lib/sysxattrs.c b/lib/sysxattrs.c
--- a/lib/sysxattrs.c
+++ b/lib/sysxattrs.c
@@ -22,10 +22,17 @@
 #include "rsync.h"
 #include "sysxattrs.h"
 
+extern int preserve_hfs_compression;
+
 #ifdef SUPPORT_XATTRS
 
 #ifdef HAVE_OSX_XATTRS
+#ifndef XATTR_SHOWCOMPRESSION
+#define XATTR_SHOWCOMPRESSION 0x0020
+#endif
 #define GETXATTR_FETCH_LIMIT (64*1024*1024)
+
+int xattr_options = XATTR_NOFOLLOW;
 #endif
 
 #if defined HAVE_LINUX_XATTRS
@@ -59,7 +66,12 @@ ssize_t sys_llistxattr(const char *path, char *list, size_t size)
 
 ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
 {
-	ssize_t len = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
+	ssize_t len;
+
+	if (preserve_hfs_compression)
+		xattr_options |= XATTR_SHOWCOMPRESSION;
+
+	len = getxattr(path, name, value, size, 0, xattr_options);
 
 	/* If we're retrieving data, handle resource forks > 64MB specially */
 	if (value != NULL && len == GETXATTR_FETCH_LIMIT && (size_t)len < size) {
@@ -67,7 +79,7 @@ ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t si
 		u_int32_t offset = len;
 		size_t data_retrieved = len;
 		while (data_retrieved < size) {
-			len = getxattr(path, name, value + offset, size - data_retrieved, offset, XATTR_NOFOLLOW);
+			len = getxattr(path, name, value + offset, size - data_retrieved, offset, xattr_options);
 			if (len <= 0)
 				break;
 			data_retrieved += len;
@@ -91,12 +103,16 @@ int sys_lsetxattr(const char *path, const char *name, const void *value, size_t
 
 int sys_lremovexattr(const char *path, const char *name)
 {
-	return removexattr(path, name, XATTR_NOFOLLOW);
+	if (preserve_hfs_compression)
+		xattr_options |= XATTR_SHOWCOMPRESSION;
+	return removexattr(path, name, xattr_options);
 }
 
 ssize_t sys_llistxattr(const char *path, char *list, size_t size)
 {
-	return listxattr(path, list, size, XATTR_NOFOLLOW);
+	if (preserve_hfs_compression)
+		xattr_options |= XATTR_SHOWCOMPRESSION;
+	return listxattr(path, list, size, xattr_options);
 }
 
 #elif HAVE_FREEBSD_XATTRS
diff --git a/main.c b/main.c
--- a/main.c
+++ b/main.c
@@ -34,6 +34,10 @@
 #ifdef SUPPORT_FORCE_CHANGE
 #include <sys/sysctl.h>
 #endif
+#ifdef SUPPORT_HFS_COMPRESSION
+#include <sys/attr.h> /* For getattrlist() */
+#include <sys/mount.h> /* For statfs() */
+#endif
 
 extern int dry_run;
 extern int list_only;
@@ -60,6 +64,7 @@ extern int copy_dirlinks;
 extern int copy_unsafe_links;
 extern int keep_dirlinks;
 extern int preserve_hard_links;
+extern int preserve_hfs_compression;
 extern int protocol_version;
 extern int mkpath_dest_arg;
 extern int file_total;
@@ -117,6 +122,7 @@ int daemon_connection = 0; /* 0 = no daemon, 1 = daemon via remote shell, -1 = d
 mode_t orig_umask = 0;
 int batch_gen_fd = -1;
 int sender_keeps_checksum = 0;
+int fs_supports_hfs_compression = 0;
 int raw_argc, cooked_argc;
 char **raw_argv, **cooked_argv;
 
@@ -671,6 +677,43 @@ static pid_t do_cmd(char *cmd, char *machine, char *user, char **remote_argv, in
 	return pid;
 }
 
+#ifdef SUPPORT_HFS_COMPRESSION
+static void hfs_receiver_check(void)
+{
+	struct statfs fsb;
+	struct attrlist attrs;
+	struct {
+		int32_t len;
+		vol_capabilities_set_t caps;
+	} attrData;
+
+	if (preserve_hfs_compression != 1)
+		return; /* Nothing to check if --hfs-compression option isn't enabled. */
+
+	if (statfs(".", &fsb) < 0) {
+		rsyserr(FERROR, errno, "statfs %s failed", curr_dir);
+		exit_cleanup(RERR_FILESELECT);
+	}
+
+	bzero(&attrs, sizeof attrs);
+	attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
+	attrs.volattr = ATTR_VOL_CAPABILITIES;
+
+	bzero(&attrData, sizeof attrData);
+	attrData.len = sizeof attrData;
+
+	if (getattrlist(fsb.f_mntonname, &attrs, &attrData, sizeof attrData, 0) < 0) {
+		rsyserr(FERROR, errno, "getattrlist %s failed", curr_dir);
+		exit_cleanup(RERR_FILESELECT);
+	}
+
+	if (!(attrData.caps[VOL_CAPABILITIES_FORMAT] & VOL_CAP_FMT_DECMPFS_COMPRESSION)) {
+		rprintf(FERROR, "The destination filesystem does not support HFS+ compression.\n");
+		exit_cleanup(RERR_UNSUPPORTED);
+	}
+}
+#endif
+
 /* The receiving side operates in one of two modes:
  *
  * 1. it receives any number of files into a destination directory,
@@ -748,6 +791,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
 				exit_cleanup(RERR_FILESELECT);
 			}
 			filesystem_dev = st.st_dev; /* ensures --force works right w/-x */
+#ifdef SUPPORT_HFS_COMPRESSION
+			hfs_receiver_check();
+#endif
 			return NULL;
 		}
 		if (file_total > 1) {
@@ -805,7 +851,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
 				full_fname(dest_path));
 			exit_cleanup(RERR_FILESELECT);
 		}
-
+#ifdef SUPPORT_HFS_COMPRESSION
+		hfs_receiver_check();
+#endif
 		return NULL;
 	}
 
@@ -825,6 +873,9 @@ static char *get_local_name(struct file_list *flist, char *dest_path)
 			full_fname(dest_path));
 		exit_cleanup(RERR_FILESELECT);
 	}
+#ifdef SUPPORT_HFS_COMPRESSION
+	hfs_receiver_check();
+#endif
 	*cp = '/';
 
 	return cp + 1;
diff --git a/options.c b/options.c
--- a/options.c
+++ b/options.c
@@ -52,6 +52,7 @@ int preserve_links = 0;
 int preserve_hard_links = 0;
 int preserve_acls = 0;
 int preserve_xattrs = 0;
+int preserve_hfs_compression = 0;
 int preserve_perms = 0;
 int preserve_fileflags = 0;
 int preserve_executability = 0;
@@ -721,6 +722,10 @@ static struct poptOption long_options[] = {
   {"no-force-change",  0,  POPT_ARG_VAL,    &force_change, 0, 0, 0 },
   {"force-uchange",    0,  POPT_ARG_VAL,    &force_change, USR_IMMUTABLE, 0, 0 },
   {"force-schange",    0,  POPT_ARG_VAL,    &force_change, SYS_IMMUTABLE, 0, 0 },
+  {"hfs-compression",  0,  POPT_ARG_VAL,    &preserve_hfs_compression, 1, 0, 0 },
+  {"no-hfs-compression",0, POPT_ARG_VAL,    &preserve_hfs_compression, 0, 0, 0 },
+  {"protect-decmpfs",  0,  POPT_ARG_VAL,    &preserve_hfs_compression, 2, 0, 0 },
+  {"no-protect-decmpfs",0, POPT_ARG_VAL,    &preserve_hfs_compression, 0, 0, 0 },
   {"ignore-errors",    0,  POPT_ARG_VAL,    &ignore_errors, 1, 0, 0 },
   {"no-ignore-errors", 0,  POPT_ARG_VAL,    &ignore_errors, 0, 0, 0 },
   {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
@@ -1016,6 +1021,10 @@ static void set_refuse_options(void)
 	parse_one_refuse_match(0, "force-uchange", list_end);
 	parse_one_refuse_match(0, "force-schange", list_end);
 #endif
+#ifndef SUPPORT_HFS_COMPRESSION
+	parse_one_refuse_match(0, "hfs-compression", list_end);
+	parse_one_refuse_match(0, "protect-decmpfs", list_end);
+#endif
 
 	/* Now we use the descrip values to actually mark the options for refusal. */
 	for (op = long_options; op != list_end; op++) {
@@ -2070,6 +2079,15 @@ int parse_arguments(int *argc_p, const char ***argv_p)
 	}
 #endif
 
+#ifdef SUPPORT_HFS_COMPRESSION
+	if (preserve_hfs_compression) {
+		if (!preserve_xattrs)
+			preserve_xattrs = 1;
+		if (!preserve_fileflags)
+			preserve_fileflags = 1;
+	}
+#endif
+
 	if (write_batch && read_batch) {
 		snprintf(err_buf, sizeof err_buf,
 			"--write-batch and --read-batch can not be used together\n");
@@ -2667,6 +2685,11 @@ void server_options(char **args, int *argc_p)
 	if (preserve_fileflags)
 		args[ac++] = "--fileflags";
 
+#ifdef SUPPORT_HFS_COMPRESSION
+	if (preserve_hfs_compression)
+		args[ac++] = preserve_hfs_compression == 1 ? "--hfs-compression" : "--protect-decmpfs";
+#endif
+
 	if (do_compression && do_compression_level != CLVL_NOT_SPECIFIED) {
 		if (asprintf(&arg, "--compress-level=%d", do_compression_level) < 0)
 			goto oom;
diff --git a/rsync.1.md b/rsync.1.md
--- a/rsync.1.md
+++ b/rsync.1.md
@@ -366,6 +366,8 @@ detailed description below for a complete description.
 --chmod=CHMOD            affect file and/or directory permissions
 --acls, -A               preserve ACLs (implies --perms)
 --xattrs, -X             preserve extended attributes
+--hfs-compression        preserve HFS compression if supported
+--protect-decmpfs        preserve HFS compression as xattrs
 --owner, -o              preserve owner (super-user only)
 --group, -g              preserve group
 --devices                preserve device files (super-user only)
@@ -1291,6 +1293,47 @@ your home directory (remove the '=' for that).
     receiving side.  It does not try to affect user flags.  This option
     overrides `--force-change` and `--force-uchange`.
 
+0.  `--hfs-compression`
+
+    This option causes rsync to preserve HFS+ compression if the destination
+    filesystem supports it.  If the destination does not support it, rsync will
+    exit with an error.
+
+    Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
+    is compressed has no data in its data fork. Rather, the compressed data is
+    stored in an extended attribute named com.apple.decmpfs and a file flag is
+    set to indicate that the file is compressed (UF_COMPRESSED). HFS+
+    decompresses this data "on-the-fly" and presents it to the operating system
+    as a normal file.  Normal attempts to copy compressed files (e.g. in the
+    Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
+    remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
+    extended attribute. This option will preserve the data in the
+    com.apple.decmpfs extended attribute and ignore the synthesized data in the
+    file contents.
+
+    This option implies both `--fileflags` and (--xattrs).
+
+0.  `--protect-decmpfs`
+
+    The com.apple.decmpfs extended attribute is hidden by default from list/get
+    xattr calls, therefore normal attempts to copy compressed files will
+    functionally decompress those files. While this is desirable behavior when
+    copying files to filesystems that do not support HFS+ compression, it has
+    serious performance and capacity impacts when backing up or restoring the
+    Mac OS X filesystem.
+
+    This option will transfer the com.apple.decmpfs extended attribute
+    regardless of support on the destination. If a source file is compressed
+    and an existing file on the destination is not compressed, the data fork of
+    the destination file will be truncated and the com.apple.decmpfs xattr will
+    be transferred instead. Note that compressed files will not be readable to
+    the operating system of the destination if that operating system does not
+    support HFS+ compression. Once restored (with or without this option) to an
+    operating system that supports HFS+ compression, however, these files will
+    be accessible as usual.
+
+    This option implies `--fileflags` and `--xattrs`.
+
 0.  `--chmod=CHMOD`
 
     This option tells rsync to apply one or more comma-separated "chmod" modes
diff --git a/rsync.c b/rsync.c
--- a/rsync.c
+++ b/rsync.c
@@ -606,8 +606,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
 #ifdef SUPPORT_XATTRS
 	if (am_root < 0)
 		set_stat_xattr(fname, file, new_mode);
-	if (preserve_xattrs && fnamecmp)
+	if (preserve_xattrs && fnamecmp) {
+		uint32 tmpflags = sxp->st.st_flags;
+		sxp->st.st_flags = F_FFLAGS(file); /* set_xattr() needs to check UF_COMPRESSED */
 		set_xattr(fname, file, fnamecmp, sxp);
+		sxp->st.st_flags = tmpflags;
+		if (S_ISDIR(sxp->st.st_mode))
+			link_stat(fname, &sx2.st, 0);
+	}
 #endif
 
 	if (!preserve_times
@@ -621,7 +627,11 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
 	/* Don't set the creation date on the root folder of an HFS+ volume. */
 	if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
 		flags |= ATTRS_SKIP_CRTIME;
-	if (!(flags & ATTRS_SKIP_MTIME) && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
+	if (!(flags & ATTRS_SKIP_MTIME)
+#ifdef SUPPORT_HFS_COMPRESSION
+	 && !(sxp->st.st_flags & UF_COMPRESSED) /* setting this alters mtime, so defer to after set_fileflags */
+#endif
+	 && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
 		sx2.st.st_mtime = file->modtime;
 #ifdef ST_MTIME_NSEC
 		sx2.st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
@@ -698,6 +708,16 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
 		 && !set_fileflags(fname, fileflags))
 			goto cleanup;
 		updated = 1;
+#ifdef SUPPORT_HFS_COMPRESSION
+		int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), new_mode, fileflags);
+		if (ret < 0) {
+			rsyserr(FERROR_XFER, errno, "failed to set times on %s",
+				full_fname(fname));
+			goto cleanup;
+		}
+		if (ret != 0)
+			file->flags |= FLAG_TIME_FAILED;
+#endif
 	}
 #endif
 
diff --git a/rsync.h b/rsync.h
--- a/rsync.h
+++ b/rsync.h
@@ -572,6 +572,17 @@ typedef unsigned int size_t;
 #endif
 #endif
 
+#ifndef UF_COMPRESSED
+#define UF_COMPRESSED 0x00000020
+#endif
+#ifndef VOL_CAP_FMT_DECMPFS_COMPRESSION
+#define VOL_CAP_FMT_DECMPFS_COMPRESSION 0x00010000
+#endif
+
+#if defined SUPPORT_XATTRS && defined SUPPORT_FILEFLAGS
+#define SUPPORT_HFS_COMPRESSION 1
+#endif
+
 #ifndef __APPLE__ /* Do we need a configure check for this? */
 #define SUPPORT_ATIMES 1
 #endif
diff --git a/t_stub.c b/t_stub.c
--- a/t_stub.c
+++ b/t_stub.c
@@ -34,6 +34,7 @@ int preserve_times = 0;
 int preserve_xattrs = 0;
 int preserve_perms = 0;
 int preserve_executability = 0;
+int preserve_hfs_compression = 0;
 int open_noatime = 0;
 size_t max_alloc = 0; /* max_alloc is needed when combined with util2.o */
 char *partial_dir;
diff --git a/xattrs.c b/xattrs.c
--- a/xattrs.c
+++ b/xattrs.c
@@ -33,6 +33,7 @@ extern int am_generator;
 extern int read_only;
 extern int list_only;
 extern int preserve_xattrs;
+extern int preserve_hfs_compression;
 extern int preserve_links;
 extern int preserve_devices;
 extern int preserve_specials;
@@ -42,6 +43,10 @@ extern int saw_xattr_filter;
 #define RSYNC_XAL_INITIAL 5
 #define RSYNC_XAL_LIST_INITIAL 100
 
+#define GXD_NO_MISSING_ERROR (1<<0)
+#define GXD_OMIT_COMPRESSED (1<<1)
+#define GXD_FILE_IS_COMPRESSED (1<<2)
+
 #define MAX_FULL_DATUM 32
 
 #define HAS_PREFIX(str, prfx) (*(str) == *(prfx) && strncmp(str, prfx, sizeof (prfx) - 1) == 0)
@@ -73,6 +78,17 @@ extern int saw_xattr_filter;
 #define XDEF_ACL_SUFFIX "dacl"
 #define XDEF_ACL_ATTR RSYNC_PREFIX "%" XDEF_ACL_SUFFIX
 
+#define APPLE_PREFIX "com.apple."
+#define APLPRE_LEN ((int)sizeof APPLE_PREFIX - 1)
+#define DECMPFS_SUFFIX "decmpfs"
+#define RESOURCEFORK_SUFFIX "ResourceFork"
+
+#define UNREAD_DATA ((char *)1)
+
+#if MAX_DIGEST_LEN < SIZEOF_TIME_T
+#error MAX_DIGEST_LEN is too small to hold an mtime
+#endif
+
 typedef struct {
 	char *datum, *name;
 	size_t datum_len, name_len;
@@ -180,7 +196,7 @@ static ssize_t get_xattr_names(const char *fname)
 /* On entry, the *len_ptr parameter contains the size of the extra space we
  * should allocate when we create a buffer for the data.  On exit, it contains
  * the length of the datum. */
-static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, int no_missing_error)
+static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr, int flags)
 {
 	size_t datum_len = sys_lgetxattr(fname, name, NULL, 0);
 	size_t extra_len = *len_ptr;
@@ -189,7 +205,7 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
 	*len_ptr = datum_len;
 
 	if (datum_len == (size_t)-1) {
-		if (errno == ENOTSUP || no_missing_error)
+		if (errno == ENOTSUP || flags & GXD_NO_MISSING_ERROR)
 			return NULL;
 		rsyserr(FERROR_XFER, errno,
 			"get_xattr_data: lgetxattr(%s,\"%s\",0) failed",
@@ -197,6 +213,15 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
 		return NULL;
 	}
 
+	if (flags & GXD_OMIT_COMPRESSED && datum_len > MAX_FULL_DATUM
+	 && HAS_PREFIX(name, APPLE_PREFIX)
+	 && (strcmp(name+APLPRE_LEN, DECMPFS_SUFFIX) == 0
+	  || (flags & GXD_FILE_IS_COMPRESSED && strcmp(name+APLPRE_LEN, RESOURCEFORK_SUFFIX) == 0))) {
+		/* If we are omitting compress-file-related data, we don't want to
+		 * actually read this data. */
+		return UNREAD_DATA;
+	}
+
 	if (!datum_len && !extra_len)
 		extra_len = 1; /* request non-zero amount of memory */
 	if (SIZE_MAX - datum_len < extra_len)
@@ -224,7 +249,31 @@ static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr
 	return ptr;
 }
 
-static int rsync_xal_get(const char *fname, item_list *xalp)
+static void checksum_xattr_data(char *sum, const char *datum, size_t datum_len, stat_x *sxp)
+{
+	if (datum == UNREAD_DATA) {
+		/* For abbreviated compressed data, we store the file's mtime as the checksum. */
+		SIVAL(sum, 0, sxp->st.st_mtime);
+#if SIZEOF_TIME_T > 4
+		SIVAL(sum, 4, sxp->st.st_mtime >> 32);
+#if MAX_DIGEST_LEN > 8
+		memset(sum + 8, 0, MAX_DIGEST_LEN - 8);
+#endif
+#else
+#if MAX_DIGEST_LEN > 4
+		memset(sum + 4, 0, MAX_DIGEST_LEN - 4);
+#endif
+#endif
+	} else {
+		sum_init(-1, checksum_seed);
+		sum_update(datum, datum_len);
+		sum_end(sum);
+	}
+}
+
+$$$ERROR$$$ the old patch needs reworking since rsync_xal_get() has totally changed!
+
+static int rsync_xal_get(const char *fname, stat_x *sxp)
 {
 	ssize_t list_len, name_len;
 	size_t datum_len, name_offset;
@@ -233,7 +282,8 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
 	int user_only = am_sender ? 0 : !am_root;
 #endif
 	rsync_xa *rxa;
-	int count;
+	int count, flags;
+	item_list *xalp = sxp->xattr;
 
 	/* This puts the name list into the "namebuf" buffer. */
 	if ((list_len = get_xattr_names(fname)) < 0)
@@ -264,11 +314,15 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
 		}
 
 		datum_len = name_len; /* Pass extra size to get_xattr_data() */
-		if (!(ptr = get_xattr_data(fname, name, &datum_len, 0)))
+		flags = GXD_OMIT_COMPRESSED;
+		if (preserve_hfs_compression && sxp->st.st_flags & UF_COMPRESSED)
+			flags |= GXD_FILE_IS_COMPRESSED;
+		if (!(ptr = get_xattr_data(fname, name, &datum_len, flags)))
 			return -1;
 
 		if (datum_len > MAX_FULL_DATUM) {
 			/* For large datums, we store a flag and a checksum. */
+			char *datum = ptr;
 			name_offset = 1 + MAX_DIGEST_LEN;
 			sum_init(-1, checksum_seed);
 			sum_update(ptr, datum_len);
@@ -276,7 +330,9 @@ static int rsync_xal_get(const char *fname, item_list *xalp)
 
 			ptr = new_array(char, name_offset + name_len);
 			*ptr = XSTATE_ABBREV;
-			sum_end(ptr + 1);
+			checksum_xattr_data(ptr+1, datum, datum_len, sxp);
+			if (datum != UNREAD_DATA)
+				free(datum);
 		} else
 			name_offset = datum_len;
 
@@ -322,7 +378,7 @@ int get_xattr(const char *fname, stat_x *sxp)
 	} else if (IS_MISSING_FILE(sxp->st))
 		return 0;
 
-	if (rsync_xal_get(fname, sxp->xattr) < 0) {
+	if (rsync_xal_get(fname, sxp) < 0) {
 		free_xattr(sxp);
 		return -1;
 	}
@@ -359,6 +415,8 @@ int copy_xattrs(const char *source, const char *dest)
 		datum_len = 0;
 		if (!(ptr = get_xattr_data(source, name, &datum_len, 0)))
 			return -1;
+		if (ptr == UNREAD_DATA)
+			continue; /* XXX Is this right? */
 		if (sys_lsetxattr(dest, name, ptr, datum_len) < 0) {
 			int save_errno = errno ? errno : EINVAL;
 			rsyserr(FERROR_XFER, errno,
@@ -449,6 +507,7 @@ static int find_matching_xattr(const item_list *xalp)
 	}
 
 	return -1;
+#endif
 }
 
 /* Store *xalp on the end of rsync_xal_l */
@@ -663,11 +722,13 @@ void send_xattr_request(const char *fname, struct file_struct *file, int f_out)
 
 			/* Re-read the long datum. */
 			if (!(ptr = get_xattr_data(fname, rxa->name, &len, 0))) {
-				rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname);
+				if (errno != ENOTSUP && errno != ENOATTR)
+					rprintf(FERROR_XFER, "failed to re-read xattr %s for %s\n", rxa->name, fname);
 				write_varint(f_out, 0);
 				continue;
 			}
 
+			assert(ptr != UNREAD_DATA);
 			write_varint(f_out, len); /* length might have changed! */
 			write_bigbuf(f_out, ptr, len);
 			free(ptr);
@@ -948,7 +1009,7 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 	int user_only = am_root <= 0;
 #endif
 	size_t name_len;
-	int ret = 0;
+	int flags, ret = 0;
 
 	/* This puts the current name list into the "namebuf" buffer. */
 	if ((list_len = get_xattr_names(fname)) < 0)
@@ -961,7 +1022,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 			int sum_len;
 			/* See if the fnamecmp version is identical. */
 			len = name_len = rxas[i].name_len;
-			if ((ptr = get_xattr_data(fnamecmp, name, &len, 1)) == NULL) {
+			flags = GXD_OMIT_COMPRESSED | GXD_NO_MISSING_ERROR;
+			if (preserve_hfs_compression && sxp->st.st_flags & UF_COMPRESSED)
+				flags |= GXD_FILE_IS_COMPRESSED;
+			if ((ptr = get_xattr_data(fnamecmp, name, &len, flags)) == NULL) {
 			  still_abbrev:
 				if (am_generator)
 					continue;
@@ -970,6 +1034,8 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 				ret = -1;
 				continue;
 			}
+			if (ptr == UNREAD_DATA)
+				continue; /* XXX Is this right? */
 			if (len != rxas[i].datum_len) {
 				free(ptr);
 				goto still_abbrev;
@@ -1047,6 +1113,10 @@ static int rsync_xal_set(const char *fname, item_list *xalp,
 		}
 	}
 
+#ifdef HAVE_OSX_XATTRS
+	rsync_xal_free(xalp); /* Free this because we aren't using find_matching_xattr(). */
+#endif
+
 	return ret;
 }
 
@@ -1095,7 +1165,7 @@ char *get_xattr_acl(const char *fname, int is_access_acl, size_t *len_p)
 {
 	const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR;
 	*len_p = 0; /* no extra data alloc needed from get_xattr_data() */
-	return get_xattr_data(fname, name, len_p, 1);
+	return get_xattr_data(fname, name, len_p, GXD_NO_MISSING_ERROR);
 }
 
 int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len)
@@ -1238,11 +1308,33 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
 	return 0;
 }
 
+#ifdef SUPPORT_HFS_COMPRESSION
+static inline void hfs_compress_tweaks(STRUCT_STAT *fst)
+{
+	if (fst->st_flags & UF_COMPRESSED) {
+		if (preserve_hfs_compression) {
+			/* We're sending the compression xattr, not the decompressed data fork.
+			 * Setting rsync's idea of the file size to 0 effectively prevents the
+			 * transfer of the data fork. */
+			fst->st_size = 0;
+		} else {
+			/* If the sender's filesystem supports compression, then we'll be able
+			 * to send the decompressed data fork and the decmpfs xattr will be
+			 * hidden (not sent). As such, we need to strip the compression flag. */
+			fst->st_flags &= ~UF_COMPRESSED;
+		}
+	}
+}
+#endif
+
 int x_stat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
 {
 	int ret = do_stat(fname, fst);
 	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
 		xst->st_mode = 0;
+#ifdef SUPPORT_HFS_COMPRESSION
+	hfs_compress_tweaks(fst);
+#endif
 	return ret;
 }
 
@@ -1251,6 +1343,9 @@ int x_lstat(const char *fname, STRUCT_STAT *fst, STRUCT_STAT *xst)
 	int ret = do_lstat(fname, fst);
 	if ((ret < 0 || get_stat_xattr(fname, -1, fst, xst) < 0) && xst)
 		xst->st_mode = 0;
+#ifdef SUPPORT_HFS_COMPRESSION
+	hfs_compress_tweaks(fst);
+#endif
 	return ret;
 }
 
@@ -1259,6 +1354,9 @@ int x_fstat(int fd, STRUCT_STAT *fst, STRUCT_STAT *xst)
 	int ret = do_fstat(fd, fst);
 	if ((ret < 0 || get_stat_xattr(NULL, fd, fst, xst) < 0) && xst)
 		xst->st_mode = 0;
+#ifdef SUPPORT_HFS_COMPRESSION
+	hfs_compress_tweaks(fst);
+#endif
 	return ret;
 }
 
diff -Nurp a/rsync.1 b/rsync.1
--- a/rsync.1
+++ b/rsync.1
@@ -442,6 +442,8 @@ detailed description below for a complet
 --chmod=CHMOD            affect file and/or directory permissions
 --acls, -A               preserve ACLs (implies --perms)
 --xattrs, -X             preserve extended attributes
+--hfs-compression        preserve HFS compression if supported
+--protect-decmpfs        preserve HFS compression as xattrs
 --owner, -o              preserve owner (super-user only)
 --group, -g              preserve group
 --devices                preserve device files (super-user only)
@@ -1373,6 +1375,43 @@ side.  It does not try to affect system
 flags on files and directories that are being updated or deleted on the
 receiving side.  It does not try to affect user flags.  This option
 overrides \fB\-\-force-change\fP and \fB\-\-force-uchange\fP."
+.IP "\fB\-\-hfs-compression\fP"
+This option causes rsync to preserve HFS+ compression if the destination
+filesystem supports it.  If the destination does not support it, rsync will
+exit with an error.
+.IP
+Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
+is compressed has no data in its data fork. Rather, the compressed data is
+stored in an extended attribute named com.apple.decmpfs and a file flag is
+set to indicate that the file is compressed (UF_COMPRESSED). HFS+
+decompresses this data "on-the-fly" and presents it to the operating system
+as a normal file.  Normal attempts to copy compressed files (e.g. in the
+Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
+remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
+extended attribute. This option will preserve the data in the
+com.apple.decmpfs extended attribute and ignore the synthesized data in the
+file contents.
+.IP
+This option implies both \fB\-\-fileflags\fP and (\-\-xattrs).
+.IP "\fB\-\-protect-decmpfs\fP"
+The com.apple.decmpfs extended attribute is hidden by default from list/get
+xattr calls, therefore normal attempts to copy compressed files will
+functionally decompress those files. While this is desirable behavior when
+copying files to filesystems that do not support HFS+ compression, it has
+serious performance and capacity impacts when backing up or restoring the
+Mac OS X filesystem.
+.IP
+This option will transfer the com.apple.decmpfs extended attribute
+regardless of support on the destination. If a source file is compressed
+and an existing file on the destination is not compressed, the data fork of
+the destination file will be truncated and the com.apple.decmpfs xattr will
+be transferred instead. Note that compressed files will not be readable to
+the operating system of the destination if that operating system does not
+support HFS+ compression. Once restored (with or without this option) to an
+operating system that supports HFS+ compression, however, these files will
+be accessible as usual.
+.IP
+This option implies \fB\-\-fileflags\fP and \fB\-\-xattrs\fP.
 .IP "\fB\-\-chmod=CHMOD\fP"
 This option tells rsync to apply one or more comma-separated "chmod" modes
 to the permission of the files in the transfer.  The resulting value is
diff -Nurp a/rsync.1.html b/rsync.1.html
--- a/rsync.1.html
+++ b/rsync.1.html
@@ -357,6 +357,8 @@ detailed description below for a complet
 --chmod=CHMOD            affect file and/or directory permissions
 --acls, -A               preserve ACLs (implies --perms)
 --xattrs, -X             preserve extended attributes
+--hfs-compression        preserve HFS compression if supported
+--protect-decmpfs        preserve HFS compression as xattrs
 --owner, -o              preserve owner (super-user only)
 --group, -g              preserve group
 --devices                preserve device files (super-user only)
@@ -1252,6 +1254,43 @@ receiving side.  It does not try to affe
 overrides <code>--force-change</code> and <code>--force-uchange</code>.</dt><dd>
 </dd>
 
+<dt><code>--hfs-compression</code></dt><dd>
+<p>This option causes rsync to preserve HFS+ compression if the destination
+filesystem supports it.  If the destination does not support it, rsync will
+exit with an error.</p>
+<p>Filesystem compression was introduced to HFS+ in Mac OS 10.6. A file that
+is compressed has no data in its data fork. Rather, the compressed data is
+stored in an extended attribute named com.apple.decmpfs and a file flag is
+set to indicate that the file is compressed (UF_COMPRESSED). HFS+
+decompresses this data &quot;on-the-fly&quot; and presents it to the operating system
+as a normal file.  Normal attempts to copy compressed files (e.g. in the
+Finder, via cp, ditto, etc.) will copy the file's decompressed contents,
+remove the UF_COMPRESSED file flag, and discard the com.apple.decmpfs
+extended attribute. This option will preserve the data in the
+com.apple.decmpfs extended attribute and ignore the synthesized data in the
+file contents.</p>
+<p>This option implies both <code>--fileflags</code> and (-&#8288;-&#8288;xattrs).</p>
+</dd>
+
+<dt><code>--protect-decmpfs</code></dt><dd>
+<p>The com.apple.decmpfs extended attribute is hidden by default from list/get
+xattr calls, therefore normal attempts to copy compressed files will
+functionally decompress those files. While this is desirable behavior when
+copying files to filesystems that do not support HFS+ compression, it has
+serious performance and capacity impacts when backing up or restoring the
+Mac OS X filesystem.</p>
+<p>This option will transfer the com.apple.decmpfs extended attribute
+regardless of support on the destination. If a source file is compressed
+and an existing file on the destination is not compressed, the data fork of
+the destination file will be truncated and the com.apple.decmpfs xattr will
+be transferred instead. Note that compressed files will not be readable to
+the operating system of the destination if that operating system does not
+support HFS+ compression. Once restored (with or without this option) to an
+operating system that supports HFS+ compression, however, these files will
+be accessible as usual.</p>
+<p>This option implies <code>--fileflags</code> and <code>--xattrs</code>.</p>
+</dd>
+
 <dt><code>--chmod=CHMOD</code></dt><dd>
 <p>This option tells rsync to apply one or more comma-separated &quot;chmod&quot; modes
 to the permission of the files in the transfer.  The resulting value is

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>