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 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,35 @@ 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_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]) + ;; + *) + 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,87 @@ +/* + * 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, int flags) +{ + return lsetxattr(path, name, value, size, flags); +} + +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, int flags) +{ + return setxattr(path, name, value, size, 0, XATTR_NOFOLLOW | flags); +} + +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); +} + +#else + +#error You need to create xattr compatibility functions. + +#endif + +#endif /* SUPPORT_XATTRS */ --- old/lib/sysxattr.h +++ new/lib/sysxattr.h @@ -0,0 +1,24 @@ +#ifdef SUPPORT_XATTRS + +#if defined HAVE_ATTR_XATTR_H +#include +#elif defined HAVE_SYS_XATTR_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 flags); +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,375 @@ +/* + * 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 read_only; +extern int list_only; +extern unsigned int file_struct_len; + +#define RSYNC_XAL_INITIAL 5 +#define RSYNC_XAL_LIST_INITIAL 100 + +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; + +#ifdef HAVE_OSX_XATTRS +#define UNIQUE_PREFIX "user.0S%." /* OSX */ +#define UPRE_LEN (sizeof UNIQUE_PREFIX - 1) +#endif + +/* ------------------------------------------------------------------------- */ + +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 = EXPAND_ITEM_LIST(xalp, rsync_xa, RSYNC_XAL_INITIAL); + + len = strlen(name) + 1; + 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++) { +#ifdef HAVE_OSX_XATTRS + if (strncmp(rxa->name, "user.", 5) != 0 + && strncmp(rxa->name, "system.", 7) != 0) { + write_int(f, rxa->name_len + UPRE_LEN); + write_int(f, rxa->datum_len); + write_buf(f, UNIQUE_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_OSX_XATTRS + if (strncmp(rxa->name, UNIQUE_PREFIX, UPRE_LEN) == 0) { + rxa->name_len -= UPRE_LEN; + memmove(rxa->name, rxa->name + 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, 0); + 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 */