This patch adds basic support for extended attributes.  It works, but there
are some things that still needs to be improved (see TODO list below).

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

    patch -p1 <patches/acls.diff
    patch -p1 <patches/xattrs.diff
    ./prepare-source
    ./configure --enable-acl-support --enable-xattr-support
    make

Alternately, if you don't want ACL support, configure it this way:

    ./configure --enable-xattr-support

TODO:

 - This patch needs to more efficiently handle large xattrs, especially when
   they're unchanged.

 - Extraneous xattr values need to be removed from files that are not being
   recreated.

 - We need to affect the itemized output to know when xattrs are being updated.

 - We need to affect the --link-dest option to avoid hard-linking two files
   that differ in their xattrs (when --xattrs was specified).

--- old/Makefile.in
+++ new/Makefile.in
@@ -28,13 +28,13 @@ VERSION=@VERSION@
 
 HEADERS=byteorder.h config.h errcode.h proto.h rsync.h smb_acls.h lib/pool_alloc.h
 LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o \
-	lib/permstring.o lib/pool_alloc.o lib/sysacls.o @LIBOBJS@
+	lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattr.o @LIBOBJS@
 ZLIBOBJ=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
 	zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
 OBJS1=rsync.o generator.o receiver.o cleanup.o sender.o exclude.o util.o \
 	main.o checksum.o match.o syscall.o log.o backup.o
 OBJS2=options.o flist.o io.o compat.o hlink.o token.o uidlist.o socket.o \
-	fileio.o batch.o clientname.o chmod.o acls.o
+	fileio.o batch.o clientname.o chmod.o acls.o xattr.o
 OBJS3=progress.o pipe.o
 DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o
 popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
--- old/acls.c
+++ new/acls.c
@@ -30,6 +30,7 @@ extern int read_only;
 extern int list_only;
 extern int orig_umask;
 extern int preserve_acls;
+extern int preserve_xattrs;
 extern unsigned int file_struct_len;
 
 /* === ACL structures === */
@@ -741,6 +742,10 @@ void receive_acl(struct file_struct *fil
 	type = SMB_ACL_TYPE_ACCESS;
 	racl_list = &access_acl_list;
 	ndx_ptr = (char*)file + file_struct_len;
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		ndx_ptr += 4;
+#endif
 	do {
 		char tag = read_byte(f);
 		int ndx;
@@ -800,6 +805,10 @@ void cache_acl(struct file_struct *file,
 	racl = sxp->acc_acl;
 	racl_list = &access_acl_list;
 	ndx_ptr = (char*)file + file_struct_len;
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		ndx_ptr += 4;
+#endif
 	do {
 		if (!racl)
 			ndx = -1;
@@ -920,6 +929,10 @@ int set_acl(const char *fname, const str
 
 	type = SMB_ACL_TYPE_ACCESS;
 	ndx_ptr = (char*)file + file_struct_len;
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		ndx_ptr += 4;
+#endif
 	do {
 		acl_duo *duo_item;
 		BOOL eq;
--- old/backup.c
+++ new/backup.c
@@ -30,6 +30,7 @@ extern char *backup_dir;
 
 extern int am_root;
 extern int preserve_acls;
+extern int preserve_xattrs;
 extern int preserve_devices;
 extern int preserve_specials;
 extern int preserve_links;
@@ -136,6 +137,9 @@ static int make_bak_dir(char *fullpath)
 #ifdef SUPPORT_ACLS
 				sx.acc_acl = sx.def_acl = NULL;
 #endif
+#ifdef SUPPORT_XATTRS
+				sx.xattr = NULL;
+#endif
 				if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS)))
 					continue;
 #ifdef SUPPORT_ACLS
@@ -144,6 +148,12 @@ static int make_bak_dir(char *fullpath)
 					cache_acl(file, &sx);
 				}
 #endif
+#ifdef SUPPORT_XATTRS
+				if (preserve_xattrs) {
+					get_xattr(rel, &sx);
+					cache_xattr(file, &sx);
+				}
+#endif
 				set_file_attrs(fullpath, file, NULL, 0);
 				free(file);
 			}
@@ -195,6 +205,9 @@ static int keep_backup(char *fname)
 #ifdef SUPPORT_ACLS
 	sx.acc_acl = sx.def_acl = NULL;
 #endif
+#ifdef SUPPORT_XATTRS
+	sx.xattr = NULL;
+#endif
 
 	if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS)))
 		return 1; /* the file could have disappeared */
@@ -208,6 +221,12 @@ static int keep_backup(char *fname)
 		cache_acl(file, &sx);
 	}
 #endif
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs) {
+		get_xattr(fname, &sx);
+		cache_xattr(file, &sx);
+	}
+#endif
 
 	/* Check to see if this is a device file, or link */
 	if ((am_root && preserve_devices && IS_DEVICE(file->mode))
--- old/configure.in
+++ new/configure.in
@@ -856,6 +856,40 @@ samba_cv_HAVE_ACL_GET_PERM_NP=yes,samba_
   AC_MSG_RESULT(no)
 )
 
+AC_CHECK_HEADERS(attr/xattr.h)
+AC_CHECK_HEADERS(sys/xattr.h)
+AC_CHECK_HEADERS(sys/extattr.h)
+AC_MSG_CHECKING(whether to support extended attributes)
+AC_ARG_ENABLE(xattr-support,
+AC_HELP_STRING([--enable-xattr-support], [Include extended attribute support (default=no)]),
+[ case "$enableval" in
+  yes)
+      case "$host_os" in
+      *linux*)
+            AC_MSG_RESULT(Using Linux xattrs)
+            AC_DEFINE(HAVE_LINUX_XATTRS, 1, [True if you have Linux xattrs])
+            ;;
+      darwin*)
+            AC_MSG_RESULT(Using OS X xattrs)
+            AC_DEFINE(HAVE_OSX_XATTRS, 1, [True if you have Mac OS X xattrs])
+            ;;
+      freebsd*)
+            AC_MSG_RESULT(Using FreeBSD extattrs)
+            AC_DEFINE(HAVE_FREEBSD_XATTRS, 1, [True if you have FreeBSD xattrs])
+            ;;
+      *)
+            AC_MSG_RESULT(Xattrs requested but not Linux or OS X.  Good luck...)
+            ;;
+      esac
+      ;;
+  *)
+      AC_MSG_RESULT(no)
+      AC_DEFINE(HAVE_NO_XATTRS, 1, [True if you don't have extended attributes])
+  esac ],
+  AC_MSG_RESULT(no)
+  AC_DEFINE(HAVE_NO_XATTRS, 1, [True if you don't have extended attributes])
+)
+
 AC_CONFIG_FILES([Makefile lib/dummy zlib/dummy popt/dummy shconfig])
 AC_OUTPUT
 
--- old/flist.c
+++ new/flist.c
@@ -41,6 +41,7 @@ extern int one_file_system;
 extern int copy_dirlinks;
 extern int keep_dirlinks;
 extern int preserve_acls;
+extern int preserve_xattrs;
 extern int preserve_links;
 extern int preserve_hard_links;
 extern int preserve_devices;
@@ -498,7 +499,7 @@ static struct file_struct *receive_file_
 	char thisname[MAXPATHLEN];
 	unsigned int l1 = 0, l2 = 0;
 	int alloc_len, basename_len, dirname_len, linkname_len, sum_len;
-#ifdef SUPPORT_ACLS
+#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
 	int xtra_len;
 #endif
 	OFF_T file_length;
@@ -610,10 +611,16 @@ static struct file_struct *receive_file_
 		xtra_len = (S_ISDIR(mode) ? 2 : 1) * 4;
 	else
 		xtra_len = 0;
+#elif defined SUPPORT_XATTRS
+	xtra_len = 0;
+#endif
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		xtra_len += 4;
 #endif
 
 	alloc_len = file_struct_len + dirname_len + basename_len
-#ifdef SUPPORT_ACLS
+#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
 		  + xtra_len
 #endif
 		  + linkname_len + sum_len;
@@ -622,7 +629,7 @@ static struct file_struct *receive_file_
 	file = (struct file_struct *)bp;
 	memset(bp, 0, file_struct_len);
 	bp += file_struct_len;
-#ifdef SUPPORT_ACLS
+#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
 	bp += xtra_len;
 #endif
 
@@ -723,6 +730,10 @@ static struct file_struct *receive_file_
 	if (preserve_acls)
 		receive_acl(file, f);
 #endif
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		receive_xattr(file, f );
+#endif
 
 	return file;
 }
@@ -974,7 +985,7 @@ static struct file_struct *send_file_nam
 					  unsigned short flags)
 {
 	struct file_struct *file;
-#ifdef SUPPORT_ACLS
+#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS
 	statx sx;
 #endif
 
@@ -994,6 +1005,13 @@ static struct file_struct *send_file_nam
 			return NULL;
 	}
 #endif
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs) {
+		sx.xattr = NULL;
+		if (get_xattr(fname, &sx) < 0)
+			return NULL;
+	}
+#endif
 
 	maybe_emit_filelist_progress(flist->count + flist_count_offset);
 
@@ -1006,11 +1024,19 @@ static struct file_struct *send_file_nam
 		if (preserve_acls)
 			send_acl(&sx, f);
 #endif
+#ifdef SUPPORT_XATTRS
+		if (preserve_xattrs)
+			send_xattr(&sx, f);
+#endif
 	} else {
 #ifdef SUPPORT_ACLS
 		if (preserve_acls)
 			free_acl(&sx);
 #endif
+#ifdef SUPPORT_XATTRS
+		if (preserve_xattrs)
+			free_xattr(&sx);
+#endif
 	}
 	return file;
 }
--- old/lib/sysxattr.c
+++ new/lib/sysxattr.c
@@ -0,0 +1,136 @@
+/*
+ * Extended attribute support for rsync.
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Written by Jay Fenlason.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "rsync.h"
+#include "sysxattr.h"
+
+#ifdef SUPPORT_XATTRS
+
+#if defined HAVE_LINUX_XATTRS
+
+ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
+{
+	return lgetxattr(path, name, value, size);
+}
+
+ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
+{
+	return fgetxattr(filedes, name, value, size);
+}
+
+int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
+{
+	return lsetxattr(path, name, value, size, 0);
+}
+
+int sys_lremovexattr(const char *path, const char *name)
+{
+	return lremovexattr(path, name);
+}
+
+ssize_t sys_llistxattr(const char *path, char *list, size_t size)
+{
+	return llistxattr(path, list, size);
+}
+
+#elif HAVE_OSX_XATTRS
+
+ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
+{
+	return getxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
+}
+
+ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
+{
+	return fgetxattr(filedes, name, value, size, 0, 0);
+}
+
+int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
+{
+	return setxattr(path, name, value, size, 0, XATTR_NOFOLLOW);
+}
+
+int sys_lremovexattr(const char *path, const char *name)
+{
+	return removexattr(path, name, XATTR_NOFOLLOW);
+}
+
+ssize_t sys_llistxattr(const char *path, char *list, size_t size)
+{
+	return listxattr(path, list, size, XATTR_NOFOLLOW);
+}
+
+#elif HAVE_FREEBSD_XATTRS
+
+ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size)
+{
+	return extattr_get_link(path, EXTATTR_NAMESPACE_USER, name, value, size);
+}
+
+ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size)
+{
+	return extattr_get_fd(filedes, EXTATTR_NAMESPACE_USER, name, value, size);
+}
+
+int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size)
+{
+	return extattr_set_link(path, EXTATTR_NAMESPACE_USER, name, value, size);
+}
+
+int sys_lremovexattr(const char *path, const char *name)
+{
+	return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name);
+}
+
+ssize_t sys_llistxattr(const char *path, char *list, size_t size)
+{
+	unsigned char keylen;
+	ssize_t off, len = extattr_list_link(path, EXTATTR_NAMESPACE_USER, list, size);
+
+	if (len <= 0 || (size_t)len > size)
+		return len;
+
+	/* FreeBSD puts a single-byte length before each string, with no '\0'
+	 * terminator.  We need to change this into a series of null-terminted
+	 * strings.  Since the size is the same, we can simply transform the
+	 * output in place. */
+	for (off = 0; off < len; ) {
+		keylen = ((unsigned char*)list)[off];
+		if (off + keylen >= len) {
+			/* Should be impossible, but kernel bugs happen! */
+			errno = EINVAL;
+			return -1;
+		}
+		memmove(list+off, list+off+1, keylen);
+		off += keylen;
+		list[off++] = '\0';
+	}
+
+	return len;
+}
+
+#else
+
+#error You need to create xattr compatibility functions.
+
+#endif
+
+#endif /* SUPPORT_XATTRS */
--- old/lib/sysxattr.h
+++ new/lib/sysxattr.h
@@ -0,0 +1,26 @@
+#ifdef SUPPORT_XATTRS
+
+#if defined HAVE_ATTR_XATTR_H
+#include <attr/xattr.h>
+#elif defined HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+#elif defined HAVE_SYS_EXTATTR_H
+#include <sys/extattr.h>
+#endif
+
+/* Linux 2.4 does not define this as a distinct errno value: */
+#ifndef ENOATTR
+#define ENOATTR ENODATA
+#endif
+
+ssize_t sys_lgetxattr(const char *path, const char *name, void *value, size_t size);
+ssize_t sys_fgetxattr(int filedes, const char *name, void *value, size_t size);
+int sys_lsetxattr(const char *path, const char *name, const void *value, size_t size);
+int sys_lremovexattr(const char *path, const char *name);
+ssize_t sys_llistxattr(const char *path, char *list, size_t size);
+
+#else
+
+/* No xattrs available */
+
+#endif
--- old/options.c
+++ new/options.c
@@ -48,6 +48,7 @@ int copy_links = 0;
 int preserve_links = 0;
 int preserve_hard_links = 0;
 int preserve_acls = 0;
+int preserve_xattrs = 0;
 int preserve_perms = 0;
 int preserve_executability = 0;
 int preserve_devices = 0;
@@ -201,6 +202,7 @@ static void print_rsync_version(enum log
 	char const *have_inplace = "no ";
 	char const *hardlinks = "no ";
 	char const *acls = "no ";
+	char const *xattrs = "no ";
 	char const *links = "no ";
 	char const *ipv6 = "no ";
 	STRUCT_STAT *dumstat;
@@ -220,7 +222,9 @@ static void print_rsync_version(enum log
 #ifdef SUPPORT_ACLS
 	acls = "";
 #endif
-
+#ifdef SUPPORT_XATTRS
+	xattrs = "";
+#endif
 #ifdef SUPPORT_LINKS
 	links = "";
 #endif
@@ -236,8 +240,8 @@ static void print_rsync_version(enum log
 	rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, %shard links, %ssymlinks,\n",
 		(int) (sizeof (OFF_T) * 8), got_socketpair, hardlinks, links);
 
-	rprintf(f, "              batchfiles, %sinplace, %sIPv6, %sACLs,\n",
-		have_inplace, ipv6, acls);
+	rprintf(f, "              batchfiles, %sinplace, %sIPv6, %sACLs, %sxattrs,\n",
+		have_inplace, ipv6, acls, xattrs);
 
 	/* Note that this field may not have type ino_t.  It depends
 	 * on the complicated interaction between largefile feature
@@ -289,7 +293,7 @@ void usage(enum logcode F)
   rprintf(F," -q, --quiet                 suppress non-error messages\n");
   rprintf(F,"     --no-motd               suppress daemon-mode MOTD (see manpage caveat)\n");
   rprintf(F," -c, --checksum              skip based on checksum, not mod-time & size\n");
-  rprintf(F," -a, --archive               archive mode; same as -rlptgoD (no -H, -A)\n");
+  rprintf(F," -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)\n");
   rprintf(F,"     --no-OPTION             turn off an implied OPTION (e.g. --no-D)\n");
   rprintf(F," -r, --recursive             recurse into directories\n");
   rprintf(F," -R, --relative              use relative path names\n");
@@ -314,6 +318,9 @@ void usage(enum logcode F)
 #ifdef SUPPORT_ACLS
   rprintf(F," -A, --acls                  preserve ACLs (implies --perms)\n");
 #endif
+#ifdef SUPPORT_XATTRS
+  rprintf(F," -X, --xattrs                preserve extended attributes (implies --perms)\n");
+#endif
   rprintf(F," -o, --owner                 preserve owner (super-user only)\n");
   rprintf(F," -g, --group                 preserve group\n");
   rprintf(F,"     --devices               preserve device files (super-user only)\n");
@@ -436,6 +443,9 @@ static struct poptOption long_options[] 
   {"acls",            'A', POPT_ARG_NONE,   0, 'A', 0, 0 },
   {"no-acls",          0,  POPT_ARG_VAL,    &preserve_acls, 0, 0, 0 },
   {"no-A",             0,  POPT_ARG_VAL,    &preserve_acls, 0, 0, 0 },
+  {"xattrs",          'X', POPT_ARG_NONE,   0, 'X', 0, 0 },
+  {"no-xattrs",        0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
+  {"no-X",             0,  POPT_ARG_VAL,    &preserve_xattrs, 0, 0, 0 },
   {"times",           't', POPT_ARG_VAL,    &preserve_times, 1, 0, 0 },
   {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
   {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
@@ -1117,6 +1127,17 @@ int parse_arguments(int *argc, const cha
 			return 0;
 #endif
 
+		case 'X':
+#ifdef SUPPORT_XATTRS
+			preserve_xattrs = 1;
+			preserve_perms = 1;
+			break;
+#else
+			snprintf(err_buf,sizeof(err_buf),
+				 "extended attributes are not supported on this %s\n",
+				 am_server ? "server" : "client");
+			return 0;
+#endif
 
 		default:
 			/* A large opt value means that set_refuse_options()
@@ -1563,6 +1584,10 @@ void server_options(char **args,int *arg
 	if (preserve_acls)
 		argstr[x++] = 'A';
 #endif
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs)
+		argstr[x++] = 'X';
+#endif
 	if (preserve_uid)
 		argstr[x++] = 'o';
 	if (preserve_gid)
--- old/rsync.c
+++ new/rsync.c
@@ -33,6 +33,7 @@
 extern int verbose;
 extern int dry_run;
 extern int preserve_acls;
+extern int preserve_xattrs;
 extern int preserve_perms;
 extern int preserve_executability;
 extern int preserve_times;
@@ -218,6 +219,10 @@ int set_file_attrs(char *fname, struct f
 	if (daemon_chmod_modes && !S_ISLNK(new_mode))
 		new_mode = tweak_mode(new_mode, daemon_chmod_modes);
 
+#ifdef SUPPORT_XATTRS
+	if (preserve_xattrs && set_xattr(fname, file, sxp) == 0)
+		updated = 1;
+#endif
 #ifdef SUPPORT_ACLS
 	/* It's OK to call set_acl() now, even for a dir, as the generator
 	 * will enable owner-writability using chmod, if necessary.
--- old/rsync.h
+++ new/rsync.h
@@ -500,6 +500,10 @@ struct idev {
 #define ACLS_NEED_MASK 1
 #endif
 
+#ifndef HAVE_NO_XATTRS
+#define SUPPORT_XATTRS 1
+#endif
+
 #define GID_NONE ((gid_t)-1)
 
 #define HL_CHECK_MASTER	0
@@ -694,6 +698,9 @@ typedef struct {
     struct rsync_acl *acc_acl; /* access ACL */
     struct rsync_acl *def_acl; /* default ACL */
 #endif
+#ifdef SUPPORT_XATTRS
+    item_list *xattr;
+#endif
 } statx;
 
 #define ACL_READY(sx) ((sx).acc_acl != NULL)
--- old/rsync.yo
+++ new/rsync.yo
@@ -301,7 +301,7 @@ to the detailed description below for a 
  -q, --quiet                 suppress non-error messages
      --no-motd               suppress daemon-mode MOTD (see caveat)
  -c, --checksum              skip based on checksum, not mod-time & size
- -a, --archive               archive mode; same as -rlptgoD (no -H, -A)
+ -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
      --no-OPTION             turn off an implied OPTION (e.g. --no-D)
  -r, --recursive             recurse into directories
  -R, --relative              use relative path names
@@ -324,6 +324,7 @@ to the detailed description below for a 
  -E, --executability         preserve executability
      --chmod=CHMOD           affect file and/or directory permissions
  -A, --acls                  preserve ACLs (implies -p) [non-standard]
+ -X, --xattrs                preserve extended attrs (implies -p) [n.s.]
  -o, --owner                 preserve owner (super-user only)
  -g, --group                 preserve group
      --devices               preserve device files (super-user only)
@@ -818,6 +819,11 @@ version makes it incompatible with sendi
 rsync unless you double the bf(--acls) option (e.g. bf(-AA)).  This
 doubling is not needed when pulling files from an older rsync.
 
+dit(bf(-X, --xattrs)) This option causes rsync to update the remote
+extended attributes to be the same as the local ones.  This will work
+only if the remote machine's rsync supports this option also. This is
+a non-standard option.
+
 dit(bf(--chmod)) This option tells rsync to apply one or more
 comma-separated "chmod" strings to the permission of the files in the
 transfer.  The resulting value is treated as though it was the permissions
--- old/xattr.c
+++ new/xattr.c
@@ -0,0 +1,393 @@
+/*
+ * Extended Attribute support for rsync.
+ * Written by Jay Fenlason, vaguely based on the ACLs patch.
+ *
+ * Copyright (C) 2004 Red Hat, Inc.
+ * Copyright (C) 2006 Wayne Davison
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "rsync.h"
+#include "lib/sysxattr.h"
+
+#ifdef SUPPORT_XATTRS
+
+extern int dry_run;
+extern int am_root;
+extern int read_only;
+extern int list_only;
+extern unsigned int file_struct_len;
+
+#define RSYNC_XAL_INITIAL 5
+#define RSYNC_XAL_LIST_INITIAL 100
+
+#define USER_PREFIX "user."
+#define UPRE_LEN (sizeof USER_PREFIX - 1)
+#define ROOT_PREFIX "root."
+#define RPRE_LEN (sizeof ROOT_PREFIX - 1)
+
+typedef struct {
+	char *name;
+	char *datum;
+	size_t name_len;
+	size_t datum_len;
+} rsync_xa;
+
+static size_t namebuf_len = 0;
+static char *namebuf = NULL;
+
+static item_list empty_xattr = EMPTY_ITEM_LIST;
+static item_list rsync_xal_l = EMPTY_ITEM_LIST;
+
+/* ------------------------------------------------------------------------- */
+
+static void rsync_xal_free(item_list *xalp)
+{
+	size_t i;
+	rsync_xa *rxas = xalp->items;
+
+	for (i = 0; i < xalp->count; i++) {
+		free(rxas[i].name);
+		/* free(rxas[i].value); */
+	}
+	xalp->count = 0;
+}
+
+void free_xattr(statx *sxp)
+{
+	rsync_xal_free(sxp->xattr);
+	free(sxp->xattr);
+	sxp->xattr = NULL;
+}
+
+static int rsync_xal_compare_names(const void *x1, const void *x2)
+{
+	const rsync_xa *xa1 = x1;
+	const rsync_xa *xa2 = x2;
+	return strcmp(xa1->name, xa2->name);
+}
+
+static int rsync_xal_get(const char *fname, item_list *xalp)
+{
+	ssize_t name_size;
+	ssize_t datum_size;
+	ssize_t left;
+	char *name;
+	size_t len;
+	char *ptr;
+
+	if (!namebuf) {
+		namebuf_len = 1024;
+		namebuf = new_array(char, namebuf_len);
+		if (!namebuf)
+			out_of_memory("rsync_xal_get");
+	}
+
+	/* The length returned includes all the '\0' terminators. */
+	name_size = sys_llistxattr(fname, namebuf, namebuf_len);
+	if (name_size > (ssize_t)namebuf_len) {
+		name_size = -1;
+		errno = ERANGE;
+	}
+	if (name_size < 0) {
+		if (errno == ENOTSUP)
+			return 0;
+		if (errno == ERANGE) {
+			name_size = sys_llistxattr(fname, NULL, 0);
+			if (name_size < 0) {
+				rsyserr(FERROR, errno, "%s: rsync_xal_get: llistxattr",
+					fname);
+				return -1;
+			}
+			namebuf = realloc_array(namebuf, char, name_size + 1024);
+			if (!namebuf)
+				out_of_memory("rsync_xal_get");
+			namebuf_len = name_size + 1024;
+			name_size = sys_llistxattr(fname, namebuf, namebuf_len);
+			if (name_size < 0) {
+				rsyserr(FERROR, errno,
+				    "%s: rsync_xal_get: re-llistxattr failed",
+				    fname);
+				return -1;
+			}
+		} else {
+			rsyserr(FERROR, errno,
+			    "%s: rsync_xal_get: llistxattr failed:",
+			    fname);
+			return -1;
+		}
+	}
+	if (name_size == 0)
+		return 0;
+	for (left = name_size, name = namebuf; left > 0 ; left -= len, name += len) {
+		rsync_xa *rxas;
+
+		len = strlen(name) + 1;
+
+#ifdef HAVE_LINUX_XATTRS
+		/* We only send user and root namespaces. */
+		if (strncmp(name, USER_PREFIX, UPRE_LEN) != 0
+		 && strncmp(name, ROOT_PREFIX, RPRE_LEN) != 0)
+			continue;
+#endif
+
+		rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL);
+
+		datum_size = sys_lgetxattr(fname, name, NULL, 0);
+		if (datum_size < 0) {
+			if (errno == ENOTSUP)
+				return -1;
+			rsyserr(FERROR, errno,
+			    "%s: rsync_xal_get: lgetxattr %s failed",
+			    fname, name);
+			return -1;
+		}
+		ptr = new_array(char, len + datum_size);
+		if (!ptr)
+			out_of_memory("rsync_xal_get");
+		memcpy(ptr, name, len);
+		rxas->name_len = len;
+		rxas->name = ptr;
+		rxas->datum_len = datum_size;
+		rxas->datum = ptr + len;
+		if (datum_size) {
+			ssize_t size = sys_lgetxattr(fname, name, rxas->datum, datum_size);
+			if (size != datum_size) {
+				if (size < 0) {
+					rsyserr(FERROR, errno,
+					    "rsync_xal_get: lgetxattr(%s,%s)"
+					    " failed", fname, name);
+				} else {
+					rprintf(FERROR,
+					    "rsync_xal_get: lgetxattr(%s,%s)"
+					    " returned %ld instead of %ld\n",
+					    fname, name,
+					    (long)size, (long)datum_size);
+				}
+				return -1;
+			}
+		}
+	}
+	if (xalp->count > 1)
+		qsort(xalp->items, xalp->count, sizeof (rsync_xa), rsync_xal_compare_names);
+	return 0;
+}
+
+/* Read the xattr(s) for this filename. */
+int get_xattr(const char *fname, statx *sxp)
+{
+	sxp->xattr = new(item_list);
+	*sxp->xattr = empty_xattr;
+	if (rsync_xal_get(fname, sxp->xattr) < 0) {
+		free_xattr(sxp);
+		return -1;
+	}
+	return 0;
+}
+
+static int find_matching_xattr(item_list *xalp)
+{
+	size_t i, j;
+	item_list *lst = rsync_xal_l.items;
+
+	for (i = 0; i < rsync_xal_l.count; i++) {
+		rsync_xa *rxas1 = lst[i].items;
+		rsync_xa *rxas2 = xalp->items;
+
+		/* Wrong number of elements? */
+		if (lst[i].count != xalp->count)
+			continue;
+		/* any elements different? */
+		for (j = 0; j < xalp->count; j++) {
+			if (rxas1[j].name_len != rxas2[j].name_len
+			 || rxas1[j].datum_len != rxas2[j].datum_len
+			 || strcmp(rxas1[j].name, rxas2[j].name)
+			 || memcmp(rxas1[j].datum, rxas2[j].datum, rxas2[j].datum_len))
+				break;
+		}
+		/* no differences found.  This is The One! */
+		if (j == xalp->count)
+			return i;
+	}
+
+	return -1;
+}
+
+/* Store *xalp on the end of rsync_xal_l */
+static void rsync_xal_store(item_list *xalp)
+{
+	item_list *new_lst = EXPAND_ITEM_LIST(&rsync_xal_l, item_list, RSYNC_XAL_LIST_INITIAL);
+	/* Since the following call starts a new list, we know it will hold the
+	 * entire initial-count, not just enough space for one new item. */
+	*new_lst = empty_xattr;
+	(void)EXPAND_ITEM_LIST(new_lst, rsync_xa, xalp->count);
+	memcpy(new_lst->items, xalp->items, xalp->count * sizeof (rsync_xa));
+	new_lst->count = xalp->count;
+	xalp->count = 0;
+}
+
+/* Send the make_xattr()-generated xattr list for this flist entry. */
+void send_xattr(statx *sxp, int f)
+{
+	int ndx = find_matching_xattr(sxp->xattr);
+	if (ndx != -1) {
+		write_byte(f, 'x');
+		write_int(f, ndx);
+		rsync_xal_free(sxp->xattr);
+	} else {
+		rsync_xa *rxa;
+		int count = sxp->xattr->count;
+		write_byte(f, 'X');
+		write_int(f, count);
+		for (rxa = sxp->xattr->items; count--; rxa++) {
+#ifndef HAVE_LINUX_XATTRS
+			/* This OS only sends the user namespace, so we may have
+			 * stored a root namespace from Linux in user space. */
+			if (strncmp(rxa->name, ROOT_PREFIX, RPRE_LEN) != 0) {
+				write_int(f, rxa->name_len + UPRE_LEN);
+				write_int(f, rxa->datum_len);
+				write_buf(f, USER_PREFIX, UPRE_LEN);
+			} else
+#endif
+			{
+				write_int(f, rxa->name_len);
+				write_int(f, rxa->datum_len);
+			}
+			write_buf(f, rxa->name, rxa->name_len);
+			write_buf(f, rxa->datum, rxa->datum_len);
+		}
+		rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */
+	}
+	free_xattr(sxp);
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* receive and build the rsync_xattr_lists */
+void receive_xattr(struct file_struct *file, int f)
+{
+	static item_list temp_xattr = EMPTY_ITEM_LIST;
+	int tag = read_byte(f);
+	char *ndx_ptr = (char*)file + file_struct_len;
+	int ndx;
+
+	if (tag == 'X') {
+		int i, count = read_int(f);
+		for (i = 0; i < count; i++) {
+			char *ptr;
+			rsync_xa *rxa;
+			size_t name_len = read_int(f);
+			size_t datum_len = read_int(f);
+			if (name_len + datum_len < name_len)
+				out_of_memory("receive_xattr"); /* overflow */
+			rxa = EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count);
+			ptr = new_array(char, name_len + datum_len);
+			if (!ptr)
+				out_of_memory("receive_xattr");
+			read_buf(f, ptr, name_len);
+			read_buf(f, ptr + name_len, datum_len);
+			rxa->name_len = name_len;
+			rxa->datum_len = datum_len;
+			rxa->name = ptr;
+			rxa->datum = ptr + name_len;
+#ifdef HAVE_LINUX_XATTRS
+			if (!am_root && strncmp(ptr, ROOT_PREFIX, RPRE_LEN) == 0) {
+				temp_xattr.count--;
+				free(ptr);
+				continue;
+			}
+#else
+			if (strncmp(ptr, USER_PREFIX, UPRE_LEN) == 0) {
+				rxa->name_len -= UPRE_LEN;
+				memmove(ptr, ptr + UPRE_LEN, rxa->name_len);
+			}
+#endif
+		}
+		ndx = rsync_xal_l.count; /* pre-incremented count */
+		rsync_xal_store(&temp_xattr); /* adds item to rsync_xal_l */
+	} else if (tag == 'x') {
+		ndx = read_int(f);
+		if (ndx < 0 || (size_t)ndx >= rsync_xal_l.count) {
+			rprintf(FERROR, "%s: receive_xattr: xa index %d out of range\n",
+				f_name(file, NULL), ndx);
+			exit_cleanup(RERR_STREAMIO);
+		}
+	} else {
+		rprintf(FERROR,
+		    "%s: receive_xattr: unknown extended attribute type tag: %c\n",
+		    f_name(file, NULL), tag);
+		exit_cleanup(RERR_STREAMIO);
+		ndx = 0; /* silence a compiler warning... */
+	}
+
+	SIVAL(ndx_ptr, 0, ndx);
+}
+
+/* Turn the xattr data in statx into cached xattr data, setting the index
+ * values in the file struct. */
+void cache_xattr(struct file_struct *file, statx *sxp)
+{
+	char *ndx_ptr = (char*)file + file_struct_len;
+	int ndx;
+
+	if (!sxp->xattr)
+		return;
+
+	ndx = find_matching_xattr(sxp->xattr);
+	if (ndx == -1)
+		rsync_xal_store(sxp->xattr); /* adds item to rsync_xal_l */
+	free_xattr(sxp);
+
+	SIVAL(ndx_ptr, 0, ndx);
+}
+
+static int rsync_xal_set(const char *fname, item_list *xalp)
+{
+	rsync_xa *rxas = xalp->items;
+	size_t i;
+	int ret = 0;
+
+	for (i = 0; i < xalp->count; i++) {
+		int status = sys_lsetxattr(fname, rxas[i].name, rxas[i].datum, rxas[i].datum_len);
+		if (status < 0) {
+			rsyserr(FERROR, errno, "%s: rsync_xal_set: lsetxattr %s failed",
+				fname, rxas[i].name);
+			ret = -1;
+		}
+	}
+	return ret;
+}
+
+/* Set extended attributes on indicated filename. */
+int set_xattr(const char *fname, const struct file_struct *file, UNUSED(statx *sxp))
+{
+	int ndx;
+	char *ndx_ptr = (char*)file + file_struct_len;
+	item_list *lst = rsync_xal_l.items;
+
+	if (dry_run)
+		return 1; /* FIXME: --dry-run needs to compute this value */
+
+	if (read_only || list_only) {
+		errno = EROFS;
+		return -1;
+	}
+
+	ndx = IVAL(ndx_ptr, 0);
+	return rsync_xal_set(fname, lst + ndx); /* TODO:  This needs to return 1 if no xattrs changed! */
+}
+
+#endif /* SUPPORT_XATTRS */
