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 mode)) --- old/compat.c +++ new/compat.c @@ -58,6 +58,8 @@ void setup_protocol(int f_out,int f_in) preserve_gid = ++file_extra_cnt; if (preserve_acls && !am_sender) preserve_acls = ++file_extra_cnt; + if (preserve_xattrs && !am_sender) + preserve_xattrs = ++file_extra_cnt; if (remote_protocol == 0) { if (!read_batch) --- old/configure.in +++ new/configure.in @@ -883,6 +883,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 @@ -43,6 +43,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; @@ -862,6 +863,10 @@ static struct file_struct *recv_file_ent if (preserve_acls) receive_acl(file, f); #endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs) + receive_xattr(file, f ); +#endif return file; } @@ -1134,7 +1139,7 @@ static struct file_struct *send_file_nam int flags, int filter_flags) { struct file_struct *file; -#ifdef SUPPORT_ACLS +#if defined SUPPORT_ACLS || defined SUPPORT_XATTRS statx sx; #endif @@ -1153,6 +1158,13 @@ static struct file_struct *send_file_nam return NULL; } #endif +#ifdef SUPPORT_XATTRS + if (preserve_xattrs && f >= 0) { + sx.xattr = NULL; + if (get_xattr(fname, &sx) < 0) + return NULL; + } +#endif maybe_emit_filelist_progress(flist->count + flist_count_offset); @@ -1164,6 +1176,10 @@ 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 } return file; } --- old/lib/sysxattr.c +++ new/lib/sysxattr.c @@ -0,0 +1,135 @@ +/* + * 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; off += keylen + 1) { + 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); + list[off+keylen] = '\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 +#elif defined HAVE_SYS_XATTR_H +#include +#elif defined HAVE_SYS_EXTATTR_H +#include +#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 @@ -239,8 +243,8 @@ static void print_rsync_version(enum log (int)(sizeof (int64) * 8)); rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n", got_socketpair, hardlinks, links, ipv6, have_inplace); - rprintf(f, " %sappend, %sACLs\n", - have_inplace, acls); + rprintf(f, " %sappend, %sACLs, %sxattrs\n", + have_inplace, acls, xattrs); #ifdef MAINTAINER_MODE rprintf(f, "Panic Action: \"%s\"\n", get_panic_action()); @@ -286,7 +290,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"); @@ -311,6 +315,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"); @@ -434,6 +441,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 }, @@ -1122,6 +1132,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() @@ -1590,6 +1611,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 (recurse) argstr[x++] = 'r'; if (always_checksum) --- 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; @@ -321,6 +322,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 @@ -554,6 +554,10 @@ struct idev_node { #define ACLS_NEED_MASK 1 #endif +#ifndef HAVE_NO_XATTRS +#define SUPPORT_XATTRS 1 +#endif + #define GID_NONE ((gid_t)-1) union file_extras { @@ -574,6 +578,7 @@ extern int file_extra_cnt; extern int preserve_uid; extern int preserve_gid; extern int preserve_acls; +extern int preserve_xattrs; #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename)) #define EXTRA_LEN (sizeof (union file_extras)) @@ -607,6 +612,7 @@ extern int preserve_acls; #define F_OWNER(f) REQ_EXTRA(f, preserve_uid)->unum #define F_GROUP(f) REQ_EXTRA(f, preserve_gid)->unum #define F_ACL(f) REQ_EXTRA(f, preserve_acls)->unum +#define F_XATTR(f) REQ_EXTRA(f, preserve_xattrs)->unum /* These items are per-entry optional and mutally exclusive: */ #define F_HL_GNUM(f) OPT_EXTRA(f, LEN64_BUMP(f))->num @@ -798,6 +804,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 @@ The ACL-sending protocol used by this ve the patch that was shipped with 2.6.8. Sending ACLs to an older version of the ACL patch is not supported. +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,413 @@ +/* + * 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; + +#define RSYNC_XAL_INITIAL 5 +#define RSYNC_XAL_LIST_INITIAL 100 + +#define HAS_PREFIX(str, prfx) (*(str) == *(prfx) \ + && strncmp(str, prfx, sizeof (prfx) - 1) == 0) + +#define USER_PREFIX "user." +#define UPRE_LEN ((int)sizeof USER_PREFIX - 1) +#define SYSTEM_PREFIX "system." +#define SPRE_LEN ((int)sizeof SYSTEM_PREFIX - 1) + +#ifdef HAVE_LINUX_XATTRS +#define RPRE_LEN 0 +#else +#define RSYNC_PREFIX "rsync." +#define RPRE_LEN ((int)sizeof RSYNC_PREFIX - 1) +#endif + +typedef struct { + char *datum, *name; + size_t datum_len, name_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].datum); + /*free(rxas[i].name);*/ + } + 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 list_len, name_len, datum_len; + char *name, *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. */ + list_len = sys_llistxattr(fname, namebuf, namebuf_len); + if (list_len > (ssize_t)namebuf_len) { + list_len = -1; + errno = ERANGE; + } + if (list_len < 0) { + if (errno == ENOTSUP) + return 0; + if (errno == ERANGE) { + list_len = sys_llistxattr(fname, NULL, 0); + if (list_len < 0) { + rsyserr(FERROR, errno, + "rsync_xal_get: llistxattr(\"%s\",0) failed", + fname); + return -1; + } + namebuf = realloc_array(namebuf, char, list_len + 1024); + if (!namebuf) + out_of_memory("rsync_xal_get"); + namebuf_len = list_len + 1024; + list_len = sys_llistxattr(fname, namebuf, namebuf_len); + if (list_len < 0) { + rsyserr(FERROR, errno, + "rsync_xal_get: llistxattr(\"%s\",%ld) failed", + fname, (long)namebuf_len); + return -1; + } + } else { + rsyserr(FERROR, errno, + "rsync_xal_get: llistxattr(\"%s\",%ld) failed", + fname, (long)namebuf_len); + return -1; + } + } + + for (name = namebuf; list_len > 0; name += name_len) { + rsync_xa *rxas; + + name_len = strlen(name) + 1; + list_len -= name_len; + +#ifdef HAVE_LINUX_XATTRS + /* We don't send the system namespace. */ + if (HAS_PREFIX(name, SYSTEM_PREFIX)) + continue; +#endif + + datum_len = sys_lgetxattr(fname, name, NULL, 0); + if (datum_len < 0) { + if (errno == ENOTSUP) + return -1; + rsyserr(FERROR, errno, + "rsync_xal_get: lgetxattr(\"%s\",\"%s\",0) failed", + fname, name); + return -1; + } + ptr = new_array(char, name_len + datum_len); + if (!ptr) + out_of_memory("rsync_xal_get"); + if (datum_len) { + ssize_t len = sys_lgetxattr(fname, name, ptr, datum_len); + if (len != datum_len) { + if (len < 0) { + rsyserr(FERROR, errno, + "rsync_xal_get: lgetxattr(\"%s\",\"%s\",%ld)" + " failed", fname, name, (long)datum_len); + } else { + rprintf(FERROR, + "rsync_xal_get: lgetxattr(\"%s\",\"%s\",%ld)" + " returned %ld\n", fname, name, + (long)datum_len, (long)len); + } + free(ptr); + return -1; + } + } + rxas = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL); + rxas->name = ptr + datum_len; + rxas->datum = ptr; + rxas->name_len = name_len; + rxas->datum_len = datum_len; + memcpy(rxas->name, name, name_len); + } + 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++) { +#ifdef HAVE_LINUX_XATTRS + write_int(f, rxa->name_len); + write_int(f, rxa->datum_len); + write_buf(f, rxa->name, rxa->name_len); +#else + /* We strip the rsync prefix from disguised namespaces + * and put everything else in the user namespace. */ + if (HAS_PREFIX(rxa->name, RSYNC_PREFIX) + && rxa->name[RPRE_LEN] != '%') { + write_int(f, rxa->name_len - RPRE_LEN); + write_int(f, rxa->datum_len); + write_buf(f, rxa->name + RPRE_LEN, rxa->name_len - RPRE_LEN); + } else { + write_int(f, rxa->name_len + UPRE_LEN); + write_int(f, rxa->datum_len); + write_buf(f, USER_PREFIX, UPRE_LEN); + write_buf(f, rxa->name, rxa->name_len); + } +#endif + 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 ndx, tag = read_byte(f); + + if (tag == 'X') { + int i, count = read_int(f); + for (i = 0; i < count; i++) { + char *ptr, *name; + rsync_xa *rxa; + size_t name_len = read_int(f); + size_t datum_len = read_int(f); +#ifdef HAVE_LINUX_XATTRS + size_t extra_len = 0; +#else + size_t extra_len = am_root ? RPRE_LEN : 0; + if (datum_len + extra_len < datum_len) + out_of_memory("receive_xattr"); /* overflow */ +#endif + if (name_len + datum_len + extra_len < name_len) + out_of_memory("receive_xattr"); /* overflow */ + ptr = new_array(char, name_len + datum_len + extra_len); + if (!ptr) + out_of_memory("receive_xattr"); + name = ptr + datum_len + extra_len; + read_buf(f, name, name_len); + read_buf(f, ptr, datum_len); +#ifdef HAVE_LINUX_XATTRS + /* Non-root can only save the user namespace. */ + if (!am_root && !HAS_PREFIX(name, USER_PREFIX)) { + free(ptr); + continue; + } +#else + /* This OS only has a user namespace, so we either + * strip the user prefix, or we put a non-user + * namespace inside our rsync hierarchy. */ + if (HAS_PREFIX(name, USER_PREFIX)) { + name += UPRE_LEN; + name_len -= UPRE_LEN; + } else if (am_root) { + name -= RPRE_LEN; + name_len += RPRE_LEN; + memcpy(name, RSYNC_PREFIX, RPRE_LEN); + } else { + free(ptr); + continue; + } +#endif + rxa = EXPAND_ITEM_LIST(&temp_xattr, rsync_xa, count); + rxa->name = name; + rxa->datum = ptr; + rxa->name_len = name_len; + rxa->datum_len = datum_len; + } + 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, "receive_xattr: xa index %d out of" + " range for %s\n", ndx, f_name(file, NULL)); + exit_cleanup(RERR_STREAMIO); + } + } else { + rprintf(FERROR, "receive_xattr: unknown extended attribute" + " type tag (%c) for %s\n", tag, f_name(file, NULL)); + exit_cleanup(RERR_STREAMIO); + } + + F_XATTR(file) = 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) +{ + 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); + + F_XATTR(file) = 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, + "rsync_xal_set: lsetxattr(\"%s\",\"%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; + 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 = F_XATTR(file); + return rsync_xal_set(fname, lst + ndx); /* TODO: This needs to return 1 if no xattrs changed! */ +} + +#endif /* SUPPORT_XATTRS */