Ticket #35514: rsync-hfscompression.diff

File rsync-hfscompression.diff, 31.9 KB (added by jimjag (Jim Jagielski), 12 years ago)
  • Portfile

    diff --git a/a/Portfile b/b/Portfile
    index 367c049..247c061 100644
    a/a b/b depends_lib port:popt port:libiconv 
    3030# these come from http://rsync.samba.org/ftp/rsync/rsync-patches-3.0.9.tar.gz
    3131# and need to be updated with each release
    3232patchfiles          patch-fileflags.diff \
    33                     patch-crtimes.diff
     33                    patch-crtimes.diff \
     34                    patch-hfs-compression.diff \
     35                    patch-hfs-compression-options.diff
    3436patch.pre_args      -p1
    3537
    3638configure.args      --with-rsyncd-conf=${prefix}/etc/rsyncd.conf
  • new file files/patch-hfs-compression-options.diff

    diff --git a/b/files/patch-hfs-compression-options.diff b/b/files/patch-hfs-compression-options.diff
    new file mode 100644
    index 0000000..55198b4
    - +  
     1This patch adds support for HFS+ compression status to be
     2shown during options display.
     3
     4To use this patch, run these commands for a successful build:
     5
     6    patch -p1 <patches/fileflags.diff
     7    patch -p1 <patches/crtimes.diff
     8    patch -p1 <patches/hfs-compression.diff
     9    patch -p1 <patches/hfs-compression-options.diff
     10    ./prepare-source
     11    ./configure
     12    make
     13
     14TODO:
     15 - Should rsync try to treat the compressed data as file data and use the
     16   rsync algorithm on the data transfer?
     17
     18diff --git a/options.c b/options.c
     19--- a/options.c
     20+++ b/options.c
     21@@ -228,6 +228,7 @@
     22        char const *iconv = "no ";
     23        char const *ipv6 = "no ";
     24        char const *fileflags = "no ";
     25+       char const *hfscomp = "no";
     26        STRUCT_STAT *dumstat;
     27 
     28 #if SUBPROTOCOL_VERSION != 0
     29@@ -264,6 +265,9 @@
     30 #ifdef SUPPORT_FILEFLAGS
     31        fileflags = "";
     32 #endif
     33+#ifdef SUPPORT_HFS_COMPRESSION
     34+       hfscomp = "";
     35+#endif
     36 
     37        rprintf(f, "%s  version %s  protocol version %d%s\n",
     38                RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
     39@@ -277,8 +281,10 @@
     40                (int)(sizeof (int64) * 8));
     41        rprintf(f, "    %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
     42                got_socketpair, hardlinks, links, ipv6, have_inplace);
     43-       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
     44+       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags,\n",
     45                have_inplace, acls, xattrs, iconv, symtimes, fileflags);
     46+       rprintf(f, "    %sHFS-compression\n",
     47+               hfscomp);
     48 
     49 #ifdef MAINTAINER_MODE
     50        rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
  • new file files/patch-hfs-compression.diff

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