-After applying this patch, run these commands for a successful build:
+To use this patch, run these commands for a successful build:
+ patch -p1 <patches/acls.diff
./prepare-source
./configure --enable-acl-support
make
+See the --acls (-A) option in the revised man page for a note on using this
+latest ACL-enabling patch to send files to an older ACL-enabled rsync.
+
--- old/Makefile.in
+++ new/Makefile.in
-@@ -25,15 +25,15 @@ VERSION=@VERSION@
+@@ -26,15 +26,15 @@ VERSION=@VERSION@
.SUFFIXES:
.SUFFIXES: .c .o
+ lib/permstring.o lib/pool_alloc.o lib/sysacls.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 \
+ OBJS1=flist.o 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 io.o compat.o hlink.o token.o uidlist.o socket.o \
- fileio.o batch.o clientname.o chmod.o
+ fileio.o batch.o clientname.o chmod.o acls.o
OBJS3=progress.o pipe.o
popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
--- old/acls.c
+++ new/acls.c
-@@ -0,0 +1,1201 @@
-+/* -*- c-file-style: "linux" -*-
-+ Copyright (C) Andrew Tridgell 1996
-+ Copyright (C) Paul Mackerras 1996
-+
-+ 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-+*/
-+
-+/* handle passing ACLs between systems */
+@@ -0,0 +1,1094 @@
++/*
++ * Handle passing Access Control Lists between systems.
++ *
++ * Copyright (C) 1996 Andrew Tridgell
++ * Copyright (C) 1996 Paul Mackerras
++ * 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/sysacls.h"
+
+#ifdef SUPPORT_ACLS
+
-+extern int am_root;
+extern int dry_run;
++extern int read_only;
++extern int list_only;
+extern int orig_umask;
++extern int protocol_version;
++
++/* === ACL structures === */
+
+typedef struct {
+ id_t id;
+ uchar access;
-+ SMB_ACL_TAG_T tag_type;
-+} rsync_ace;
++} id_access;
+
+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ rsync_ace *races;
++ id_access *idas;
++ int count;
++} ida_entries;
++
++#define NO_ENTRY ((uchar)0x80)
++typedef struct rsync_acl {
++ ida_entries users;
++ ida_entries groups;
++ /* These will be NO_ENTRY if there's no such entry. */
++ uchar user_obj;
++ uchar group_obj;
++ uchar mask;
++ uchar other;
+} rsync_acl;
+
-+static const rsync_acl rsync_acl_initializer = { 0, 0, NULL };
++typedef struct {
++ rsync_acl racl;
++ SMB_ACL_T sacl;
++} acl_duo;
++
++static const rsync_acl empty_rsync_acl = {
++ {NULL, 0}, {NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY
++};
++
++static item_list access_acl_list = EMPTY_ITEM_LIST;
++static item_list default_acl_list = EMPTY_ITEM_LIST;
++
++/* === Calculations on ACL types === */
++
++static const char *str_acl_type(SMB_ACL_TYPE_T type)
++{
++ return type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS"
++ : type == SMB_ACL_TYPE_DEFAULT ? "SMB_ACL_TYPE_DEFAULT"
++ : "unknown SMB_ACL_TYPE_T";
++}
+
+#define OTHER_TYPE(t) (SMB_ACL_TYPE_ACCESS+SMB_ACL_TYPE_DEFAULT-(t))
+#define BUMP_TYPE(t) ((t = OTHER_TYPE(t)) == SMB_ACL_TYPE_DEFAULT)
+
-+static void expand_rsync_acl(rsync_acl *racl)
++static int count_racl_entries(const rsync_acl *racl)
+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (racl->malloced <= racl->count) {
-+ rsync_ace *new_ptr;
-+ size_t new_size = racl->malloced + 10;
-+ new_ptr = realloc_array(racl->races, rsync_ace, new_size);
-+ if (verbose >= 4) {
-+ rprintf(FINFO, "expand rsync_acl to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof racl->races[0],
-+ racl->races ? "" : " not");
-+ }
++ return racl->users.count + racl->groups.count
++ + (racl->user_obj != NO_ENTRY)
++ + (racl->group_obj != NO_ENTRY)
++ + (racl->mask != NO_ENTRY)
++ + (racl->other != NO_ENTRY);
++}
+
-+ racl->races = new_ptr;
-+ racl->malloced = new_size;
++static int calc_sacl_entries(const rsync_acl *racl)
++{
++ /* A System ACL always gets user/group/other permission entries. */
++ return racl->users.count + racl->groups.count
++#ifdef ACLS_NEED_MASK
++ + 4;
++#else
++ + (racl->mask != NO_ENTRY) + 3;
++#endif
++}
+
-+ if (!racl->races)
-+ out_of_memory("expand_rsync_acl");
++/* Extracts and returns the permission bits from the ACL. This cannot be
++ * called on an rsync_acl that has NO_ENTRY in any spot but the mask. */
++static int rsync_acl_get_perms(const rsync_acl *racl)
++{
++ return (racl->user_obj << 6)
++ + ((racl->mask != NO_ENTRY ? racl->mask : racl->group_obj) << 3)
++ + racl->other;
++}
++
++/* Removes the permission-bit entries from the ACL because these
++ * can be reconstructed from the file's mode. */
++static void rsync_acl_strip_perms(rsync_acl *racl)
++{
++ racl->user_obj = NO_ENTRY;
++ if (racl->mask == NO_ENTRY)
++ racl->group_obj = NO_ENTRY;
++ else {
++ if (racl->group_obj == racl->mask)
++ racl->group_obj = NO_ENTRY;
++ racl->mask = NO_ENTRY;
+ }
++ racl->other = NO_ENTRY;
++}
++
++/* Given an empty rsync_acl, fake up the permission bits. */
++static void rsync_acl_fake_perms(rsync_acl *racl, mode_t mode)
++{
++ racl->user_obj = (mode >> 6) & 7;
++ racl->group_obj = (mode >> 3) & 7;
++ racl->other = mode & 7;
++}
++
++/* === Rsync ACL functions === */
++
++static BOOL ida_entries_equal(const ida_entries *ial1, const ida_entries *ial2)
++{
++ id_access *ida1, *ida2;
++ int count = ial1->count;
++ if (count != ial2->count)
++ return False;
++ ida1 = ial1->idas;
++ ida2 = ial2->idas;
++ for (; count--; ida1++, ida2++) {
++ if (ida1->access != ida2->access || ida1->id != ida2->id)
++ return False;
++ }
++ return True;
++}
++
++static BOOL rsync_acl_equal(const rsync_acl *racl1, const rsync_acl *racl2)
++{
++ return racl1->user_obj == racl2->user_obj
++ && racl1->group_obj == racl2->group_obj
++ && racl1->mask == racl2->mask
++ && racl1->other == racl2->other
++ && ida_entries_equal(&racl1->users, &racl2->users)
++ && ida_entries_equal(&racl1->groups, &racl2->groups);
++}
++
++/* Are the extended (non-permission-bit) entries equal? If so, the rest of
++ * the ACL will be handled by the normal mode-preservation code. This is
++ * only meaningful for access ACLs! Note: the 1st arg is a fully-populated
++ * rsync_acl, but the 2nd parameter can be a condensed rsync_acl, which means
++ * that it might have several of its permission objects set to NO_ENTRY. */
++static BOOL rsync_acl_equal_enough(const rsync_acl *racl1,
++ const rsync_acl *racl2, mode_t m)
++{
++ if ((racl1->mask ^ racl2->mask) & NO_ENTRY)
++ return False; /* One has a mask and the other doesn't */
++
++ /* When there's a mask, the group_obj becomes an extended entry. */
++ if (racl1->mask != NO_ENTRY) {
++ /* A condensed rsync_acl with a mask can only have no
++ * group_obj when it was identical to the mask. This
++ * means that it was also identical to the group attrs
++ * from the mode. */
++ if (racl2->group_obj == NO_ENTRY) {
++ if (racl1->group_obj != ((m >> 3) & 7))
++ return False;
++ } else if (racl1->group_obj != racl2->group_obj)
++ return False;
++ }
++ return ida_entries_equal(&racl1->users, &racl2->users)
++ && ida_entries_equal(&racl1->groups, &racl2->groups);
+}
+
+static void rsync_acl_free(rsync_acl *racl)
+{
-+ free(racl->races);
-+ racl->races = NULL;
-+ racl->count = 0;
-+ racl->malloced = 0;
++ if (racl->users.idas)
++ free(racl->users.idas);
++ if (racl->groups.idas)
++ free(racl->groups.idas);
++ *racl = empty_rsync_acl;
+}
+
-+static int rsync_ace_sorter(const void *r1, const void *r2)
++void free_acl(statx *sxp)
+{
-+ rsync_ace *race1 = (rsync_ace *)r1;
-+ SMB_ACL_TAG_T rtag1 = race1->tag_type;
-+ id_t rid1 = race1->id;
-+ rsync_ace *race2 = (rsync_ace *)r2;
-+ SMB_ACL_TAG_T rtag2 = race2->tag_type;
-+ id_t rid2 = race2->id;
-+ /* start at the extrema */
-+ if (rtag1 == SMB_ACL_USER_OBJ || rtag2 == SMB_ACL_MASK)
-+ return -1;
-+ if (rtag2 == SMB_ACL_USER_OBJ || rtag1 == SMB_ACL_MASK)
-+ return 1;
-+ /* work inwards */
-+ if (rtag1 == SMB_ACL_OTHER)
-+ return 1;
-+ if (rtag2 == SMB_ACL_OTHER)
-+ return -1;
-+ /* only SMB_ACL_USERs and SMB_ACL_GROUP*s left */
-+ if (rtag1 == SMB_ACL_USER) {
-+ switch (rtag2) {
-+ case SMB_ACL_GROUP:
-+ case SMB_ACL_GROUP_OBJ:
-+ case SMB_ACL_OTHER:
-+ return -1;
-+ }
-+ /* both USER */
-+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
++ if (sxp->acc_acl) {
++ rsync_acl_free(sxp->acc_acl);
++ free(sxp->acc_acl);
++ sxp->acc_acl = NULL;
+ }
-+ if (rtag2 == SMB_ACL_USER)
-+ return 1;
-+ /* only SMB_ACL_GROUP*s to worry about; kick out GROUP_OBJs first */
-+ if (rtag1 == SMB_ACL_GROUP_OBJ)
-+ return -1;
-+ if (rtag2 == SMB_ACL_GROUP_OBJ)
-+ return 1;
-+ /* only SMB_ACL_GROUPs left */
++ if (sxp->def_acl) {
++ rsync_acl_free(sxp->def_acl);
++ free(sxp->def_acl);
++ sxp->def_acl = NULL;
++ }
++}
++
++static int id_access_sorter(const void *r1, const void *r2)
++{
++ id_access *ida1 = (id_access *)r1;
++ id_access *ida2 = (id_access *)r2;
++ id_t rid1 = ida1->id, rid2 = ida2->id;
+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
+}
+
-+static void sort_rsync_acl(rsync_acl *racl)
++static void sort_ida_entries(ida_entries *idal)
+{
-+ if (!racl->count)
++ if (!idal->count)
+ return;
-+ qsort((void **)racl->races, racl->count, sizeof racl->races[0],
-+ &rsync_ace_sorter);
++ qsort(idal->idas, idal->count, sizeof idal->idas[0], id_access_sorter);
+}
+
++/* Transfer the count id_access items out of the temp_ida_list into either
++ * the users or groups ida_entries list in racl. */
++static void save_idas(item_list *temp_ida_list, rsync_acl *racl, SMB_ACL_TAG_T type)
++{
++ id_access *idas;
++ ida_entries *ent;
++
++ if (temp_ida_list->count) {
++ int cnt = temp_ida_list->count;
++ id_access *temp_idas = temp_ida_list->items;
++ if (!(idas = new_array(id_access, cnt)))
++ out_of_memory("save_idas");
++ memcpy(idas, temp_idas, cnt * sizeof *temp_idas);
++ } else
++ idas = NULL;
++
++ ent = type == SMB_ACL_USER ? &racl->users : &racl->groups;
++
++ if (ent->count) {
++ rprintf(FERROR, "save_idas: disjoint list found for type %d\n", type);
++ exit_cleanup(RERR_UNSUPPORTED);
++ }
++ ent->count = temp_ida_list->count;
++ ent->idas = idas;
++
++ /* Truncate the temporary list now that its idas have been saved. */
++ temp_ida_list->count = 0;
++}
++
++/* === System ACLs === */
++
++/* Unpack system ACL -> rsync ACL verbatim. Return whether we succeeded. */
+static BOOL unpack_smb_acl(rsync_acl *racl, SMB_ACL_T sacl)
+{
++ static item_list temp_ida_list = EMPTY_ITEM_LIST;
++ SMB_ACL_TAG_T prior_list_type = 0;
+ SMB_ACL_ENTRY_T entry;
-+ int rc;
+ const char *errfun;
-+ *racl = rsync_acl_initializer;
++ int rc;
++
+ errfun = "sys_acl_get_entry";
+ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
+ rc == 1;
+ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
+ SMB_ACL_PERMSET_T permset;
++ uchar access;
+ void *qualifier;
-+ rsync_ace *race;
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ if ((rc = sys_acl_get_tag_type(entry, &race->tag_type))) {
++ id_access *ida;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
-+ race->access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
-+ switch (race->tag_type) {
++ access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
++ /* continue == done with entry; break == store in temporary ida list */
++ switch (tag_type) {
++ case SMB_ACL_USER_OBJ:
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_USER:
++ break;
++ case SMB_ACL_GROUP_OBJ:
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_GROUP:
+ break;
++ case SMB_ACL_MASK:
++ if (racl->mask == NO_ENTRY)
++ racl->mask = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate MASK entry ignored\n");
++ continue;
++ case SMB_ACL_OTHER:
++ if (racl->other == NO_ENTRY)
++ racl->other = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n");
++ continue;
+ default:
++ rprintf(FINFO, "unpack_smb_acl: warning: entry with unrecognized tag type ignored\n");
+ continue;
+ }
+ if (!(qualifier = sys_acl_get_qualifier(entry))) {
+ rc = EINVAL;
+ break;
+ }
-+ race->id = *((id_t *)qualifier);
-+ sys_acl_free_qualifier(qualifier, race->tag_type);
++ if (tag_type != prior_list_type) {
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
++ prior_list_type = tag_type;
++ }
++ ida = EXPAND_ITEM_LIST(&temp_ida_list, id_access, -10);
++ ida->id = *((id_t *)qualifier);
++ ida->access = access;
++ sys_acl_free_qualifier(qualifier, tag_type);
+ }
+ if (rc) {
-+ rprintf(FERROR, "unpack_smb_acl: %s(): %s\n",
-+ errfun, strerror(errno));
++ rsyserr(FERROR, errno, "unpack_smb_acl: %s()", errfun);
+ rsync_acl_free(racl);
+ return False;
+ }
-+ sort_rsync_acl(racl);
-+ return True;
-+}
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
+
-+static BOOL rsync_acls_equal(const rsync_acl *racl1, const rsync_acl *racl2)
-+{
-+ rsync_ace *race1, *race2;
-+ size_t count = racl1->count;
-+ if (count != racl2->count)
-+ return False;
-+ race1 = racl1->races;
-+ race2 = racl2->races;
-+ for (; count--; race1++, race2++) {
-+ if (race1->tag_type != race2->tag_type
-+ || race1->access != race2->access
-+ || ((race1->tag_type == SMB_ACL_USER
-+ || race1->tag_type == SMB_ACL_GROUP)
-+ && race1->id != race2->id))
-+ return False;
++ sort_ida_entries(&racl->users);
++ sort_ida_entries(&racl->groups);
++
++#ifdef ACLS_NEED_MASK
++ if (!racl->users.count && !racl->groups.count && racl->mask != NO_ENTRY) {
++ /* Throw away a superfluous mask, but mask off the
++ * group perms with it first. */
++ racl->group_obj &= racl->mask;
++ racl->mask = NO_ENTRY;
+ }
++#endif
++
+ return True;
+}
+
-+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ rsync_acl *racls;
-+} rsync_acl_list;
++/* Synactic sugar for system calls */
+
-+static rsync_acl_list _rsync_acl_lists[] = {
-+ { 0, 0, NULL }, /* SMB_ACL_TYPE_ACCESS */
-+ { 0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */
-+};
++#define CALL_OR_ERROR(func,args,str) \
++ do { \
++ if (func args) { \
++ errfun = str; \
++ goto error_exit; \
++ } \
++ } while (0)
++
++#define COE(func,args) CALL_OR_ERROR(func,args,#func)
++#define COE2(func,args) CALL_OR_ERROR(func,args,NULL)
+
-+static inline rsync_acl_list *rsync_acl_lists(SMB_ACL_TYPE_T type)
++/* Store the permissions in the system ACL entry. */
++static int store_access_in_entry(uchar access, SMB_ACL_ENTRY_T entry)
+{
-+ return type == SMB_ACL_TYPE_ACCESS ? &_rsync_acl_lists[0]
-+ : &_rsync_acl_lists[1];
++ const char *errfun = NULL;
++ SMB_ACL_PERMSET_T permset;
++
++ COE( sys_acl_get_permset,(entry, &permset) );
++ COE( sys_acl_clear_perms,(permset) );
++ if (access & 4)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_READ) );
++ if (access & 2)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_WRITE) );
++ if (access & 1)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_EXECUTE) );
++ COE( sys_acl_set_permset,(entry, permset) );
++
++ return 0;
++
++ error_exit:
++ rsyserr(FERROR, errno, "store_access_in_entry %s()", errfun);
++ return -1;
+}
+
-+static void expand_rsync_acl_list(rsync_acl_list *racl_list)
++/* Pack rsync ACL -> system ACL verbatim. Return whether we succeeded. */
++static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (racl_list->malloced <= racl_list->count) {
-+ rsync_acl *new_ptr;
-+ size_t new_size;
-+ if (racl_list->malloced < 1000)
-+ new_size = racl_list->malloced + 1000;
-+ else
-+ new_size = racl_list->malloced * 2;
-+ new_ptr = realloc_array(racl_list->racls, rsync_acl, new_size);
-+ if (verbose >= 3) {
-+ rprintf(FINFO, "expand_rsync_acl_list to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof racl_list->racls[0],
-+ racl_list->racls ? "" : " not");
-+ }
++#ifdef ACLS_NEED_MASK
++ uchar mask_bits;
++#endif
++ size_t count;
++ id_access *ida;
++ const char *errfun = NULL;
++ SMB_ACL_ENTRY_T entry;
+
-+ racl_list->racls = new_ptr;
-+ racl_list->malloced = new_size;
++ if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) {
++ rsyserr(FERROR, errno, "pack_smb_acl: sys_acl_init()");
++ return False;
++ }
++
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) );
++ COE2( store_access_in_entry,(racl->user_obj & 7, entry) );
+
-+ if (!racl_list->racls)
-+ out_of_memory("expand_rsync_acl_list");
++ for (ida = racl->users.idas, count = racl->users.count; count--; ida++) {
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER) );
++ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) );
++ COE2( store_access_in_entry,(ida->access, entry) );
+ }
-+}
+
-+#if 0
-+static void free_rsync_acl_list(rsync_acl_list *racl_list)
-+{
-+ /* Run this in reverse, so references are freed before referents,
-+ * although not currently necessary. */
-+ while (racl_list->count--) {
-+ rsync_acl *racl = &racl_list->racls[racl_list->count];
-+ if (racl)
-+ rsync_acl_free(racl);
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP_OBJ) );
++ COE2( store_access_in_entry,(racl->group_obj & 7, entry) );
++
++ for (ida = racl->groups.idas, count = racl->groups.count; count--; ida++) {
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP) );
++ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) );
++ COE2( store_access_in_entry,(ida->access, entry) );
++ }
++
++#ifdef ACLS_NEED_MASK
++ mask_bits = racl->mask == NO_ENTRY ? racl->group_obj & 7 : racl->mask;
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_MASK) );
++ COE2( store_access_in_entry,(mask_bits, entry) );
++#else
++ if (racl->mask != NO_ENTRY) {
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_MASK) );
++ COE2( store_access_in_entry,(racl->mask, entry) );
+ }
-+ free(racl_list->racls);
-+ racl_list->racls = NULL;
-+ racl_list->malloced = 0;
-+}
+#endif
+
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_OTHER) );
++ COE2( store_access_in_entry,(racl->other & 7, entry) );
++
++#ifdef DEBUG
++ if (sys_acl_valid(*smb_acl) < 0)
++ rprintf(FERROR, "pack_smb_acl: warning: system says the ACL I packed is invalid\n");
++#endif
++
++ return True;
++
++ error_exit:
++ if (errfun) {
++ rsyserr(FERROR, errno, "pack_smb_acl %s()", errfun);
++ }
++ sys_acl_free_acl(*smb_acl);
++ return False;
++}
++
+static int find_matching_rsync_acl(SMB_ACL_TYPE_T type,
-+ const rsync_acl_list *racl_list,
++ const item_list *racl_list,
+ const rsync_acl *racl)
+{
+ static int access_match = -1, default_match = -1;
-+ int *match = (type == SMB_ACL_TYPE_ACCESS) ?
-+ &access_match : &default_match;
++ int *match = type == SMB_ACL_TYPE_ACCESS ? &access_match : &default_match;
+ size_t count = racl_list->count;
++
+ /* If this is the first time through or we didn't match the last
+ * time, then start at the end of the list, which should be the
+ * best place to start hunting. */
+ if (*match == -1)
+ *match = racl_list->count - 1;
+ while (count--) {
-+ if (rsync_acls_equal(&racl_list->racls[*match], racl))
++ rsync_acl *base = racl_list->items;
++ if (rsync_acl_equal(base + *match, racl))
+ return *match;
+ if (!(*match)--)
+ *match = racl_list->count - 1;
+ }
++
+ *match = -1;
+ return *match;
+}
+
-+/* The general strategy with the tag_type <-> character mapping is that
-+ * lowercase implies that no qualifier follows, where uppercase does.
-+ * A similar idiom for the acl type (access or default) itself, but
-+ * lowercase in this instance means there's no ACL following, so the
-+ * ACL is a repeat, so the receiver should reuse the last of the same
-+ * type ACL. */
-+
-+static void send_rsync_acl(int f, const rsync_acl *racl)
-+{
-+ rsync_ace *race;
-+ size_t count = racl->count;
-+ write_int(f, count);
-+ for (race = racl->races; count--; race++) {
-+ int ch;
-+ switch (race->tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ ch = 'u';
-+ break;
-+ case SMB_ACL_USER:
-+ ch = 'U';
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ ch = 'g';
-+ break;
-+ case SMB_ACL_GROUP:
-+ ch = 'G';
-+ break;
-+ case SMB_ACL_OTHER:
-+ ch = 'o';
-+ break;
-+ case SMB_ACL_MASK:
-+ ch = 'm';
-+ break;
-+ default:
-+ rprintf(FERROR,
-+ "send_rsync_acl: unknown tag_type (%02x) on ACE; disregarding\n",
-+ race->tag_type);
-+ continue;
-+ }
-+ write_byte(f, ch);
-+ write_byte(f, race->access);
-+ if (isupper(ch)) {
-+ write_int(f, race->id);
-+ /* FIXME: sorta wasteful: we should maybe buffer as
-+ * many ids as max(ACL_USER + ACL_GROUP) objects to
-+ * keep from making so many calls. */
-+ if (ch == 'U')
-+ add_uid(race->id);
-+ else
-+ add_gid(race->id);
-+ }
-+ }
-+}
-+
-+static rsync_acl _curr_rsync_acls[2];
-+
-+
-+static const char *str_acl_type(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS" :
-+ type == SMB_ACL_TYPE_DEFAULT ? "SMB_ACL_TYPE_DEFAULT" :
-+ "unknown SMB_ACL_TYPE_T";
-+}
-+
-+/*
-+ * Overwrite racl with a new three-entry ACL from the given permissions.
-+ */
-+static void perms_to_acl(int perms, rsync_acl *racl)
-+{
-+ racl->count = 0;
-+ expand_rsync_acl(racl);
-+ racl->races[racl->count].tag_type = SMB_ACL_USER_OBJ;
-+ racl->races[racl->count++].access = (perms >> 6) & 7;
-+ expand_rsync_acl(racl);
-+ racl->races[racl->count].tag_type = SMB_ACL_GROUP_OBJ;
-+ racl->races[racl->count++].access = (perms >> 3) & 7;
-+ expand_rsync_acl(racl);
-+ racl->races[racl->count].tag_type = SMB_ACL_OTHER;
-+ racl->races[racl->count++].access = (perms >> 0) & 7;
-+}
-+
-+/* Generate the ACL(s) for this flist entry;
-+ * ACL(s) are either sent or cleaned-up by send_acl() below. */
-+
-+int make_acl(const struct file_struct *file, const char *fname)
++/* Return the Access Control List for the given filename. */
++int get_acl(const char *fname, statx *sxp)
+{
+ SMB_ACL_TYPE_T type;
-+ rsync_acl *curr_racl;
+
-+ if (S_ISLNK(file->mode))
-+ return 1;
++ if (S_ISLNK(sxp->st.st_mode))
++ return 0;
+
-+ curr_racl = &_curr_rsync_acls[0];
+ type = SMB_ACL_TYPE_ACCESS;
+ do {
-+ SMB_ACL_T sacl;
-+ BOOL ok;
-+ if ((sacl = sys_acl_get_file(fname, type)) != 0) {
-+ ok = unpack_smb_acl(curr_racl, sacl);
++ SMB_ACL_T sacl = sys_acl_get_file(fname, type);
++ rsync_acl *racl = new(rsync_acl);
++
++ if (!racl)
++ out_of_memory("get_acl");
++ *racl = empty_rsync_acl;
++ if (type == SMB_ACL_TYPE_ACCESS)
++ sxp->acc_acl = racl;
++ else
++ sxp->def_acl = racl;
++
++ if (sacl) {
++ BOOL ok = unpack_smb_acl(racl, sacl);
++
+ sys_acl_free_acl(sacl);
-+ if (!ok)
++ if (!ok) {
++ free_acl(sxp);
+ return -1;
-+ } else if (errno == ENOTSUP) {
-+ /* ACLs are not supported. Invent an access ACL from
-+ * permissions; let the default ACL default to empty. */
-+ *curr_racl = rsync_acl_initializer;
++ }
++ } else if (errno == ENOTSUP || errno == ENOSYS) {
++ /* ACLs are not supported, so pretend we have a basic ACL. */
+ if (type == SMB_ACL_TYPE_ACCESS)
-+ perms_to_acl(file->mode & ACCESSPERMS, curr_racl);
++ rsync_acl_fake_perms(racl, sxp->st.st_mode);
+ } else {
-+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(type), strerror(errno));
++ rsyserr(FERROR, errno, "get_acl: sys_acl_get_file(%s, %s)",
++ fname, str_acl_type(type));
++ free_acl(sxp);
+ return -1;
+ }
-+ curr_racl++;
-+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
+ return 0;
+}
+
-+/* Send the make_acl()-generated ACLs for this flist entry,
-+ * or clean up after an flist entry that's not being sent (f == -1). */
-+
-+void send_acl(const struct file_struct *file, int f)
-+{
-+ SMB_ACL_TYPE_T type;
-+ rsync_acl *curr_racl;
++/* === Send functions === */
+
-+ if (S_ISLNK(file->mode))
-+ return;
-+
-+ curr_racl = &_curr_rsync_acls[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ int index;
-+ rsync_acl_list *racl_list = rsync_acl_lists(type);
-+ if (f == -1) {
-+ rsync_acl_free(curr_racl);
-+ continue;
-+ }
-+ if ((index = find_matching_rsync_acl(type, racl_list, curr_racl))
-+ != -1) {
-+ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'a' : 'd');
-+ write_int(f, index);
-+ rsync_acl_free(curr_racl);
-+ } else {
-+ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'A' : 'D');
-+ send_rsync_acl(f, curr_racl);
-+ expand_rsync_acl_list(racl_list);
-+ racl_list->racls[racl_list->count++] = *curr_racl;
-+ }
-+ curr_racl++;
-+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
-+}
-+
-+/* The below stuff is only used by the receiver: */
-+
-+/* structure to hold index to rsync_acl_list member corresponding to
-+ * flist->files[i] */
-+
-+typedef struct {
-+ const struct file_struct *file;
-+ int aclidx;
-+} file_acl_index;
-+
-+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ file_acl_index *fileaclidxs;
-+} file_acl_index_list;
-+
-+static file_acl_index_list _file_acl_index_lists[] = {
-+ {0, 0, NULL },/* SMB_ACL_TYPE_ACCESS */
-+ {0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */
-+};
-+
-+static inline file_acl_index_list *file_acl_index_lists(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ?
-+ &_file_acl_index_lists[0] : &_file_acl_index_lists[1];
-+}
++/* The general strategy with the tag_type <-> character mapping is that
++ * lowercase implies that no qualifier follows, where uppercase does.
++ * A similar idiom for the ACL type (access or default) itself, but
++ * lowercase in this instance means there's no ACL following, so the
++ * ACL is a repeat, so the receiver should reuse the last of the same
++ * type ACL. */
+
-+static void expand_file_acl_index_list(file_acl_index_list *fileaclidx_list)
-+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (fileaclidx_list->malloced <= fileaclidx_list->count) {
-+ file_acl_index *new_ptr;
-+ size_t new_size;
-+ if (fileaclidx_list->malloced < 1000)
-+ new_size = fileaclidx_list->malloced + 1000;
++/* Send the ida list over the file descriptor. */
++static void send_ida_entries(int f, const ida_entries *idal, char tag_char)
++{
++ id_access *ida;
++ size_t count = idal->count;
++ for (ida = idal->idas; count--; ida++) {
++ write_byte(f, tag_char);
++ write_byte(f, ida->access);
++ write_int(f, ida->id);
++ /* FIXME: sorta wasteful: we should maybe buffer as
++ * many ids as max(ACL_USER + ACL_GROUP) objects to
++ * keep from making so many calls. */
++ if (tag_char == 'U')
++ add_uid(ida->id);
+ else
-+ new_size = fileaclidx_list->malloced * 2;
-+ new_ptr = realloc_array(fileaclidx_list->fileaclidxs, file_acl_index, new_size);
-+ if (verbose >= 3) {
-+ rprintf(FINFO, "expand_file_acl_index_list to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof fileaclidx_list->fileaclidxs[0],
-+ fileaclidx_list->fileaclidxs ? "" : " not");
-+ }
-+
-+ fileaclidx_list->fileaclidxs = new_ptr;
-+ fileaclidx_list->malloced = new_size;
-+
-+ if (!fileaclidx_list->fileaclidxs)
-+ out_of_memory("expand_file_acl_index_list");
++ add_gid(ida->id);
+ }
+}
+
-+#if 0
-+static void free_file_acl_index_list(file_acl_index_list *fileaclidx_list)
-+{
-+ free(fileaclidx_list->fileaclidxs);
-+ fileaclidx_list->fileaclidxs = NULL;
-+ fileaclidx_list->malloced = 0;
-+}
-+#endif
-+
-+/* lists to hold the SMB_ACL_Ts corresponding to the rsync_acl_list entries */
-+
-+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ SMB_ACL_T *sacls;
-+} smb_acl_list;
-+
-+static smb_acl_list _smb_acl_lists[] = {
-+ { 0, 0, NULL }, /* SMB_ACL_TYPE_ACCESS */
-+ { 0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */
-+};
-+
-+static inline smb_acl_list *smb_acl_lists(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ? &_smb_acl_lists[0] :
-+ &_smb_acl_lists[1];
-+}
-+
-+static void expand_smb_acl_list(smb_acl_list *sacl_list)
++/* Send an rsync ACL over the file descriptor. */
++static void send_rsync_acl(int f, const rsync_acl *racl)
+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (sacl_list->malloced <= sacl_list->count) {
-+ SMB_ACL_T *new_ptr;
-+ size_t new_size;
-+ if (sacl_list->malloced < 1000)
-+ new_size = sacl_list->malloced + 1000;
-+ else
-+ new_size = sacl_list->malloced * 2;
-+ new_ptr = realloc_array(sacl_list->sacls, SMB_ACL_T, new_size);
-+ if (verbose >= 3) {
-+ rprintf(FINFO, "expand_smb_acl_list to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof sacl_list->sacls[0],
-+ sacl_list->sacls ? "" : " not");
-+ }
-+
-+ sacl_list->sacls = new_ptr;
-+ sacl_list->malloced = new_size;
-+
-+ if (!sacl_list->sacls)
-+ out_of_memory("expand_smb_acl_list");
++ size_t count = count_racl_entries(racl);
++ write_int(f, count);
++ if (racl->user_obj != NO_ENTRY) {
++ write_byte(f, 'u');
++ write_byte(f, racl->user_obj);
+ }
-+}
-+
-+#if 0
-+static void free_smb_acl_list(SMB_ACL_TYPE_T type)
-+{
-+ smb_acl_list *sacl_list = smb_acl_lists(type);
-+ SMB_ACL_T *sacl = sacl_list->sacls;
-+ while (sacl_list->count--) {
-+ if (*sacl)
-+ sys_acl_free_acl(*sacl++);
++ send_ida_entries(f, &racl->users, 'U');
++ if (racl->group_obj != NO_ENTRY) {
++ write_byte(f, 'g');
++ write_byte(f, racl->group_obj);
++ }
++ send_ida_entries(f, &racl->groups, 'G');
++ if (racl->mask != NO_ENTRY) {
++ write_byte(f, 'm');
++ write_byte(f, racl->mask);
++ }
++ if (racl->other != NO_ENTRY) {
++ write_byte(f, 'o');
++ write_byte(f, racl->other);
+ }
-+ free(sacl_list->sacls);
-+ sacl_list->sacls = NULL;
-+ sacl_list->malloced = 0;
+}
-+#endif
+
-+/* build an SMB_ACL_T corresponding to an rsync_acl */
-+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl, int perms)
++/* Send the ACL from the statx structure down the indicated file descriptor.
++ * This also frees the ACL data. */
++void send_acl(statx *sxp, int f)
+{
-+ size_t count = racl->count;
-+ rsync_ace *race = racl->races;
-+ const char *errfun = NULL;
-+ int bits;
++ SMB_ACL_TYPE_T type;
++ rsync_acl *racl, *new_racl;
++ item_list *racl_list;
++ int ndx;
+
-+ *smb_acl = sys_acl_init(count);
-+ if (!*smb_acl) {
-+ rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n",
-+ strerror(errno));
-+ return False;
-+ }
++ if (S_ISLNK(sxp->st.st_mode))
++ return;
+
-+ for (; count--; race++) {
-+ SMB_ACL_ENTRY_T entry;
-+ SMB_ACL_PERMSET_T permset;
-+ if (sys_acl_create_entry(smb_acl, &entry)) {
-+ errfun = "sys_acl_create)";
-+ break;
-+ }
-+ if (sys_acl_set_tag_type(entry, race->tag_type)) {
-+ errfun = "sys_acl_set_tag";
-+ break;
-+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ if (sys_acl_set_qualifier(entry, (void*)&race->id)) {
-+ errfun = "sys_acl_set_qualfier";
-+ break;
-+ }
-+ }
-+ if (sys_acl_get_permset(entry, &permset)) {
-+ errfun = "sys_acl_get_permset";
-+ break;
-+ }
-+ if (sys_acl_clear_perms(permset)) {
-+ errfun = "sys_acl_clear_perms";
-+ break;
-+ }
-+ switch (perms >= 0 ? race->tag_type : SMB_ACL_USER_OBJ) {
-+ case SMB_ACL_GROUP_OBJ:
-+ bits = racl->count > 3 ? race->access : (perms >> 3) & 7;
-+ break;
-+ case SMB_ACL_MASK:
-+ bits = (perms >> 3) & 7;
-+ break;
-+ case SMB_ACL_OTHER:
-+ bits = perms & 7;
-+ break;
-+ default:
-+ bits = race->access;
-+ break;
-+ }
-+ if (bits & 4) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_READ)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ }
-+ if (bits & 2) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_WRITE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ }
-+ if (bits & 1) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_EXECUTE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
++ type = SMB_ACL_TYPE_ACCESS;
++ racl = sxp->acc_acl;
++ racl_list = &access_acl_list;
++ do {
++ if (!racl) {
++ racl = new(rsync_acl);
++ if (!racl)
++ out_of_memory("send_acl");
++ *racl = empty_rsync_acl;
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ rsync_acl_fake_perms(racl, sxp->st.st_mode);
++ sxp->acc_acl = racl;
++ } else
++ sxp->def_acl = racl;
+ }
-+ if (sys_acl_set_permset(entry, permset)) {
-+ errfun = "sys_acl_set_permset";
-+ break;
++
++ /* Avoid sending values that can be inferred from other data. */
++ if (type == SMB_ACL_TYPE_ACCESS && protocol_version >= 30)
++ rsync_acl_strip_perms(racl);
++ if ((ndx = find_matching_rsync_acl(type, racl_list, racl)) != -1) {
++ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'a' : 'd');
++ write_int(f, ndx);
++ } else {
++ new_racl = EXPAND_ITEM_LIST(racl_list, rsync_acl, 1000);
++ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'A' : 'D');
++ send_rsync_acl(f, racl);
++ *new_racl = *racl;
++ *racl = empty_rsync_acl;
+ }
-+ }
-+ if (errfun) {
-+ sys_acl_free_acl(*smb_acl);
-+ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
-+ strerror(errno));
-+ return False;
-+ }
-+ return True;
++ racl = sxp->def_acl;
++ racl_list = &default_acl_list;
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
++
++ free_acl(sxp);
+}
+
-+static void receive_rsync_acl(rsync_acl *racl, mode_t mode, int f)
++/* === Receive functions === */
++
++static void receive_rsync_acl(rsync_acl *racl, int f, SMB_ACL_TYPE_T type)
+{
-+ rsync_ace *user = NULL, *group = NULL, *other = NULL, *mask = NULL;
-+ rsync_ace *race;
-+ BOOL need_sort = 0;
++ static item_list temp_ida_list = EMPTY_ITEM_LIST;
++ SMB_ACL_TAG_T tag_type = 0, prior_list_type = 0;
++ uchar computed_mask_bits = 0;
++ id_access *ida;
+ size_t count;
+
+ if (!(count = read_int(f)))
+ return;
+
+ while (count--) {
-+ uchar tag = read_byte(f);
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
++ char tag = read_byte(f);
++ uchar access = read_byte(f);
++ if (access & ~ (4 | 2 | 1)) {
++ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
++ access);
++ exit_cleanup(RERR_STREAMIO);
++ }
+ switch (tag) {
+ case 'u':
-+ race->tag_type = SMB_ACL_USER_OBJ;
-+ user = race;
-+ break;
++ if (racl->user_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate USER_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->user_obj = access;
++ continue;
+ case 'U':
-+ race->tag_type = SMB_ACL_USER;
++ tag_type = SMB_ACL_USER;
+ break;
+ case 'g':
-+ race->tag_type = SMB_ACL_GROUP_OBJ;
-+ group = race;
-+ break;
++ if (racl->group_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate GROUP_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->group_obj = access;
++ continue;
+ case 'G':
-+ race->tag_type = SMB_ACL_GROUP;
-+ break;
-+ case 'o':
-+ race->tag_type = SMB_ACL_OTHER;
-+ other = race;
++ tag_type = SMB_ACL_GROUP;
+ break;
+ case 'm':
-+ race->tag_type = SMB_ACL_MASK;
-+ mask = race;
-+ break;
++ if (racl->mask != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate MASK entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->mask = access;
++ continue;
++ case 'o':
++ if (racl->other != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate OTHER entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->other = access;
++ continue;
+ default:
+ rprintf(FERROR, "receive_rsync_acl: unknown tag %c\n",
+ tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
-+ race->access = read_byte(f);
-+ if (race->access & ~ (4 | 2 | 1)) {
-+ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
-+ race->access);
-+ exit_cleanup(RERR_STREAMIO);
++ if (tag_type != prior_list_type) {
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
++ prior_list_type = tag_type;
+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ race->id = read_int(f);
-+ }
-+ }
-+ if (!user) {
-+ expand_rsync_acl(racl);
-+ user = &racl->races[racl->count++];
-+ user->tag_type = SMB_ACL_USER_OBJ;
-+ user->access = (mode >> 6) & 7;
-+ need_sort = True;
-+ }
-+ if (!group) {
-+ expand_rsync_acl(racl);
-+ group = &racl->races[racl->count++];
-+ group->tag_type = SMB_ACL_GROUP_OBJ;
-+ group->access = (mode >> 3) & 7;
-+ need_sort = True;
-+ }
-+ if (!other) {
-+ expand_rsync_acl(racl);
-+ other = &racl->races[racl->count++];
-+ other->tag_type = SMB_ACL_OTHER;
-+ other->access = mode & 7;
-+ need_sort = True;
-+ }
-+#if ACLS_NEED_MASK
-+ if (!mask) {
-+ expand_rsync_acl(racl);
-+ mask = &racl->races[racl->count++];
-+ mask->tag_type = SMB_ACL_MASK;
-+ mask->access = (mode >> 3) & 7;
-+ need_sort = True;
++ ida = EXPAND_ITEM_LIST(&temp_ida_list, id_access, -10);
++ ida->access = access;
++ ida->id = read_int(f);
++ computed_mask_bits |= access;
+ }
-+#else
-+ /* If we, a system without ACLS_NEED_MASK, received data from a
-+ * system that has masks, throw away the extraneous CLASS_OBJs. */
-+ if (mask && racl->count == 4) {
-+ /* mask off group perms with it first */
-+ group->access &= mask->access;
-+ /* dump mask; re-slot any followers-on */
-+ racl->count--;
-+ if (mask != &racl->races[racl->count]) {
-+ *mask = racl->races[racl->count];
-+ need_sort = True;
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
++
++ if (type == SMB_ACL_TYPE_DEFAULT) {
++ /* Ensure that these are never unset. */
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = 7;
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = 0;
++ if (racl->other == NO_ENTRY)
++ racl->other = 0;
++ }
++
++ if (!racl->users.count && !racl->groups.count) {
++ /* If we received a superfluous mask, throw it away. */
++ if (racl->mask != NO_ENTRY) {
++ /* Mask off the group perms with it first. */
++ racl->group_obj &= racl->mask | NO_ENTRY;
++ racl->mask = NO_ENTRY;
+ }
-+ }
-+#endif
-+ if (need_sort)
-+ sort_rsync_acl(racl);
++ } else if (racl->mask == NO_ENTRY) /* Must be non-empty with lists. */
++ racl->mask = computed_mask_bits | (racl->group_obj & 7);
+}
+
-+/* receive and build the rsync_acl_lists */
-+
++/* Receive the ACL info the sender has included for this file-list entry. */
+void receive_acl(struct file_struct *file, int f)
+{
+ SMB_ACL_TYPE_T type;
-+ char *fname;
++ item_list *racl_list;
+
+ if (S_ISLNK(file->mode))
+ return;
+
-+ fname = f_name(file, NULL);
+ type = SMB_ACL_TYPE_ACCESS;
++ racl_list = &access_acl_list;
+ do {
-+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(type);
-+ uchar tag;
-+ expand_file_acl_index_list(fileaclidx_list);
++ char tag = read_byte(f);
++ int ndx;
+
-+ tag = read_byte(f);
+ if (tag == 'A' || tag == 'a') {
+ if (type != SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: duplicate access ACL\n",
-+ fname);
++ f_name(file, NULL));
+ exit_cleanup(RERR_STREAMIO);
+ }
+ } else if (tag == 'D' || tag == 'd') {
+ if (type == SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: expecting access ACL; got default\n",
-+ fname);
++ f_name(file, NULL));
+ exit_cleanup(RERR_STREAMIO);
+ }
+ } else {
+ rprintf(FERROR, "receive_acl %s: unknown ACL type tag: %c\n",
-+ fname, tag);
++ f_name(file, NULL), tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
+ if (tag == 'A' || tag == 'D') {
-+ rsync_acl racl = rsync_acl_initializer;
-+ rsync_acl_list *racl_list = rsync_acl_lists(type);
-+ smb_acl_list *sacl_list = smb_acl_lists(type);
-+ fileaclidx_list->fileaclidxs[fileaclidx_list->count].
-+ aclidx = racl_list->count;
-+ fileaclidx_list->fileaclidxs[fileaclidx_list->count++].
-+ file = file;
-+ receive_rsync_acl(&racl, file->mode, f);
-+ expand_rsync_acl_list(racl_list);
-+ racl_list->racls[racl_list->count++] = racl;
-+ expand_smb_acl_list(sacl_list);
-+ sacl_list->sacls[sacl_list->count++] = NULL;
++ acl_duo *duo_item;
++ ndx = racl_list->count;
++ duo_item = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000);
++ duo_item->racl = empty_rsync_acl;
++ receive_rsync_acl(&duo_item->racl, f, type);
++ duo_item->sacl = NULL;
+ } else {
-+ int index = read_int(f);
-+ rsync_acl_list *racl_list = rsync_acl_lists(type);
-+ if ((size_t) index >= racl_list->count) {
++ ndx = read_int(f);
++ if (ndx < 0 || (size_t)ndx >= racl_list->count) {
+ rprintf(FERROR, "receive_acl %s: %s ACL index %d out of range\n",
-+ fname,
-+ str_acl_type(type),
-+ index);
++ f_name(file, NULL), str_acl_type(type), ndx);
+ exit_cleanup(RERR_STREAMIO);
+ }
-+ fileaclidx_list->fileaclidxs[fileaclidx_list->count].
-+ aclidx = index;
-+ fileaclidx_list->fileaclidxs[fileaclidx_list->count++].
-+ file = file;
+ }
++ if (type == SMB_ACL_TYPE_ACCESS)
++ F_ACL(file) = ndx;
++ else
++ F_DEF_ACL(file) = ndx;
++ racl_list = &default_acl_list;
+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
+}
+
-+static int file_acl_index_list_sorter(const void *f1, const void *f2)
-+{
-+ const file_acl_index *fileaclidx1 = (const file_acl_index *)f1;
-+ const file_acl_index *fileaclidx2 = (const file_acl_index *)f2;
-+ return fileaclidx1->file == fileaclidx2->file ? 0 :
-+ fileaclidx1->file < fileaclidx2->file ? -1 : 1;
-+}
-+
-+void sort_file_acl_index_lists()
++/* Turn the ACL data in statx into cached ACL data, setting the index
++ * values in the file struct. */
++void cache_acl(struct file_struct *file, statx *sxp)
+{
+ SMB_ACL_TYPE_T type;
++ rsync_acl *racl;
++ item_list *racl_list;
++ int ndx;
+
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(type);
-+ if (!fileaclidx_list->count)
-+ continue;
-+ qsort(fileaclidx_list->fileaclidxs, fileaclidx_list->count,
-+ sizeof fileaclidx_list->fileaclidxs[0],
-+ &file_acl_index_list_sorter);
-+ } while (BUMP_TYPE(type));
-+}
-+
-+static int find_file_acl_index(const file_acl_index_list *fileaclidx_list,
-+ const struct file_struct *file) {
-+ int low = 0, high = fileaclidx_list->count;
-+ const struct file_struct *file_mid;
-+ if (!high--)
-+ return -1;
-+ do {
-+ int mid = (high + low) / 2;
-+ file_mid = fileaclidx_list->fileaclidxs[mid].file;
-+ if (file_mid == file)
-+ return fileaclidx_list->fileaclidxs[mid].aclidx;
-+ if (file_mid > file)
-+ high = mid - 1;
-+ else
-+ low = mid + 1;
-+ } while (low < high);
-+ if (low == high) {
-+ file_mid = fileaclidx_list->fileaclidxs[low].file;
-+ if (file_mid == file)
-+ return fileaclidx_list->fileaclidxs[low].aclidx;
-+ }
-+ rprintf(FERROR,
-+ "find_file_acl_index: can't find entry for file in list\n");
-+ exit_cleanup(RERR_STREAMIO);
-+ return -1;
-+}
-+
-+/* for duplicating ACLs on backups when using backup_dir */
-+
-+int dup_acl(const char *orig, const char *bak, mode_t mode)
-+{
-+ SMB_ACL_TYPE_T type;
-+ int ret = 0;
++ if (S_ISLNK(file->mode))
++ return;
+
+ type = SMB_ACL_TYPE_ACCESS;
++ racl = sxp->acc_acl;
++ racl_list = &access_acl_list;
+ do {
-+ SMB_ACL_T sacl_orig, sacl_bak;
-+ rsync_acl racl_orig, racl_bak;
-+ if (!(sacl_orig = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ orig, str_acl_type(type), strerror(errno));
-+ ret = -1;
-+ continue;
-+ }
-+ if (!(sacl_bak = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s. ignoring\n",
-+ bak, str_acl_type(type), strerror(errno));
-+ ret = -1;
-+ /* try to forge on through */
-+ }
-+ if (!unpack_smb_acl(&racl_orig, sacl_orig)) {
-+ ret = -1;
-+ goto out_with_sacls;
++ if (!racl)
++ ndx = -1;
++ else if ((ndx = find_matching_rsync_acl(type, racl_list, racl)) == -1) {
++ acl_duo *new_duo;
++ ndx = racl_list->count;
++ new_duo = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000);
++ new_duo->racl = *racl;
++ new_duo->sacl = NULL;
++ *racl = empty_rsync_acl;
+ }
-+ if (sacl_bak) {
-+ if (!unpack_smb_acl(&racl_bak, sacl_bak)) {
-+ ret = -1;
-+ goto out_with_one_racl;
-+ }
-+ if (rsync_acls_equal(&racl_orig, &racl_bak))
-+ goto out_with_all;
-+ } else {
-+ ; /* presume they're unequal */
-+ }
-+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_orig.count) {
-+ if (sys_acl_delete_def_file(bak) < 0) {
-+ rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n",
-+ bak, strerror(errno));
-+ ret = -1;
-+ }
-+ } else if (sys_acl_set_file(bak, type, sacl_bak) < 0) {
-+ rprintf(FERROR, "dup_acl: sys_acl_set_file(%s, %s): %s\n",
-+ bak, str_acl_type(type), strerror(errno));
-+ ret = -1;
-+ }
-+ out_with_all:
-+ if (sacl_bak)
-+ rsync_acl_free(&racl_bak);
-+ out_with_one_racl:
-+ rsync_acl_free(&racl_orig);
-+ out_with_sacls:
-+ if (sacl_bak)
-+ sys_acl_free_acl(sacl_bak);
-+ /* out_with_one_sacl: */
-+ if (sacl_orig)
-+ sys_acl_free_acl(sacl_orig);
-+ } while (BUMP_TYPE(type) && S_ISDIR(mode));
++ if (type == SMB_ACL_TYPE_ACCESS)
++ F_ACL(file) = ndx;
++ else
++ F_DEF_ACL(file) = ndx;
++ racl = sxp->def_acl;
++ racl_list = &default_acl_list;
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
-+ return ret;
++ free_acl(sxp);
+}
+
-+/* Stuff for redirecting calls to set_acl() from set_file_attrs()
-+ * for keep_backup(). */
-+static const struct file_struct *backup_orig_file = NULL;
-+static const char null_string[] = "";
-+static const char *backup_orig_fname = null_string;
-+static const char *backup_dest_fname = null_string;
-+static SMB_ACL_T _backup_sacl[] = { NULL, NULL };
-+
-+void push_keep_backup_acl(const struct file_struct *file,
-+ const char *orig, const char *dest)
++static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode)
+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
++ SMB_ACL_ENTRY_T entry;
++ const char *errfun;
++ int rc;
+
-+ backup_orig_file = file;
-+ backup_orig_fname = orig;
-+ backup_dest_fname = dest;
++ if (S_ISDIR(mode)) {
++ /* If the sticky bit is going on, it's not safe to allow all
++ * the new ACL to go into effect before it gets set. */
++#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
++ if (mode & S_ISVTX)
++ mode &= ~0077;
++#else
++ if (mode & S_ISVTX && !(old_mode & S_ISVTX))
++ mode &= ~0077;
++ } else {
++ /* If setuid or setgid is going off, it's not safe to allow all
++ * the new ACL to go into effect before they get cleared. */
++ if ((old_mode & S_ISUID && !(mode & S_ISUID))
++ || (old_mode & S_ISGID && !(mode & S_ISGID)))
++ mode &= ~0077;
++#endif
++ }
+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode)) {
-+ *sacl = NULL;
++ errfun = "sys_acl_get_entry";
++ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
++ rc == 1;
++ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
++ errfun = "sys_acl_get_tag_type";
+ break;
+ }
-+ if (!(*sacl = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR,
-+ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ orig, str_acl_type(type),
-+ strerror(errno));
-+ }
-+ } while (BUMP_TYPE(type));
-+}
-+
-+static int set_keep_backup_acl()
-+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
-+ int ret = 0;
-+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (*sacl
-+ && sys_acl_set_file(backup_dest_fname, type, *sacl) < 0) {
-+ rprintf(FERROR,
-+ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ backup_dest_fname,
-+ str_acl_type(type),
-+ strerror(errno));
-+ ret = -1;
++ switch (tag_type) {
++ case SMB_ACL_USER_OBJ:
++ COE2( store_access_in_entry,((mode >> 6) & 7, entry) );
++ break;
++ case SMB_ACL_GROUP_OBJ:
++ /* group is only empty when identical to group perms. */
++ if (racl->group_obj != NO_ENTRY)
++ break;
++ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
++ break;
++ case SMB_ACL_MASK:
++#ifndef ACLS_NEED_MASK
++ /* mask is only empty when we don't need it. */
++ if (racl->mask == NO_ENTRY)
++ break;
++#endif
++ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
++ break;
++ case SMB_ACL_OTHER:
++ COE2( store_access_in_entry,(mode & 7, entry) );
++ break;
+ }
-+ } while (BUMP_TYPE(type));
-+
-+ return ret;
-+}
-+
-+void cleanup_keep_backup_acl()
-+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
++ }
++ if (rc) {
++ error_exit:
++ if (errfun) {
++ rsyserr(FERROR, errno, "change_sacl_perms: %s()",
++ errfun);
++ }
++ return (mode_t)~0;
++ }
+
-+ backup_orig_file = NULL;
-+ backup_orig_fname = null_string;
-+ backup_dest_fname = null_string;
++#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
++ /* Ensure that chmod() will be called to restore any lost setid bits. */
++ if (old_mode & (S_ISUID | S_ISGID | S_ISVTX)
++ && BITS_EQUAL(old_mode, mode, CHMOD_BITS))
++ old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
++#endif
+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (*sacl) {
-+ sys_acl_free_acl(*sacl);
-+ *sacl = NULL;
-+ }
-+ } while (BUMP_TYPE(type));
++ /* Return the mode of the file on disk, as we will set them. */
++ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
+}
+
-+/* set ACL on rsync-ed or keep_backup-ed file */
-+
-+int set_acl(const char *fname, const struct file_struct *file, mode_t old_mode)
++/* Set ACL on indicated filename.
++ *
++ * This sets extended access ACL entries and default ACL. If convenient,
++ * it sets permission bits along with the access ACL and signals having
++ * done so by modifying sxp->st.st_mode.
++ *
++ * Returns 1 for unchanged, 0 for changed, -1 for failed. Call this
++ * with fname set to NULL to just check if the ACL is unchanged. */
++int set_acl(const char *fname, const struct file_struct *file, statx *sxp)
+{
+ int unchanged = 1;
+ SMB_ACL_TYPE_T type;
+
-+ if (dry_run || S_ISLNK(file->mode))
-+ return 1; /* FIXME: --dry-run needs to compute this value */
-+
-+ if (file == backup_orig_file) {
-+ if (!strcmp(fname, backup_dest_fname))
-+ return set_keep_backup_acl();
++ if (!dry_run && (read_only || list_only)) {
++ errno = EROFS;
++ return -1;
+ }
++
++ if (S_ISLNK(file->mode))
++ return 1;
++
+ type = SMB_ACL_TYPE_ACCESS;
+ do {
-+ SMB_ACL_T sacl_orig, *sacl_new;
-+ rsync_acl racl_orig, *racl_new;
-+ int aclidx = find_file_acl_index(file_acl_index_lists(type),
-+ file);
-+ BOOL ok;
-+ racl_new = &(rsync_acl_lists(type)->racls[aclidx]);
-+ sacl_new = &(smb_acl_lists(type)->sacls[aclidx]);
-+ sacl_orig = sys_acl_get_file(fname, type);
-+ if (!sacl_orig) {
-+ rprintf(FERROR, "set_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(type), strerror(errno));
-+ unchanged = -1;
-+ continue;
-+ }
-+ ok = unpack_smb_acl(&racl_orig, sacl_orig);
-+ sys_acl_free_acl(sacl_orig);
-+ if (!ok) {
-+ unchanged = -1;
-+ continue;
-+ }
-+ ok = rsync_acls_equal(&racl_orig, racl_new);
-+ rsync_acl_free(&racl_orig);
-+ if (ok)
-+ continue;
-+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_new->count) {
-+ if (sys_acl_delete_def_file(fname) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
-+ fname, strerror(errno));
-+ unchanged = -1;
++ acl_duo *duo_item;
++ int32 ndx;
++ BOOL eq;
++
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ ndx = F_ACL(file);
++ if (ndx < 0 || (size_t)ndx >= access_acl_list.count)
+ continue;
-+ }
++ duo_item = access_acl_list.items;
++ duo_item += ndx;
++ eq = sxp->acc_acl
++ && rsync_acl_equal_enough(sxp->acc_acl, &duo_item->racl, file->mode);
+ } else {
-+ /* We set the mask (if it exists) to the file's group
-+ * permissions (as they currently exist on the disk).
-+ * This avoids a permission race, particularly for new
-+ * files (where the current group permissions == 0).
-+ * If this is not the right value, the upcoming chmod()
-+ * call will change it. */
-+ int perms = type == SMB_ACL_TYPE_ACCESS
-+ ? (int)(old_mode & CHMOD_BITS) : -1;
-+ if (!*sacl_new
-+ && !pack_smb_acl(sacl_new, racl_new, perms)) {
-+ unchanged = -1;
-+ continue;
-+ }
-+ if (sys_acl_set_file(fname, type, *sacl_new) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
-+ fname, str_acl_type(type),
-+ strerror(errno));
-+ unchanged = -1;
++ ndx = F_DEF_ACL(file);
++ if (ndx < 0 || (size_t)ndx >= default_acl_list.count)
+ continue;
++ duo_item = default_acl_list.items;
++ duo_item += ndx;
++ eq = sxp->def_acl
++ && rsync_acl_equal(sxp->def_acl, &duo_item->racl);
++ }
++ if (eq)
++ continue;
++ if (!dry_run && fname) {
++ if (type == SMB_ACL_TYPE_DEFAULT
++ && duo_item->racl.user_obj == NO_ENTRY) {
++ if (sys_acl_delete_def_file(fname) < 0) {
++ rsyserr(FERROR, errno, "set_acl: sys_acl_delete_def_file(%s)",
++ fname);
++ unchanged = -1;
++ continue;
++ }
++ } else {
++ mode_t cur_mode = sxp->st.st_mode;
++ if (!duo_item->sacl
++ && !pack_smb_acl(&duo_item->sacl, &duo_item->racl)) {
++ unchanged = -1;
++ continue;
++ }
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ cur_mode = change_sacl_perms(duo_item->sacl, &duo_item->racl,
++ cur_mode, file->mode);
++ if (cur_mode == (mode_t)~0)
++ continue;
++ }
++ if (sys_acl_set_file(fname, type, duo_item->sacl) < 0) {
++ rsyserr(FERROR, errno, "set_acl: sys_acl_set_file(%s, %s)",
++ fname, str_acl_type(type));
++ unchanged = -1;
++ continue;
++ }
++ if (type == SMB_ACL_TYPE_ACCESS)
++ sxp->st.st_mode = cur_mode;
+ }
+ }
+ if (unchanged == 1)
+ return unchanged;
+}
+
-+/* Enumeration functions for uid mapping: */
++/* === Enumeration functions for uid mapping === */
+
+/* Context -- one and only one. Should be cycled through once on uid
+ * mapping and once on gid mapping. */
-+static rsync_acl_list *_enum_racl_lists[] = {
-+ &_rsync_acl_lists[0], &_rsync_acl_lists[1], NULL
++static item_list *_enum_racl_lists[] = {
++ &access_acl_list, &default_acl_list, NULL
+};
+
-+static rsync_acl_list **enum_racl_list = &_enum_racl_lists[0];
++static item_list **enum_racl_list = &_enum_racl_lists[0];
++static int enum_ida_index = 0;
+static size_t enum_racl_index = 0;
-+static size_t enum_race_index = 0;
+
-+/* This returns the next tag_type id from the given acl for the next entry,
++/* This returns the next tag_type id from the given ACL for the next entry,
+ * or it returns 0 if there are no more tag_type ids in the acl. */
-+
+static id_t *next_ace_id(SMB_ACL_TAG_T tag_type, const rsync_acl *racl)
+{
-+ for (; enum_race_index < racl->count; enum_race_index++) {
-+ rsync_ace *race = &racl->races[enum_race_index++];
-+ if (race->tag_type == tag_type)
-+ return &race->id;
++ const ida_entries *idal = tag_type == SMB_ACL_USER ? &racl->users : &racl->groups;
++ if (enum_ida_index < idal->count) {
++ id_access *ida = &idal->idas[enum_ida_index++];
++ return &ida->id;
+ }
-+ enum_race_index = 0;
++ enum_ida_index = 0;
+ return NULL;
+}
+
-+static id_t *next_acl_id(SMB_ACL_TAG_T tag_type, const rsync_acl_list *racl_list)
++static id_t *next_acl_id(SMB_ACL_TAG_T tag_type, const item_list *racl_list)
+{
+ for (; enum_racl_index < racl_list->count; enum_racl_index++) {
-+ rsync_acl *racl = &racl_list->racls[enum_racl_index];
-+ id_t *id = next_ace_id(tag_type, racl);
-+ if (id)
++ id_t *id;
++ acl_duo *duo_item = racl_list->items;
++ duo_item += enum_racl_index;
++ if ((id = next_ace_id(tag_type, &duo_item->racl)) != NULL)
+ return id;
+ }
+ enum_racl_index = 0;
+ return next_acl_list_id(SMB_ACL_GROUP);
+}
+
-+#define PERMS_SPLICE(perms,newbits,where) (((perms) & ~(7 << (where))) | ((newbits) << (where)))
-+
++/* This is used by dest_mode(). */
+int default_perms_for_dir(const char *dir)
+{
+ rsync_acl racl;
+ SMB_ACL_T sacl;
-+ BOOL ok, saw_mask = False;
-+ size_t i;
++ BOOL ok;
+ int perms;
+
+ if (dir == NULL)
+ /* Couldn't get an ACL. Darn. */
+ switch (errno) {
+ case ENOTSUP:
-+ /* ACLs are disabled. We could yell at the user to turn them on, but... */
++ case ENOSYS:
++ /* No ACLs are available. */
+ break;
+ case ENOENT:
+ if (dry_run) {
+ }
+
+ /* Convert it. */
++ racl = empty_rsync_acl;
+ ok = unpack_smb_acl(&racl, sacl);
+ sys_acl_free_acl(sacl);
+ if (!ok) {
+ return perms;
+ }
+
-+ /* Look at each default ACL entry and possibly modify three bits of `perms' accordingly.
-+ * If there's "no" default ACL, there will be zero entries and the umask-based perms is unchanged. */
-+ for (i = 0; i < racl.count; i++) {
-+ switch (racl.races[i].tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 6);
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ if (!saw_mask)
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_MASK:
-+ saw_mask = True;
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_OTHER:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 0);
-+ break;
-+ default:
-+ break;
-+ }
++ /* Apply the permission-bit entries of the default ACL, if any. */
++ if (racl.user_obj != NO_ENTRY) {
++ perms = rsync_acl_get_perms(&racl);
++ if (verbose > 2)
++ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ }
++
+ rsync_acl_free(&racl);
-+ if (verbose > 2)
-+ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ return perms;
+}
+
+#endif /* SUPPORT_ACLS */
--- old/backup.c
+++ new/backup.c
-@@ -28,6 +28,7 @@ extern char *backup_suffix;
- extern char *backup_dir;
+@@ -23,6 +23,7 @@
+ extern int verbose;
extern int am_root;
+extern int preserve_acls;
extern int preserve_devices;
extern int preserve_specials;
extern int preserve_links;
-@@ -132,6 +133,10 @@ static int make_bak_dir(char *fullpath)
+@@ -93,7 +94,8 @@ path
+ ****************************************************************************/
+ static int make_bak_dir(char *fullpath)
+ {
+- STRUCT_STAT st;
++ statx sx;
++ struct file_struct *file;
+ char *rel = fullpath + backup_dir_len;
+ char *end = rel + strlen(rel);
+ char *p = end;
+@@ -125,15 +127,24 @@ static int make_bak_dir(char *fullpath)
+ if (p >= rel) {
+ /* Try to transfer the directory settings of the
+ * actual dir that the files are coming from. */
+- if (do_stat(rel, &st) < 0) {
++ if (do_stat(rel, &sx.st) < 0) {
+ rsyserr(FERROR, errno,
+ "make_bak_dir stat %s failed",
+ full_fname(rel));
} else {
- do_lchown(fullpath, st.st_uid, st.st_gid);
- do_chmod(fullpath, st.st_mode);
+- do_lchown(fullpath, st.st_uid, st.st_gid);
+-#ifdef HAVE_CHMOD
+- do_chmod(fullpath, st.st_mode);
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ dup_acl(end, fullpath, st.st_mode);
++ sx.acc_acl = sx.def_acl = NULL;
+#endif
++ if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS)))
++ continue;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls) {
++ get_acl(rel, &sx);
++ cache_acl(file, &sx);
++ }
+ #endif
++ set_file_attrs(fullpath, file, NULL, 0);
++ free(file);
}
}
*p = '/';
-@@ -185,6 +190,11 @@ static int keep_backup(char *fname)
- if (!(buf = get_backup_name(fname)))
+@@ -171,15 +182,18 @@ static int robust_move(const char *src,
+ * We will move the file to be deleted into a parallel directory tree. */
+ static int keep_backup(const char *fname)
+ {
+- STRUCT_STAT st;
++ statx sx;
+ struct file_struct *file;
+ char *buf;
+ int kept = 0;
+ int ret_code;
+
+ /* return if no file to keep */
+- if (do_lstat(fname, &st) < 0)
++ if (do_lstat(fname, &sx.st) < 0)
+ return 1;
++#ifdef SUPPORT_ACLS
++ sx.acc_acl = sx.def_acl = NULL;
++#endif
+
+ if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS)))
+ return 1; /* the file could have disappeared */
+@@ -189,6 +203,13 @@ static int keep_backup(const char *fname
return 0;
+ }
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ push_keep_backup_acl(file, fname, buf);
++ if (preserve_acls) {
++ get_acl(fname, &sx);
++ cache_acl(file, &sx);
++ }
+#endif
+
/* Check to see if this is a device file, or link */
if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) {
-@@ -260,6 +270,10 @@ static int keep_backup(char *fname)
- }
- }
- set_file_attrs(buf, file, NULL, 0);
-+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ cleanup_keep_backup_acl();
-+#endif
- free(file);
+@@ -260,7 +281,7 @@ static int keep_backup(const char *fname
+ if (robust_move(fname, buf) != 0) {
+ rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"",
+ full_fname(fname), buf);
+- } else if (st.st_nlink > 1) {
++ } else if (sx.st.st_nlink > 1) {
+ /* If someone has hard-linked the file into the backup
+ * dir, rename() might return success but do nothing! */
+ robust_unlink(fname); /* Just in case... */
+--- old/compat.c
++++ new/compat.c
+@@ -56,6 +56,8 @@ void setup_protocol(int f_out,int f_in)
+ preserve_uid = ++file_extra_cnt;
+ if (preserve_gid)
+ preserve_gid = ++file_extra_cnt;
++ if (preserve_acls && !am_sender)
++ preserve_acls = ++file_extra_cnt;
- if (verbose > 1) {
+ if (remote_protocol == 0) {
+ if (!read_batch)
--- old/configure.in
+++ new/configure.in
-@@ -482,6 +482,11 @@ if test x"$ac_cv_func_strcasecmp" = x"no
+@@ -542,6 +542,11 @@ if test x"$ac_cv_func_strcasecmp" = x"no
AC_CHECK_LIB(resolv, strcasecmp)
fi
dnl At the moment we don't test for a broken memcmp(), because all we
dnl need to do is test for equality, not comparison, and it seems that
dnl every platform has a memcmp that can do at least that.
-@@ -738,6 +743,77 @@ AC_SUBST(OBJ_RESTORE)
+@@ -806,6 +811,78 @@ AC_SUBST(OBJ_RESTORE)
AC_SUBST(CC_SHOBJ_FLAG)
AC_SUBST(BUILD_POPT)
-+AC_CHECK_HEADERS(sys/acl.h)
++AC_CHECK_HEADERS(sys/acl.h acl/libacl.h)
+AC_CHECK_FUNCS(_acl __acl _facl __facl)
+#################################################
+# check for ACL support
+ AC_MSG_RESULT(Using UnixWare ACLs)
+ AC_DEFINE(HAVE_UNIXWARE_ACLS, 1, [true if you have UnixWare ACLs])
+ ;;
-+ *solaris*)
++ *solaris*|*cygwin*)
+ AC_MSG_RESULT(Using solaris ACLs)
+ AC_DEFINE(HAVE_SOLARIS_ACLS, 1, [true if you have solaris ACLs])
+ ;;
+#include <sys/acl.h>],
+[ acl_t acl; int entry_id; acl_entry_t *entry_p; return acl_get_entry( acl, entry_id, entry_p);],
+samba_cv_HAVE_POSIX_ACLS=yes,samba_cv_HAVE_POSIX_ACLS=no)])
++ AC_MSG_CHECKING(ACL test results)
+ if test x"$samba_cv_HAVE_POSIX_ACLS" = x"yes"; then
+ AC_MSG_RESULT(Using posix ACLs)
+ AC_DEFINE(HAVE_POSIX_ACLS, 1, [true if you have posix ACLs])
--- old/flist.c
+++ new/flist.c
-@@ -44,6 +44,7 @@ extern int filesfrom_fd;
+@@ -42,6 +42,7 @@ extern int filesfrom_fd;
extern int one_file_system;
extern int copy_dirlinks;
extern int keep_dirlinks;
extern int preserve_links;
extern int preserve_hard_links;
extern int preserve_devices;
-@@ -970,6 +971,11 @@ static struct file_struct *send_file_nam
+@@ -153,6 +154,8 @@ static void list_file_entry(struct file_
+ permstring(permbuf, f->mode);
+ len = F_LENGTH(f);
+
++ /* TODO: indicate '+' if the entry has an ACL. */
++
+ #ifdef SUPPORT_LINKS
+ if (preserve_links && S_ISLNK(f->mode)) {
+ rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
+@@ -715,6 +718,12 @@ static struct file_struct *recv_file_ent
+ }
+ #endif
+
++#ifdef SUPPORT_ACLS
++ /* We need one or two index int32s when we're preserving ACLs. */
++ if (preserve_acls)
++ extra_len += (S_ISDIR(mode) ? 2 : 1) * EXTRA_LEN;
++#endif
++
+ if (always_checksum && S_ISREG(mode))
+ extra_len += SUM_EXTRA_CNT * EXTRA_LEN;
+
+@@ -852,6 +861,11 @@ static struct file_struct *recv_file_ent
+ read_buf(f, bp, checksum_len);
+ }
+
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ receive_acl(file, f);
++#endif
++
+ if (S_ISREG(mode) || S_ISLNK(mode))
+ stats.total_size += file_length;
+
+@@ -1123,6 +1137,9 @@ static struct file_struct *send_file_nam
+ int flags, int filter_flags)
+ {
+ struct file_struct *file;
++#ifdef SUPPORT_ACLS
++ statx sx;
++#endif
+
+ file = make_file(fname, flist, stp, flags, filter_flags);
+ if (!file)
+@@ -1131,12 +1148,26 @@ static struct file_struct *send_file_nam
if (chmod_modes && !S_ISLNK(file->mode))
file->mode = tweak_mode(file->mode, chmod_modes);
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls && make_acl(file, fname) < 0)
-+ return NULL;
++ if (preserve_acls && f >= 0) {
++ sx.st.st_mode = file->mode;
++ sx.acc_acl = sx.def_acl = NULL;
++ if (get_acl(fname, &sx) < 0)
++ return NULL;
++ }
++#endif
++
+ maybe_emit_filelist_progress(flist->count + flist_count_offset);
+
+ flist_expand(flist);
+ flist->files[flist->count++] = file;
+- if (f >= 0)
++ if (f >= 0) {
+ send_file_entry(f, file, flist->count - 1);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ send_acl(&sx, f);
++#endif
++ }
+ return file;
+ }
+
+--- old/generator.c
++++ new/generator.c
+@@ -36,6 +36,7 @@ extern int do_progress;
+ extern int relative_paths;
+ extern int implied_dirs;
+ extern int keep_dirlinks;
++extern int preserve_acls;
+ extern int preserve_links;
+ extern int preserve_devices;
+ extern int preserve_specials;
+@@ -90,6 +91,7 @@ extern int force_delete;
+ extern int one_file_system;
+ extern struct stats stats;
+ extern dev_t filesystem_dev;
++extern mode_t orig_umask;
+ extern char *backup_dir;
+ extern char *backup_suffix;
+ extern int backup_suffix_len;
+@@ -509,22 +511,27 @@ static void do_delete_pass(struct file_l
+ rprintf(FINFO, " \r");
+ }
+
+-int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st)
++int unchanged_attrs(struct file_struct *file, statx *sxp)
+ {
+- if (preserve_perms && !BITS_EQUAL(st->st_mode, file->mode, CHMOD_BITS))
++ if (preserve_perms && !BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS))
+ return 0;
+
+- if (am_root && preserve_uid && st->st_uid != F_UID(file))
++ if (am_root && preserve_uid && sxp->st.st_uid != F_UID(file))
+ return 0;
+
+- if (preserve_gid && F_GID(file) != GID_NONE && st->st_gid != F_GID(file))
++ if (preserve_gid && F_GID(file) != GID_NONE && sxp->st.st_gid != F_GID(file))
+ return 0;
+
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && set_acl(NULL, file, sxp) == 0)
++ return 0;
++#endif
++
+ return 1;
+ }
+
+ void itemize(struct file_struct *file, int ndx, int statret,
+- STRUCT_STAT *st, int32 iflags, uchar fnamecmp_type,
++ statx *sxp, int32 iflags, uchar fnamecmp_type,
+ const char *xname)
+ {
+ if (statret >= 0) { /* A from-dest-dir statret can == 1! */
+@@ -532,20 +539,24 @@ void itemize(struct file_struct *file, i
+ : S_ISDIR(file->mode) ? !omit_dir_times
+ : !S_ISLNK(file->mode);
+
+- if (S_ISREG(file->mode) && F_LENGTH(file) != st->st_size)
++ if (S_ISREG(file->mode) && F_LENGTH(file) != sxp->st.st_size)
+ iflags |= ITEM_REPORT_SIZE;
+ if ((iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !keep_time
+ && !(iflags & ITEM_MATCHED)
+ && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
+- || (keep_time && cmp_time(file->modtime, st->st_mtime) != 0))
++ || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0))
+ iflags |= ITEM_REPORT_TIME;
+- if (!BITS_EQUAL(st->st_mode, file->mode, CHMOD_BITS))
++ if (!BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS))
+ iflags |= ITEM_REPORT_PERMS;
+- if (preserve_uid && am_root && F_UID(file) != st->st_uid)
++ if (preserve_uid && am_root && F_UID(file) != sxp->st.st_uid)
+ iflags |= ITEM_REPORT_OWNER;
+ if (preserve_gid && F_GID(file) != GID_NONE
+- && st->st_gid != F_GID(file))
++ && sxp->st.st_gid != F_GID(file))
+ iflags |= ITEM_REPORT_GROUP;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && set_acl(NULL, file, sxp) == 0)
++ iflags |= ITEM_REPORT_ACL;
++#endif
+ } else
+ iflags |= ITEM_IS_NEW;
+
+@@ -799,7 +810,7 @@ void check_for_finished_hlinks(int itemi
+ * handling the file, -1 if no dest-linking occurred, or a non-negative
+ * value if we found an alternate basis file. */
+ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
+- char *cmpbuf, STRUCT_STAT *stp, int itemizing,
++ char *cmpbuf, statx *sxp, int itemizing,
+ enum logcode code)
+ {
+ int best_match = -1;
+@@ -808,7 +819,7 @@ static int try_dests_reg(struct file_str
+
+ do {
+ pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
+- if (link_stat(cmpbuf, stp, 0) < 0 || !S_ISREG(stp->st_mode))
++ if (link_stat(cmpbuf, &sxp->st, 0) < 0 || !S_ISREG(sxp->st.st_mode))
+ continue;
+ switch (match_level) {
+ case 0:
+@@ -816,16 +827,20 @@ static int try_dests_reg(struct file_str
+ match_level = 1;
+ /* FALL THROUGH */
+ case 1:
+- if (!unchanged_file(cmpbuf, file, stp))
++ if (!unchanged_file(cmpbuf, file, &sxp->st))
+ continue;
+ best_match = j;
+ match_level = 2;
+ /* FALL THROUGH */
+ case 2:
+- if (!unchanged_attrs(file, stp))
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(cmpbuf, sxp);
++#endif
++ if (!unchanged_attrs(file, sxp))
+ continue;
+ if (always_checksum > 0 && preserve_times
+- && cmp_time(stp->st_mtime, file->modtime))
++ && cmp_time(sxp->st.st_mtime, file->modtime))
+ continue;
+ best_match = j;
+ match_level = 3;
+@@ -840,7 +855,7 @@ static int try_dests_reg(struct file_str
+ if (j != best_match) {
+ j = best_match;
+ pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
+- if (link_stat(cmpbuf, stp, 0) < 0)
++ if (link_stat(cmpbuf, &sxp->st, 0) < 0)
+ return -1;
+ }
+
+@@ -850,16 +865,25 @@ static int try_dests_reg(struct file_str
+ if (!hard_link_one(file, fname, cmpbuf, 1))
+ goto try_a_copy;
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, stp, itemizing, code, j);
++ finish_hard_link(file, fname, &sxp->st, itemizing, code, j);
+ if (itemizing && (verbose > 1 || stdout_format_has_i > 1)) {
+- itemize(file, ndx, 1, stp,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++ itemize(file, ndx, 1, sxp,
+ ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
+ 0, "");
+ }
+ } else
+ #endif
+- if (itemizing)
+- itemize(file, ndx, 0, stp, 0, 0, NULL);
++ if (itemizing) {
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++ itemize(file, ndx, 0, sxp, 0, 0, NULL);
++ }
+ if (verbose > 1 && maybe_ATTRS_REPORT)
+ rprintf(FCLIENT, "%s is uptodate\n", fname);
+ return -2;
+@@ -876,8 +900,13 @@ static int try_dests_reg(struct file_str
+ }
+ return -1;
+ }
+- if (itemizing)
+- itemize(file, ndx, 0, stp, ITEM_LOCAL_CHANGE, 0, NULL);
++ if (itemizing) {
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++ itemize(file, ndx, 0, sxp, ITEM_LOCAL_CHANGE, 0, NULL);
++ }
+ set_file_attrs(fname, file, NULL, 0);
+ if (maybe_ATTRS_REPORT
+ && ((!itemizing && verbose && match_level == 2)
+@@ -888,7 +917,7 @@ static int try_dests_reg(struct file_str
+ }
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, stp, itemizing, code, -1);
++ finish_hard_link(file, fname, &sxp->st, itemizing, code, -1);
+ #endif
+ return -2;
+ }
+@@ -900,7 +929,7 @@ static int try_dests_reg(struct file_str
+ * handling the file, or -1 if no dest-linking occurred, or a non-negative
+ * value if we found an alternate basis file. */
+ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
+- char *cmpbuf, STRUCT_STAT *stp, int itemizing,
++ char *cmpbuf, statx *sxp, int itemizing,
+ enum logcode code)
+ {
+ char lnk[MAXPATHLEN];
+@@ -933,24 +962,24 @@ static int try_dests_non(struct file_str
+
+ do {
+ pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
+- if (link_stat(cmpbuf, stp, 0) < 0)
++ if (link_stat(cmpbuf, &sxp->st, 0) < 0)
+ continue;
+ switch (type) {
+ case TYPE_DIR:
+- if (!S_ISDIR(stp->st_mode))
++ if (!S_ISDIR(sxp->st.st_mode))
+ continue;
+ break;
+ case TYPE_SPECIAL:
+- if (!IS_SPECIAL(stp->st_mode))
++ if (!IS_SPECIAL(sxp->st.st_mode))
+ continue;
+ break;
+ case TYPE_DEVICE:
+- if (!IS_DEVICE(stp->st_mode))
++ if (!IS_DEVICE(sxp->st.st_mode))
+ continue;
+ break;
+ #ifdef SUPPORT_LINKS
+ case TYPE_SYMLINK:
+- if (!S_ISLNK(stp->st_mode))
++ if (!S_ISLNK(sxp->st.st_mode))
+ continue;
+ break;
+ #endif
+@@ -965,7 +994,7 @@ static int try_dests_non(struct file_str
+ case TYPE_SPECIAL:
+ case TYPE_DEVICE:
+ devp = F_RDEV_P(file);
+- if (stp->st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)))
++ if (sxp->st.st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)))
+ continue;
+ break;
+ #ifdef SUPPORT_LINKS
+@@ -982,7 +1011,11 @@ static int try_dests_non(struct file_str
+ match_level = 2;
+ best_match = j;
+ }
+- if (unchanged_attrs(file, stp)) {
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(cmpbuf, sxp);
++#endif
++ if (unchanged_attrs(file, sxp)) {
+ match_level = 3;
+ best_match = j;
+ break;
+@@ -995,7 +1028,7 @@ static int try_dests_non(struct file_str
+ if (j != best_match) {
+ j = best_match;
+ pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
+- if (link_stat(cmpbuf, stp, 0) < 0)
++ if (link_stat(cmpbuf, &sxp->st, 0) < 0)
+ return -1;
+ }
+
+@@ -1026,7 +1059,15 @@ static int try_dests_non(struct file_str
+ : ITEM_LOCAL_CHANGE
+ + (match_level == 3 ? ITEM_XNAME_FOLLOWS : 0);
+ char *lp = match_level == 3 ? "" : NULL;
+- itemize(file, ndx, 0, stp, chg + ITEM_MATCHED, 0, lp);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(fname, sxp);
++#endif
++ itemize(file, ndx, 0, sxp, chg + ITEM_MATCHED, 0, lp);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(sxp);
++#endif
+ }
+ if (verbose > 1 && maybe_ATTRS_REPORT) {
+ rprintf(FCLIENT, "%s%s is uptodate\n",
+@@ -1039,6 +1080,7 @@ static int try_dests_non(struct file_str
+ }
+
+ static int phase = 0;
++static int dflt_perms;
+
+ /* Acts on cur_flist->file's ndx'th item, whose name is fname. If a dir,
+ * make sure it exists, and has the right permissions/timestamp info. For
+@@ -1059,7 +1101,8 @@ static void recv_generator(char *fname,
+ static int need_fuzzy_dirlist = 0;
+ struct file_struct *fuzzy_file = NULL;
+ int fd = -1, f_copy = -1;
+- STRUCT_STAT st, real_st, partial_st;
++ statx sx, real_sx;
++ STRUCT_STAT partial_st;
+ struct file_struct *back_file = NULL;
+ int statret, real_ret, stat_errno;
+ char *fnamecmp, *partialptr, *backupptr = NULL;
+@@ -1104,6 +1147,9 @@ static void recv_generator(char *fname,
+ return;
+ }
+ }
++#ifdef SUPPORT_ACLS
++ sx.acc_acl = sx.def_acl = NULL;
++#endif
+ if (dry_run > 1) {
+ if (fuzzy_dirlist) {
+ flist_free(fuzzy_dirlist);
+@@ -1116,7 +1162,7 @@ static void recv_generator(char *fname,
+ const char *dn = file->dirname ? file->dirname : ".";
+ if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) {
+ if (relative_paths && !implied_dirs
+- && do_stat(dn, &st) < 0
++ && do_stat(dn, &sx.st) < 0
+ && create_directory_path(fname) < 0) {
+ rsyserr(FERROR, errno,
+ "recv_generator: mkdir %s failed",
+@@ -1128,6 +1174,10 @@ static void recv_generator(char *fname,
+ }
+ if (fuzzy_basis)
+ need_fuzzy_dirlist = 1;
++#ifdef SUPPORT_ACLS
++ if (!preserve_perms)
++ dflt_perms = default_perms_for_dir(dn);
++#endif
+ }
+ parent_dirname = dn;
+
+@@ -1137,7 +1187,7 @@ static void recv_generator(char *fname,
+ need_fuzzy_dirlist = 0;
+ }
+
+- statret = link_stat(fname, &st,
++ statret = link_stat(fname, &sx.st,
+ keep_dirlinks && S_ISDIR(file->mode));
+ stat_errno = errno;
+ }
+@@ -1163,8 +1213,9 @@ static void recv_generator(char *fname,
+ * mode based on the local permissions and some heuristics. */
+ if (!preserve_perms) {
+ int exists = statret == 0
+- && S_ISDIR(st.st_mode) == S_ISDIR(file->mode);
+- file->mode = dest_mode(file->mode, st.st_mode, exists);
++ && S_ISDIR(sx.st.st_mode) == S_ISDIR(file->mode);
++ file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
++ exists);
+ }
+
+ if (S_ISDIR(file->mode)) {
+@@ -1173,8 +1224,8 @@ static void recv_generator(char *fname,
+ * file of that name and it is *not* a directory, then
+ * we need to delete it. If it doesn't exist, then
+ * (perhaps recursively) create it. */
+- if (statret == 0 && !S_ISDIR(st.st_mode)) {
+- if (delete_item(fname, st.st_mode, "directory", del_opts) != 0)
++ if (statret == 0 && !S_ISDIR(sx.st.st_mode)) {
++ if (delete_item(fname, sx.st.st_mode, "directory", del_opts) != 0)
+ return;
+ statret = -1;
+ }
+@@ -1183,14 +1234,14 @@ static void recv_generator(char *fname,
+ dry_run++;
+ }
+ real_ret = statret;
+- real_st = st;
++ real_sx = sx;
+ if (new_root_dir) {
+ if (*fname == '.' && fname[1] == '\0')
+ statret = -1;
+ new_root_dir = 0;
+ }
+ if (statret != 0 && basis_dir[0] != NULL) {
+- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st,
++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
+ itemizing, code);
+ if (j == -2) {
+ itemizing = 0;
+@@ -1199,7 +1250,11 @@ static void recv_generator(char *fname,
+ statret = 1;
+ }
+ if (itemizing && f_out != -1) {
+- itemize(file, ndx, statret, &st,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && statret == 0)
++ get_acl(fname, &sx);
++#endif
++ itemize(file, ndx, statret, &sx,
+ statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
+ }
+ if (real_ret != 0 && do_mkdir(fname,file->mode) < 0 && errno != EEXIST) {
+@@ -1213,31 +1268,31 @@ static void recv_generator(char *fname,
+ "*** Skipping any contents from this failed directory ***\n");
+ missing_below = F_DEPTH(file);
+ file->flags |= FLAG_MISSING_DIR;
+- return;
++ goto cleanup;
+ }
+ }
+- if (set_file_attrs(fname, file, real_ret ? NULL : &real_st, 0)
++ if (set_file_attrs(fname, file, real_ret ? NULL : &real_sx, 0)
+ && verbose && code != FNONE && f_out != -1)
+ rprintf(code, "%s/\n", fname);
+ if (real_ret != 0 && one_file_system)
+- real_st.st_dev = filesystem_dev;
++ real_sx.st.st_dev = filesystem_dev;
+ if (inc_recurse) {
+ if (one_file_system) {
+ uint32 *devp = F_DIRDEV_P(file);
+- DEV_MAJOR(devp) = major(real_st.st_dev);
+- DEV_MINOR(devp) = minor(real_st.st_dev);
++ DEV_MAJOR(devp) = major(real_sx.st.st_dev);
++ DEV_MINOR(devp) = minor(real_sx.st.st_dev);
+ }
+ }
+ else if (delete_during && f_out != -1 && !phase && dry_run < 2
+ && (file->flags & FLAG_XFER_DIR))
+- delete_in_dir(cur_flist, fname, file, &real_st.st_dev);
+- return;
++ delete_in_dir(cur_flist, fname, file, &real_sx.st.st_dev);
++ goto cleanup;
+ }
+
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
+- && hard_link_check(file, ndx, fname, statret, &st, itemizing, code))
+- return;
++ && hard_link_check(file, ndx, fname, statret, &sx, itemizing, code))
++ goto cleanup;
+ #endif
+
+ if (preserve_links && S_ISLNK(file->mode)) {
+@@ -1257,28 +1312,28 @@ static void recv_generator(char *fname,
+ char lnk[MAXPATHLEN];
+ int len;
+
+- if (!S_ISLNK(st.st_mode))
++ if (!S_ISLNK(sx.st.st_mode))
+ statret = -1;
+ else if ((len = readlink(fname, lnk, MAXPATHLEN-1)) > 0
+ && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') {
+ /* The link is pointing to the right place. */
+ if (itemizing)
+- itemize(file, ndx, 0, &st, 0, 0, NULL);
+- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT);
++ itemize(file, ndx, 0, &sx, 0, 0, NULL);
++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT);
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, &st, itemizing, code, -1);
++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1);
+ #endif
+ if (remove_source_files == 1)
+ goto return_with_success;
+- return;
++ goto cleanup;
+ }
+ /* Not the right symlink (or not a symlink), so
+ * delete it. */
+- if (delete_item(fname, st.st_mode, "symlink", del_opts) != 0)
+- return;
++ if (delete_item(fname, sx.st.st_mode, "symlink", del_opts) != 0)
++ goto cleanup;
+ } else if (basis_dir[0] != NULL) {
+- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st,
++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
+ itemizing, code);
+ if (j == -2) {
+ #ifndef CAN_HARDLINK_SYMLINK
+@@ -1287,7 +1342,7 @@ static void recv_generator(char *fname,
+ } else
+ #endif
+ if (!copy_dest)
+- return;
++ goto cleanup;
+ itemizing = 0;
+ code = FNONE;
+ } else if (j >= 0)
+@@ -1295,7 +1350,7 @@ static void recv_generator(char *fname,
+ }
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_LAST(file))
+- return;
++ goto cleanup;
+ #endif
+ if (do_symlink(sl, fname) != 0) {
+ rsyserr(FERROR, errno, "symlink %s -> \"%s\" failed",
+@@ -1303,7 +1358,7 @@ static void recv_generator(char *fname,
+ } else {
+ set_file_attrs(fname, file, NULL, 0);
+ if (itemizing) {
+- itemize(file, ndx, statret, &st,
++ itemize(file, ndx, statret, &sx,
+ ITEM_LOCAL_CHANGE, 0, NULL);
+ }
+ if (code != FNONE && verbose)
+@@ -1319,7 +1374,7 @@ static void recv_generator(char *fname,
+ goto return_with_success;
+ }
+ #endif
+- return;
++ goto cleanup;
+ }
+
+ if ((am_root && preserve_devices && IS_DEVICE(file->mode))
+@@ -1329,33 +1384,38 @@ static void recv_generator(char *fname,
+ if (statret == 0) {
+ char *t;
+ if (IS_DEVICE(file->mode)) {
+- if (!IS_DEVICE(st.st_mode))
++ if (!IS_DEVICE(sx.st.st_mode))
+ statret = -1;
+ t = "device file";
+ } else {
+- if (!IS_SPECIAL(st.st_mode))
++ if (!IS_SPECIAL(sx.st.st_mode))
+ statret = -1;
+ t = "special file";
+ }
+ if (statret == 0
+- && BITS_EQUAL(st.st_mode, file->mode, _S_IFMT)
+- && st.st_rdev == rdev) {
++ && BITS_EQUAL(sx.st.st_mode, file->mode, _S_IFMT)
++ && sx.st.st_rdev == rdev) {
+ /* The device or special file is identical. */
+- if (itemizing)
+- itemize(file, ndx, 0, &st, 0, 0, NULL);
+- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT);
++ if (itemizing) {
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(fname, &sx);
++#endif
++ itemize(file, ndx, 0, &sx, 0, 0, NULL);
++ }
++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT);
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, &st, itemizing, code, -1);
++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1);
+ #endif
+ if (remove_source_files == 1)
+ goto return_with_success;
+- return;
++ goto cleanup;
+ }
+- if (delete_item(fname, st.st_mode, t, del_opts) != 0)
+- return;
++ if (delete_item(fname, sx.st.st_mode, t, del_opts) != 0)
++ goto cleanup;
+ } else if (basis_dir[0] != NULL) {
+- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st,
++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
+ itemizing, code);
+ if (j == -2) {
+ #ifndef CAN_HARDLINK_SPECIAL
+@@ -1364,7 +1424,7 @@ static void recv_generator(char *fname,
+ } else
+ #endif
+ if (!copy_dest)
+- return;
++ goto cleanup;
+ itemizing = 0;
+ code = FNONE;
+ } else if (j >= 0)
+@@ -1372,7 +1432,7 @@ static void recv_generator(char *fname,
+ }
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_LAST(file))
+- return;
++ goto cleanup;
+ #endif
+ if (verbose > 2) {
+ rprintf(FINFO, "mknod(%s, 0%o, [%ld,%ld])\n",
+@@ -1385,7 +1445,11 @@ static void recv_generator(char *fname,
+ } else {
+ set_file_attrs(fname, file, NULL, 0);
+ if (itemizing) {
+- itemize(file, ndx, statret, &st,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && statret == 0)
++ get_acl(fname, &sx);
+#endif
-+
- maybe_emit_filelist_progress(flist->count + flist_count_offset);
++ itemize(file, ndx, statret, &sx,
+ ITEM_LOCAL_CHANGE, 0, NULL);
+ }
+ if (code != FNONE && verbose)
+@@ -1397,14 +1461,14 @@ static void recv_generator(char *fname,
+ if (remove_source_files == 1)
+ goto return_with_success;
+ }
+- return;
++ goto cleanup;
+ }
- flist_expand(flist);
-@@ -977,6 +983,16 @@ static struct file_struct *send_file_nam
- if (file->basename[0]) {
- flist->files[flist->count++] = file;
- send_file_entry(file, f);
+ if (!S_ISREG(file->mode)) {
+ if (solo_file)
+ fname = f_name(file, NULL);
+ rprintf(FINFO, "skipping non-regular file \"%s\"\n", fname);
+- return;
++ goto cleanup;
+ }
+
+ if (max_size > 0 && F_LENGTH(file) > max_size) {
+@@ -1413,7 +1477,7 @@ static void recv_generator(char *fname,
+ fname = f_name(file, NULL);
+ rprintf(FINFO, "%s is over max-size\n", fname);
+ }
+- return;
++ goto cleanup;
+ }
+ if (min_size > 0 && F_LENGTH(file) < min_size) {
+ if (verbose > 1) {
+@@ -1421,39 +1485,39 @@ static void recv_generator(char *fname,
+ fname = f_name(file, NULL);
+ rprintf(FINFO, "%s is under min-size\n", fname);
+ }
+- return;
++ goto cleanup;
+ }
+
+ if (ignore_existing > 0 && statret == 0) {
+ if (verbose > 1)
+ rprintf(FINFO, "%s exists\n", fname);
+- return;
++ goto cleanup;
+ }
+
+ if (update_only > 0 && statret == 0
+- && cmp_time(st.st_mtime, file->modtime) > 0) {
++ && cmp_time(sx.st.st_mtime, file->modtime) > 0) {
+ if (verbose > 1)
+ rprintf(FINFO, "%s is newer\n", fname);
+- return;
++ goto cleanup;
+ }
+
+ fnamecmp = fname;
+ fnamecmp_type = FNAMECMP_FNAME;
+
+- if (statret == 0 && !S_ISREG(st.st_mode)) {
+- if (delete_item(fname, st.st_mode, "regular file", del_opts) != 0)
+- return;
++ if (statret == 0 && !S_ISREG(sx.st.st_mode)) {
++ if (delete_item(fname, sx.st.st_mode, "regular file", del_opts) != 0)
++ goto cleanup;
+ statret = -1;
+ stat_errno = ENOENT;
+ }
+
+ if (statret != 0 && basis_dir[0] != NULL) {
+- int j = try_dests_reg(file, fname, ndx, fnamecmpbuf, &st,
++ int j = try_dests_reg(file, fname, ndx, fnamecmpbuf, &sx,
+ itemizing, code);
+ if (j == -2) {
+ if (remove_source_files == 1)
+ goto return_with_success;
+- return;
++ goto cleanup;
+ }
+ if (j >= 0) {
+ fnamecmp = fnamecmpbuf;
+@@ -1463,7 +1527,7 @@ static void recv_generator(char *fname,
+ }
+
+ real_ret = statret;
+- real_st = st;
++ real_sx = sx;
+
+ if (partial_dir && (partialptr = partial_dir_fname(fname)) != NULL
+ && link_stat(partialptr, &partial_st, 0) == 0
+@@ -1482,7 +1546,7 @@ static void recv_generator(char *fname,
+ rprintf(FINFO, "fuzzy basis selected for %s: %s\n",
+ fname, fnamecmpbuf);
+ }
+- st.st_size = F_LENGTH(fuzzy_file);
++ sx.st.st_size = F_LENGTH(fuzzy_file);
+ statret = 0;
+ fnamecmp = fnamecmpbuf;
+ fnamecmp_type = FNAMECMP_FUZZY;
+@@ -1492,45 +1556,50 @@ static void recv_generator(char *fname,
+ if (statret != 0) {
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_LAST(file))
+- return;
++ goto cleanup;
+ #endif
+ if (stat_errno == ENOENT)
+ goto notify_others;
+ rsyserr(FERROR, stat_errno, "recv_generator: failed to stat %s",
+ full_fname(fname));
+- return;
++ goto cleanup;
+ }
+
+- if (append_mode > 0 && st.st_size > F_LENGTH(file))
+- return;
++ if (append_mode > 0 && sx.st.st_size > F_LENGTH(file))
++ goto cleanup;
+
+ if (fnamecmp_type <= FNAMECMP_BASIS_DIR_HIGH)
+ ;
+ else if (fnamecmp_type == FNAMECMP_FUZZY)
+ ;
+- else if (unchanged_file(fnamecmp, file, &st)) {
++ else if (unchanged_file(fnamecmp, file, &sx.st)) {
+ if (partialptr) {
+ do_unlink(partialptr);
+ handle_partial_dir(partialptr, PDIR_DELETE);
+ }
+- if (itemizing)
+- itemize(file, ndx, statret, &st, 0, 0, NULL);
+- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT);
++ if (itemizing) {
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ send_acl(file, f);
++ if (preserve_acls && statret == 0)
++ get_acl(fnamecmp, &sx);
+#endif
-+ } else {
++ itemize(file, ndx, statret, &sx, 0, 0, NULL);
++ }
++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT);
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, &st, itemizing, code, -1);
++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1);
+ #endif
+ if (remove_source_files != 1)
+- return;
++ goto cleanup;
+ return_with_success:
+ if (!dry_run)
+ send_msg_int(MSG_SUCCESS, ndx + cur_flist->ndx_start);
+- return;
++ goto cleanup;
+ }
+
+ prepare_to_open:
+ if (partialptr) {
+- st = partial_st;
++ sx.st = partial_st;
+ fnamecmp = partialptr;
+ fnamecmp_type = FNAMECMP_PARTIAL_DIR;
+ statret = 0;
+@@ -1555,16 +1624,20 @@ static void recv_generator(char *fname,
+ /* pretend the file didn't exist */
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_LAST(file))
+- return;
++ goto cleanup;
+ #endif
+ statret = real_ret = -1;
+#ifdef SUPPORT_ACLS
-+ /* Cleanup unsent ACL(s). */
-+ if (preserve_acls)
-+ send_acl(file, -1);
++ if (preserve_acls && ACL_READY(sx))
++ free_acl(&sx);
+#endif
+ goto notify_others;
+ }
+
+ if (inplace && make_backups > 0 && fnamecmp_type == FNAMECMP_FNAME) {
+ if (!(backupptr = get_backup_name(fname))) {
+ close(fd);
+- return;
++ goto cleanup;
+ }
+ if (!(back_file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) {
+ close(fd);
+@@ -1575,7 +1648,7 @@ static void recv_generator(char *fname,
+ full_fname(backupptr));
+ unmake_file(back_file);
+ close(fd);
+- return;
++ goto cleanup;
+ }
+ if ((f_copy = do_open(backupptr,
+ O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
+@@ -1583,14 +1656,14 @@ static void recv_generator(char *fname,
+ full_fname(backupptr));
+ unmake_file(back_file);
+ close(fd);
+- return;
++ goto cleanup;
+ }
+ fnamecmp_type = FNAMECMP_BACKUP;
+ }
+
+ if (verbose > 3) {
+ rprintf(FINFO, "gen mapped %s of size %.0f\n",
+- fnamecmp, (double)st.st_size);
++ fnamecmp, (double)sx.st.st_size);
}
- return file;
- }
-@@ -1365,6 +1381,11 @@ struct file_list *recv_file_list(int f)
- flags |= read_byte(f) << 8;
- file = receive_file_entry(flist, flags, f);
+ if (verbose > 2)
+@@ -1614,26 +1687,34 @@ static void recv_generator(char *fname,
+ iflags |= ITEM_BASIS_TYPE_FOLLOWS;
+ if (fnamecmp_type == FNAMECMP_FUZZY)
+ iflags |= ITEM_XNAME_FOLLOWS;
+- itemize(file, -1, real_ret, &real_st, iflags, fnamecmp_type,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && real_ret == 0)
++ get_acl(fnamecmp, &real_sx);
++#endif
++ itemize(file, -1, real_ret, &real_sx, iflags, fnamecmp_type,
+ fuzzy_file ? fuzzy_file->basename : NULL);
+#ifdef SUPPORT_ACLS
+ if (preserve_acls)
-+ receive_acl(file, f);
++ free_acl(&real_sx);
+#endif
-+
- if (S_ISREG(file->mode) || S_ISLNK(file->mode))
- stats.total_size += file->length;
+ }
+
+ if (!do_xfers) {
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_IS_HLINKED(file))
+- finish_hard_link(file, fname, &st, itemizing, code, -1);
++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1);
+ #endif
+- return;
++ goto cleanup;
+ }
+ if (read_batch)
+- return;
++ goto cleanup;
+
+ if (statret != 0 || whole_file) {
+ write_sum_head(f_out, NULL);
+- return;
++ goto cleanup;
+ }
-@@ -1387,6 +1408,11 @@ struct file_list *recv_file_list(int f)
+- generate_and_send_sums(fd, st.st_size, f_out, f_copy);
++ generate_and_send_sums(fd, sx.st.st_size, f_out, f_copy);
- clean_flist(flist, relative_paths, 1);
+ if (f_copy >= 0) {
+ close(f_copy);
+@@ -1646,6 +1727,13 @@ static void recv_generator(char *fname,
+ }
+ close(fd);
++
++ cleanup:
+#ifdef SUPPORT_ACLS
+ if (preserve_acls)
-+ sort_file_acl_index_lists();
++ free_acl(&sx);
+#endif
-+
- if (f >= 0) {
- recv_uid_list(f, flist);
-
---- old/generator.c
-+++ new/generator.c
-@@ -85,6 +85,7 @@ extern long block_size; /* "long" becaus
- extern int max_delete;
- extern int force_delete;
- extern int one_file_system;
-+extern mode_t orig_umask;
- extern struct stats stats;
- extern dev_t filesystem_dev;
- extern char *backup_dir;
-@@ -753,6 +754,7 @@ static int try_dests_non(struct file_str
++ return;
}
- static int phase = 0;
-+static int dflt_perms;
+ static void touch_up_dirs(struct file_list *flist, int ndx,
+@@ -1742,6 +1830,8 @@ void generate_files(int f_out, char *loc
+ * notice that and let us know via the redo pipe (or its closing). */
+ ignore_timeout = 1;
- /* Acts on the_file_list->file's ndx'th item, whose name is fname. If a dir,
- * make sure it exists, and has the right permissions/timestamp info. For
-@@ -844,6 +846,10 @@ static void recv_generator(char *fname,
++ dflt_perms = (ACCESSPERMS & ~orig_umask);
++
+ do {
+ if (inc_recurse && delete_during && cur_flist->ndx_start) {
+ struct file_struct *fp = dir_flist->files[cur_flist->parent_ndx];
+--- old/hlink.c
++++ new/hlink.c
+@@ -27,6 +27,7 @@ extern int verbose;
+ extern int dry_run;
+ extern int do_xfers;
+ extern int link_dest;
++extern int preserve_acls;
+ extern int make_backups;
+ extern int protocol_version;
+ extern int remove_source_files;
+@@ -268,15 +269,19 @@ void match_hard_links(void)
+ }
+
+ static int maybe_hard_link(struct file_struct *file, int ndx,
+- const char *fname, int statret, STRUCT_STAT *stp,
++ const char *fname, int statret, statx *sxp,
+ const char *oldname, STRUCT_STAT *old_stp,
+ const char *realname, int itemizing, enum logcode code)
+ {
+ if (statret == 0) {
+- if (stp->st_dev == old_stp->st_dev
+- && stp->st_ino == old_stp->st_ino) {
++ if (sxp->st.st_dev == old_stp->st_dev
++ && sxp->st.st_ino == old_stp->st_ino) {
+ if (itemizing) {
+- itemize(file, ndx, statret, stp,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++ itemize(file, ndx, statret, sxp,
+ ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS,
+ 0, "");
}
- if (fuzzy_basis)
- need_fuzzy_dirlist = 1;
+@@ -297,7 +302,11 @@ static int maybe_hard_link(struct file_s
+
+ if (hard_link_one(file, fname, oldname, 0)) {
+ if (itemizing) {
+- itemize(file, ndx, statret, stp,
+#ifdef SUPPORT_ACLS
-+ if (!preserve_perms)
-+ dflt_perms = default_perms_for_dir(dn);
++ if (preserve_acls && statret == 0 && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
+#endif
++ itemize(file, ndx, statret, sxp,
+ ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0,
+ realname);
}
- parent_dirname = dn;
-
-@@ -871,7 +877,8 @@ static void recv_generator(char *fname,
- if (!preserve_perms) {
- int exists = statret == 0
- && S_ISDIR(st.st_mode) == S_ISDIR(file->mode);
-- file->mode = dest_mode(file->mode, st.st_mode, exists);
-+ file->mode = dest_mode(file->mode, st.st_mode, dflt_perms,
-+ exists);
+@@ -311,7 +320,7 @@ static int maybe_hard_link(struct file_s
+ /* Only called if FLAG_HLINKED is set and FLAG_HLINK_FIRST is not. Returns:
+ * 0 = process the file, 1 = skip the file, -1 = error occurred. */
+ int hard_link_check(struct file_struct *file, int ndx, const char *fname,
+- int statret, STRUCT_STAT *stp, int itemizing,
++ int statret, statx *sxp, int itemizing,
+ enum logcode code)
+ {
+ STRUCT_STAT prev_st;
+@@ -362,18 +371,20 @@ int hard_link_check(struct file_struct *
+ if (statret < 0 && basis_dir[0] != NULL) {
+ /* If we match an alt-dest item, we don't output this as a change. */
+ char cmpbuf[MAXPATHLEN];
+- STRUCT_STAT alt_st;
++ statx alt_sx;
+ int j = 0;
++#ifdef SUPPORT_ACLS
++ alt_sx.acc_acl = alt_sx.def_acl = NULL;
++#endif
+ do {
+ pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname);
+- if (link_stat(cmpbuf, &alt_st, 0) < 0)
++ if (link_stat(cmpbuf, &alt_sx.st, 0) < 0)
+ continue;
+ if (link_dest) {
+- if (prev_st.st_dev != alt_st.st_dev
+- || prev_st.st_ino != alt_st.st_ino)
++ if (prev_st.st_dev != alt_sx.st.st_dev
++ || prev_st.st_ino != alt_sx.st.st_ino)
+ continue;
+ statret = 1;
+- *stp = alt_st;
+ if (verbose < 2 || !stdout_format_has_i) {
+ itemizing = 0;
+ code = FNONE;
+@@ -382,16 +393,36 @@ int hard_link_check(struct file_struct *
+ }
+ break;
+ }
+- if (!unchanged_file(cmpbuf, file, &alt_st))
++ if (!unchanged_file(cmpbuf, file, &alt_sx.st))
+ continue;
+ statret = 1;
+- *stp = alt_st;
+- if (unchanged_attrs(file, &alt_st))
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(cmpbuf, &alt_sx);
++#endif
++ if (unchanged_attrs(file, &alt_sx))
+ break;
+ } while (basis_dir[++j] != NULL);
++ if (statret == 1) {
++ sxp->st = alt_sx.st;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls) {
++ if (!ACL_READY(*sxp))
++ get_acl(cmpbuf, sxp);
++ else {
++ sxp->acc_acl = alt_sx.acc_acl;
++ sxp->def_acl = alt_sx.def_acl;
++ }
++ }
++#endif
++ }
++#ifdef SUPPORT_ACLS
++ else if (preserve_acls)
++ free_acl(&alt_sx);
++#endif
}
- if (S_ISDIR(file->mode)) {
-@@ -1343,6 +1350,8 @@ void generate_files(int f_out, struct fi
- * notice that and let us know via the redo pipe (or its closing). */
- ignore_timeout = 1;
+- if (maybe_hard_link(file, ndx, fname, statret, stp, prev_name, &prev_st,
++ if (maybe_hard_link(file, ndx, fname, statret, sxp, prev_name, &prev_st,
+ realname, itemizing, code) < 0)
+ return -1;
-+ dflt_perms = (ACCESSPERMS & ~orig_umask);
-+
- for (i = 0; i < flist->count; i++) {
- struct file_struct *file = flist->files[i];
+@@ -426,7 +457,8 @@ void finish_hard_link(struct file_struct
+ STRUCT_STAT *stp, int itemizing, enum logcode code,
+ int alt_dest)
+ {
+- STRUCT_STAT st, prev_st;
++ statx prev_sx;
++ STRUCT_STAT st;
+ char alt_name[MAXPATHLEN], *prev_name;
+ const char *our_name;
+ int prev_statret, ndx, prev_ndx = F_HL_PREV(file);
+@@ -450,14 +482,24 @@ void finish_hard_link(struct file_struct
+ } else
+ our_name = fname;
++#ifdef SUPPORT_ACLS
++ prev_sx.acc_acl = prev_sx.def_acl = NULL;
++#endif
++
+ while ((ndx = prev_ndx) >= 0) {
++ int val;
+ file = FPTR(ndx);
+ file->flags = (file->flags & ~FLAG_HLINK_FIRST) | FLAG_HLINK_DONE;
+ prev_ndx = F_HL_PREV(file);
+ prev_name = f_name(file, NULL);
+- prev_statret = link_stat(prev_name, &prev_st, 0);
+- if (maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_st,
+- our_name, stp, fname, itemizing, code) < 0)
++ prev_statret = link_stat(prev_name, &prev_sx.st, 0);
++ val = maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_sx,
++ our_name, stp, fname, itemizing, code);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(&prev_sx);
++#endif
++ if (val < 0)
+ continue;
+ if (remove_source_files == 1 && do_xfers)
+ send_msg_int(MSG_SUCCESS, ndx);
--- old/lib/sysacls.c
+++ new/lib/sysacls.c
-@@ -0,0 +1,3242 @@
+@@ -0,0 +1,3251 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba system utilities for ACL support.
+#include "rsync.h"
+#include "sysacls.h" /****** ADDED ******/
+
++#ifdef SUPPORT_ACLS
++
+/****** EXTRAS -- THESE ITEMS ARE NOT FROM THE SAMBA SOURCE ******/
++#ifdef DEBUG
++#undef DEBUG
++#endif
++#define DEBUG(x,y)
++
+void SAFE_FREE(void *mem)
+{
+ if (mem)
+
+ for (i = 0; i < acl_d->count; i++) {
+ struct acl *ap = &acl_d->acl[i];
-+ struct passwd *pw;
+ struct group *gr;
+ char tagbuf[12];
+ char idbuf[12];
+ * acl[] array, this actually allocates an ACL with room
+ * for (count+1) entries
+ */
-+ if ((a = SMB_MALLOC(sizeof(struct SMB_ACL_T) + count * sizeof(struct acl))) == NULL) {
++ if ((a = (SMB_ACL_T)SMB_MALLOC(sizeof(struct SMB_ACL_T) + count * sizeof(struct acl))) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ return 0;
+}
+
-+int sys_acl_free_qualifier(void *qual, SMB_ACL_TAG_T tagtype)
++int sys_acl_free_qualifier(UNUSED(void *qual), UNUSED(SMB_ACL_TAG_T tagtype))
+{
+ return 0;
+}
+
+ for (i = 0; i < acl_d->count; i++) {
+ struct acl *ap = &acl_d->acl[i];
-+ struct passwd *pw;
+ struct group *gr;
+ char tagbuf[12];
+ char idbuf[12];
+ uid_t user_id;
+
+ /* AIX has no DEFAULT */
-+ if ( type == SMB_ACL_TYPE_DEFAULT )
++ if ( type == SMB_ACL_TYPE_DEFAULT ) {
++ errno = ENOTSUP;
+ return NULL;
++ }
+
+ /* Get the acl using statacl */
+
+#endif
+ return 0;
+}
++
++#endif /* SUPPORT_ACLS */
--- old/lib/sysacls.h
+++ new/lib/sysacls.h
-@@ -0,0 +1,28 @@
+@@ -0,0 +1,40 @@
++#ifdef SUPPORT_ACLS
++
++#ifdef HAVE_SYS_ACL_H
++#include <sys/acl.h>
++#endif
++#ifdef HAVE_ACL_LIBACL_H
++#include <acl/libacl.h>
++#endif
++#include "smb_acls.h"
++
+#define SMB_MALLOC(cnt) new_array(char, cnt)
+#define SMB_MALLOC_P(obj) new_array(obj, 1)
+#define SMB_MALLOC_ARRAY(obj, cnt) new_array(obj, cnt)
+int sys_acl_free_text(char *text);
+int sys_acl_free_acl(SMB_ACL_T the_acl);
+int sys_acl_free_qualifier(void *qual, SMB_ACL_TAG_T tagtype);
---- old/mkproto.awk
-+++ new/mkproto.awk
-@@ -58,7 +58,7 @@ BEGIN {
- next;
- }
-
--!/^OFF_T|^size_t|^off_t|^pid_t|^unsigned|^mode_t|^DIR|^user|^int|^char|^uint|^uchar|^short|^struct|^BOOL|^void|^time|^const|^RETSIGTYPE/ {
-+!/^OFF_T|^size_t|^off_t|^pid_t|^id_t|^unsigned|^mode_t|^DIR|^user|^int|^char|^uint|^uchar|^short|^struct|^BOOL|^void|^time|^const|^RETSIGTYPE/ {
- next;
- }
++
++#endif /* SUPPORT_ACLS */
+--- old/log.c
++++ new/log.c
+@@ -625,8 +625,10 @@ static void log_formatted(enum logcode c
+ c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
+ c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
+ c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
+- c[8] = '.';
+- c[9] = '\0';
++ c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
++ c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
++ c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
++ c[11] = '\0';
+ if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
+ char ch = iflags & ITEM_IS_NEW ? '+' : '?';
--- old/options.c
+++ new/options.c
-@@ -45,6 +45,7 @@ int copy_dirlinks = 0;
+@@ -47,6 +47,7 @@ int copy_dirlinks = 0;
int copy_links = 0;
int preserve_links = 0;
int preserve_hard_links = 0;
int preserve_perms = 0;
int preserve_executability = 0;
int preserve_devices = 0;
-@@ -194,6 +195,7 @@ static void print_rsync_version(enum log
+@@ -199,6 +200,7 @@ static void print_rsync_version(enum log
char const *got_socketpair = "no ";
char const *have_inplace = "no ";
char const *hardlinks = "no ";
char const *links = "no ";
char const *ipv6 = "no ";
STRUCT_STAT *dumstat;
-@@ -210,6 +212,10 @@ static void print_rsync_version(enum log
+@@ -215,6 +217,10 @@ static void print_rsync_version(enum log
hardlinks = "";
#endif
#ifdef SUPPORT_LINKS
links = "";
#endif
-@@ -223,9 +229,9 @@ static void print_rsync_version(enum log
- rprintf(f, "Copyright (C) 1996-2006 by Andrew Tridgell, Wayne Davison, and others.\n");
- rprintf(f, "<http://rsync.samba.org/>\n");
- rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, "
-- "%shard links, %ssymlinks, batchfiles,\n",
-+ "%shard links, %sACLs, %ssymlinks, batchfiles,\n",
- (int) (sizeof (OFF_T) * 8),
-- got_socketpair, hardlinks, links);
-+ got_socketpair, hardlinks, acls, links);
+@@ -233,8 +239,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\n",
+- have_inplace);
++ rprintf(f, " %sappend, %sACLs\n",
++ have_inplace, acls);
- /* Note that this field may not have type ino_t. It depends
- * on the complicated interaction between largefile feature
-@@ -295,6 +301,9 @@ void usage(enum logcode F)
- rprintf(F," -H, --hard-links preserve hard links\n");
+ #ifdef MAINTAINER_MODE
+ rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
+@@ -280,7 +286,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)\n");
++ rprintf(F," -a, --archive archive mode; same as -rlptgoD (no -H, -A)\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");
+@@ -302,6 +308,9 @@ void usage(enum logcode F)
rprintf(F," -p, --perms preserve permissions\n");
rprintf(F," -E, --executability preserve the file's executability\n");
+ rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
+#ifdef SUPPORT_ACLS
+ rprintf(F," -A, --acls preserve ACLs (implies --perms)\n");
+#endif
- rprintf(F," --chmod=CHMOD change destination permissions\n");
rprintf(F," -o, --owner preserve owner (super-user only)\n");
rprintf(F," -g, --group preserve group\n");
-@@ -410,6 +419,9 @@ static struct poptOption long_options[]
+ rprintf(F," --devices preserve device files (super-user only)\n");
+@@ -422,6 +431,9 @@ static struct poptOption long_options[]
{"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
{"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
{"executability", 'E', POPT_ARG_NONE, &preserve_executability, 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 },
-@@ -1068,6 +1080,23 @@ int parse_arguments(int *argc, const cha
+@@ -1093,6 +1105,24 @@ int parse_arguments(int *argc, const cha
usage(FINFO);
exit_cleanup(0);
+ case 'A':
+#ifdef SUPPORT_ACLS
-+ preserve_acls = preserve_perms = 1;
++ preserve_acls = 1;
++ preserve_perms = 1;
+ break;
+#else
+ /* FIXME: this should probably be ignored with a
default:
/* A large opt value means that set_refuse_options()
* turned this option off. */
-@@ -1511,6 +1540,10 @@ void server_options(char **args,int *arg
-
- if (preserve_hard_links)
- argstr[x++] = 'H';
+@@ -1556,6 +1586,10 @@ void server_options(char **args,int *arg
+ argstr[x++] = 'p';
+ else if (preserve_executability && am_sender)
+ argstr[x++] = 'E';
+#ifdef SUPPORT_ACLS
+ if (preserve_acls)
+ argstr[x++] = 'A';
+#endif
- if (preserve_uid)
- argstr[x++] = 'o';
- if (preserve_gid)
+ if (recurse)
+ argstr[x++] = 'r';
+ if (always_checksum)
--- old/receiver.c
+++ new/receiver.c
-@@ -46,6 +46,7 @@ extern int keep_partial;
+@@ -48,6 +48,7 @@ extern int keep_partial;
extern int checksum_seed;
extern int inplace;
extern int delay_updates;
+extern mode_t orig_umask;
extern struct stats stats;
- extern char *log_format;
extern char *tmpdir;
-@@ -344,6 +345,10 @@ int recv_files(int f_in, struct file_lis
- int itemizing = am_daemon ? daemon_log_format_has_i
- : !am_server && log_format_has_i;
+ extern char *partial_dir;
+@@ -348,6 +349,10 @@ int recv_files(int f_in, char *local_nam
+ int itemizing = am_server ? logfile_format_has_i : stdout_format_has_i;
+ enum logcode log_code = log_before_transfer ? FLOG : FINFO;
int max_phase = protocol_version >= 29 ? 2 : 1;
+ int dflt_perms = (ACCESSPERMS & ~orig_umask);
+#ifdef SUPPORT_ACLS
-+ char *parent_dirname = "";
++ const char *parent_dirname = "";
+#endif
- int i, recv_ok;
+ int ndx, recv_ok;
if (verbose > 2)
-@@ -541,7 +546,16 @@ int recv_files(int f_in, struct file_lis
+@@ -563,7 +568,16 @@ int recv_files(int f_in, char *local_nam
* mode based on the local permissions and some heuristics. */
if (!preserve_perms) {
int exists = fd1 != -1;
- file->mode = dest_mode(file->mode, st.st_mode, exists);
+#ifdef SUPPORT_ACLS
-+ char *dn = file->dirname ? file->dirname : ".";
++ const char *dn = file->dirname ? file->dirname : ".";
+ if (parent_dirname != dn
+ && strcmp(parent_dirname, dn) != 0) {
+ dflt_perms = default_perms_for_dir(dn);
+ dflt_perms, exists);
}
- /* We now check to see if we are writing file "inplace" */
+ /* We now check to see if we are writing the file "inplace" */
--- old/rsync.c
+++ new/rsync.c
-@@ -33,6 +33,7 @@
+@@ -32,6 +32,7 @@
+
extern int verbose;
extern int dry_run;
- extern int daemon_log_format_has_i;
+extern int preserve_acls;
extern int preserve_perms;
extern int preserve_executability;
extern int preserve_times;
-@@ -101,7 +102,8 @@ void free_sums(struct sum_struct *s)
+@@ -50,7 +51,6 @@ extern int inplace;
+ extern int flist_eof;
+ extern int keep_dirlinks;
+ extern int make_backups;
+-extern mode_t orig_umask;
+ extern struct file_list *cur_flist, *first_flist, *dir_flist;
+ extern struct chmod_mode_struct *daemon_chmod_modes;
+
+@@ -202,7 +202,8 @@ void free_sums(struct sum_struct *s)
/* This is only called when we aren't preserving permissions. Figure out what
* the permissions should be and return them merged back into the mode. */
--mode_t dest_mode(mode_t flist_mode, mode_t cur_mode, int exists)
-+mode_t dest_mode(mode_t flist_mode, mode_t cur_mode, int dflt_perms,
+-mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int exists)
++mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
+ int exists)
{
+ int new_mode;
/* If the file already exists, we'll return the local permissions,
- * possibly tweaked by the --executability option. */
-@@ -116,7 +118,7 @@ mode_t dest_mode(mode_t flist_mode, mode
- cur_mode |= (cur_mode & 0444) >> 2;
+@@ -219,56 +220,65 @@ mode_t dest_mode(mode_t flist_mode, mode
+ new_mode |= (new_mode & 0444) >> 2;
+ }
+ } else {
+- /* Apply the umask and turn off special permissions. */
+- new_mode = flist_mode & (~CHMOD_BITS | (ACCESSPERMS & ~orig_umask));
++ /* Apply destination default permissions and turn
++ * off special permissions. */
++ new_mode = flist_mode & (~CHMOD_BITS | dflt_perms);
+ }
+ return new_mode;
+ }
+
+-int set_file_attrs(char *fname, struct file_struct *file, STRUCT_STAT *st,
++int set_file_attrs(char *fname, struct file_struct *file, statx *sxp,
+ int flags)
+ {
+ int updated = 0;
+- STRUCT_STAT st2;
++ statx sx2;
+ int change_uid, change_gid;
+ mode_t new_mode = file->mode;
+
+- if (!st) {
++ if (!sxp) {
+ if (dry_run)
+ return 1;
+- if (link_stat(fname, &st2, 0) < 0) {
++ if (link_stat(fname, &sx2.st, 0) < 0) {
+ rsyserr(FERROR, errno, "stat %s failed",
+ full_fname(fname));
+ return 0;
+ }
+- st = &st2;
++#ifdef SUPPORT_ACLS
++ sx2.acc_acl = sx2.def_acl = NULL;
++#endif
+ if (!preserve_perms && S_ISDIR(new_mode)
+- && st->st_mode & S_ISGID) {
++ && sx2.st.st_mode & S_ISGID) {
+ /* We just created this directory and its setgid
+ * bit is on, so make sure it stays on. */
+ new_mode |= S_ISGID;
+ }
++ sxp = &sx2;
+ }
+
+- if (!preserve_times || (S_ISDIR(st->st_mode) && omit_dir_times))
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++
++ if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && omit_dir_times))
+ flags |= ATTRS_SKIP_MTIME;
+ if (!(flags & ATTRS_SKIP_MTIME)
+- && cmp_time(st->st_mtime, file->modtime) != 0) {
+- int ret = set_modtime(fname, file->modtime, st->st_mode);
++ && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
++ int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
+ if (ret < 0) {
+ rsyserr(FERROR, errno, "failed to set times on %s",
+ full_fname(fname));
+- return 0;
++ goto cleanup;
+ }
+ if (ret == 0) /* ret == 1 if symlink could not be set */
+ updated = 1;
+ }
+
+- change_uid = am_root && preserve_uid && st->st_uid != F_UID(file);
++ change_uid = am_root && preserve_uid && sxp->st.st_uid != F_UID(file);
+ change_gid = preserve_gid && F_GID(file) != GID_NONE
+- && st->st_gid != F_GID(file);
++ && sxp->st.st_gid != F_GID(file);
+ #if !defined HAVE_LCHOWN && !defined CHOWN_MODIFIES_SYMLINK
+- if (S_ISLNK(st->st_mode))
++ if (S_ISLNK(sxp->st.st_mode))
+ ;
+ else
+ #endif
+@@ -278,45 +288,57 @@ int set_file_attrs(char *fname, struct f
+ rprintf(FINFO,
+ "set uid of %s from %ld to %ld\n",
+ fname,
+- (long)st->st_uid, (long)F_UID(file));
++ (long)sxp->st.st_uid, (long)F_UID(file));
+ }
+ if (change_gid) {
+ rprintf(FINFO,
+ "set gid of %s from %ld to %ld\n",
+ fname,
+- (long)st->st_gid, (long)F_GID(file));
++ (long)sxp->st.st_gid, (long)F_GID(file));
+ }
+ }
+ if (do_lchown(fname,
+- change_uid ? F_UID(file) : st->st_uid,
+- change_gid ? F_GID(file) : st->st_gid) != 0) {
++ change_uid ? F_UID(file) : sxp->st.st_uid,
++ change_gid ? F_GID(file) : sxp->st.st_gid) != 0) {
+ /* shouldn't have attempted to change uid or gid
+ * unless have the privilege */
+ rsyserr(FERROR, errno, "%s %s failed",
+ change_uid ? "chown" : "chgrp",
+ full_fname(fname));
+- return 0;
++ goto cleanup;
+ }
+ /* a lchown had been done - we have to re-stat if the
+ * destination had the setuid or setgid bits set due
+ * to the side effect of the chown call */
+- if (st->st_mode & (S_ISUID | S_ISGID)) {
+- link_stat(fname, st,
+- keep_dirlinks && S_ISDIR(st->st_mode));
++ if (sxp->st.st_mode & (S_ISUID | S_ISGID)) {
++ link_stat(fname, &sxp->st,
++ keep_dirlinks && S_ISDIR(sxp->st.st_mode));
}
- } else
-- cur_mode = flist_mode & ACCESSPERMS & ~orig_umask;
-+ cur_mode = flist_mode & ACCESSPERMS & dflt_perms;
- if (daemon_chmod_modes && !S_ISLNK(flist_mode))
- cur_mode = tweak_mode(cur_mode, daemon_chmod_modes);
- return (flist_mode & ~CHMOD_BITS) | (cur_mode & CHMOD_BITS);
-@@ -203,6 +205,13 @@ int set_file_attrs(char *fname, struct f
updated = 1;
}
+ if (daemon_chmod_modes && !S_ISLNK(new_mode))
+ new_mode = tweak_mode(new_mode, daemon_chmod_modes);
++
+#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. */
-+ if (preserve_acls && set_acl(fname, file, st->st_mode) == 0)
++ * will enable owner-writability using chmod, if necessary.
++ *
++ * If set_acl() changes permission bits in the process of setting
++ * an access ACL, it changes sxp->st.st_mode so we know whether we
++ * need to chmod(). */
++ if (preserve_acls && set_acl(fname, file, sxp) == 0)
+ updated = 1;
+#endif
+
#ifdef HAVE_CHMOD
- if ((st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) {
- int ret = do_chmod(fname, file->mode);
+- if (!BITS_EQUAL(st->st_mode, new_mode, CHMOD_BITS)) {
++ if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
+ int ret = do_chmod(fname, new_mode);
+ if (ret < 0) {
+ rsyserr(FERROR, errno,
+ "failed to set permissions on %s",
+ full_fname(fname));
+- return 0;
++ goto cleanup;
+ }
+ if (ret == 0) /* ret == 1 if symlink could not be set */
+ updated = 1;
+@@ -329,6 +351,11 @@ int set_file_attrs(char *fname, struct f
+ else
+ rprintf(FCLIENT, "%s is uptodate\n", fname);
+ }
++ cleanup:
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && sxp == &sx2)
++ free_acl(&sx2);
++#endif
+ return updated;
+ }
+
--- old/rsync.h
+++ new/rsync.h
-@@ -658,6 +658,20 @@ struct chmod_mode_struct;
-
- #define UNUSED(x) x __attribute__((__unused__))
+@@ -546,6 +546,14 @@ struct idev_node {
+ #define IN_LOOPBACKNET 127
+ #endif
-+#if HAVE_POSIX_ACLS|HAVE_UNIXWARE_ACLS|HAVE_SOLARIS_ACLS|\
-+ HAVE_HPUX_ACLS|HAVE_IRIX_ACLS|HAVE_AIX_ACLS|HAVE_TRU64_ACLS
++#ifndef HAVE_NO_ACLS
+#define SUPPORT_ACLS 1
+#endif
+
+#define ACLS_NEED_MASK 1
+#endif
+
-+#if defined SUPPORT_ACLS && defined HAVE_SYS_ACL_H
-+#include <sys/acl.h>
+ #define GID_NONE ((gid_t)-1)
+
+ union file_extras {
+@@ -565,6 +573,7 @@ struct file_struct {
+ extern int file_extra_cnt;
+ extern int preserve_uid;
+ extern int preserve_gid;
++extern int preserve_acls;
+
+ #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
+ #define EXTRA_LEN (sizeof (union file_extras))
+@@ -597,10 +606,12 @@ extern int preserve_gid;
+ /* When the associated option is on, all entries will have these present: */
+ #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
+
+ /* These items are per-entry optional and mutally exclusive: */
+ #define F_HL_GNUM(f) OPT_EXTRA(f, LEN64_BUMP(f))->num
+ #define F_HL_PREV(f) OPT_EXTRA(f, LEN64_BUMP(f))->num
++#define F_DEF_ACL(f) OPT_EXTRA(f, LEN64_BUMP(f))->unum
+ #define F_DIRDEV_P(f) (&OPT_EXTRA(f, LEN64_BUMP(f) + 2 - 1)->unum)
+ #define F_DIRNODE_P(f) (&OPT_EXTRA(f, LEN64_BUMP(f) + 3 - 1)->num)
+
+@@ -752,6 +763,17 @@ struct stats {
+
+ struct chmod_mode_struct;
+
++#define EMPTY_ITEM_LIST {NULL, 0, 0}
++
++typedef struct {
++ void *items;
++ size_t count;
++ size_t malloced;
++} item_list;
++
++#define EXPAND_ITEM_LIST(lp, type, incr) \
++ (type*)expand_item_list(lp, sizeof (type), #type, incr)
++
+ #include "byteorder.h"
+ #include "lib/mdfour.h"
+ #include "lib/wildmatch.h"
+@@ -770,6 +792,16 @@ struct chmod_mode_struct;
+ #define NORETURN __attribute__((__noreturn__))
+ #endif
+
++typedef struct {
++ STRUCT_STAT st;
++#ifdef SUPPORT_ACLS
++ struct rsync_acl *acc_acl; /* access ACL */
++ struct rsync_acl *def_acl; /* default ACL */
+#endif
-+#include "smb_acls.h"
++} statx;
++
++#define ACL_READY(sx) ((sx).acc_acl != NULL)
+
#include "proto.h"
/* We have replacement versions of these if they're missing. */
--- old/rsync.yo
+++ new/rsync.yo
-@@ -321,6 +321,7 @@ to the detailed description below for a
- -H, --hard-links preserve hard links
+@@ -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, --archive archive mode; same as -rlptgoD (no -H, -A)
+ --no-OPTION turn off an implied OPTION (e.g. --no-D)
+ -r, --recursive recurse into directories
+ -R, --relative use relative path names
+@@ -323,6 +323,7 @@ to the detailed description below for a
-p, --perms preserve permissions
-E, --executability preserve executability
+ --chmod=CHMOD affect file and/or directory permissions
+ -A, --acls preserve ACLs (implies -p) [non-standard]
- --chmod=CHMOD change destination permissions
-o, --owner preserve owner (super-user only)
-g, --group preserve group
-@@ -742,7 +743,9 @@ quote(itemize(
+ --devices preserve device files (super-user only)
+@@ -771,7 +772,9 @@ quote(itemization(
permissions, though the bf(--executability) option might change just
the execute permission for the file.
it() New files get their "normal" permission bits set to the source
their special permission bits disabled except in the case where a new
directory inherits a setgid bit from its parent directory.
))
-@@ -773,9 +776,11 @@ The preservation of the destination's se
+@@ -802,9 +805,11 @@ The preservation of the destination's se
directories when bf(--perms) is off was added in rsync 2.6.7. Older rsync
versions erroneously preserved the three special permission bits for
newly-created files when bf(--perms) was off, while overriding the
dit(bf(-E, --executability)) This option causes rsync to preserve the
executability (or non-executability) of regular files when bf(--perms) is
-@@ -793,6 +798,10 @@ quote(itemize(
+@@ -822,6 +827,14 @@ quote(itemization(
If bf(--perms) is enabled, this option is ignored.
+dit(bf(-A, --acls)) This option causes rsync to update the destination
+ACLs to be the same as the source ACLs. This nonstandard option only
+works if the remote rsync also supports it. bf(--acls) implies bf(--perms).
++
++The ACL-sending protocol used by this version was first introduced in
++the patch that was shipped with 2.6.8. Sending ACLs to an older version
++of the ACL patch is not supported.
+
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
+@@ -1432,8 +1445,8 @@ if the receiving rsync is at least versi
+ with older versions of rsync, but that also turns on the output of other
+ verbose messages).
+
+-The "%i" escape has a cryptic output that is 9 letters long. The general
+-format is like the string bf(YXcstpogz), where bf(Y) is replaced by the
++The "%i" escape has a cryptic output that is 11 letters long. The general
++format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
+ type of update being done, bf(X) is replaced by the file-type, and the
+ other letters represent attributes that may be output if they are being
+ modified.
+@@ -1482,7 +1495,11 @@ quote(itemization(
+ sender's value (requires bf(--owner) and super-user privileges).
+ it() A bf(g) means the group is different and is being updated to the
+ sender's value (requires bf(--group) and the authority to set the group).
+- it() The bf(z) slot is reserved for future use.
++ it() The bf(u) slot is reserved for reporting update (access) time changes
++ (a feature that is not yet released).
++ it() The bf(a) means that the ACL information changed.
++ it() The bf(x) slot is reserved for reporting extended attribute changes
++ (a feature that is not yet released).
+ ))
+
+ One other output is possible: when deleting files, the "%i" will output
--- old/smb_acls.h
+++ new/smb_acls.h
-@@ -0,0 +1,277 @@
+@@ -0,0 +1,281 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 2.2.x
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
++#ifdef __CYGWIN__
++#define SMB_ACL_LOSES_SPECIAL_MODE_BITS
++#endif
++
+#elif defined HAVE_HPUX_ACLS
+
+/*
+
+#endif /* No ACLs. */
+#endif /* _SMB_ACLS_H */
+--- old/testsuite/acls.test
++++ new/testsuite/acls.test
+@@ -0,0 +1,34 @@
++#! /bin/sh
++
++# This program is distributable under the terms of the GNU GPL (see
++# COPYING).
++
++# Test that rsync handles basic ACL preservation.
++
++. $srcdir/testsuite/rsync.fns
++
++$RSYNC --version | grep ", ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
++case "$setfacl_nodef" in
++true) test_skipped "I don't know how to use your setfacl command" ;;
++esac
++
++makepath "$fromdir/foo"
++echo something >"$fromdir/file1"
++echo else >"$fromdir/file2"
++
++files='foo file1 file2'
++
++setfacl -m u:0:7 "$fromdir/foo" || test_skipped "Your filesystem has ACLs disabled"
++setfacl -m u:0:5 "$fromdir/file1"
++setfacl -m u:0:5 "$fromdir/file2"
++
++$RSYNC -avvA "$fromdir/" "$todir/"
++
++cd "$fromdir"
++getfacl $files >"$scratchdir/acls.txt"
++
++cd "$todir"
++getfacl $files | diff $diffopt "$scratchdir/acls.txt" -
++
++# The script would have aborted on error, so getting here means we've won.
++exit 0
--- old/testsuite/default-acls.test
+++ new/testsuite/default-acls.test
-@@ -0,0 +1,55 @@
+@@ -0,0 +1,65 @@
+#! /bin/sh
+
-+# This program is distributable under the terms of the GNU GPL see
++# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync obeys default ACLs. -- Matt McCutchen
+. $srcdir/testsuite/rsync.fns
+
+$RSYNC --version | grep ", ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
-+setfacl -dm u::rwx,g::---,o::--- "$scratchdir" || test_skipped "Your filesystem has ACLs disabled"
++case "$setfacl_nodef" in
++true) test_skipped "I don't know how to use your setfacl command" ;;
++*-k*) opts='-dm u::7,g::5,o:5' ;;
++*) opts='-m d:u::7,d:g::5,d:o:5' ;;
++esac
++setfacl $opts "$scratchdir" || test_skipped "Your filesystem has ACLs disabled"
+
+# Call as: testit <dirname> <default-acl> <file-expected> <program-expected>
+testit() {
+ todir="$scratchdir/$1"
+ mkdir "$todir"
-+ # FIXME This doesn't work on solaris...
-+ setfacl -k "$todir"
-+ [ "$2" ] && setfacl -dm "$2" "$todir"
++ $setfacl_nodef "$todir"
++ if [ "$2" ]; then
++ case "$setfacl_nodef" in
++ *-k*) opts="-dm $2" ;;
++ *) opts="-m `echo $2 | sed 's/\([ugom]:\)/d:\1/g'`"
++ esac
++ setfacl $opts "$todir"
++ fi
+ # Make sure we obey ACLs when creating a directory to hold multiple transferred files,
+ # even though the directory itself is outside the transfer
+ $RSYNC -rvv "$scratchdir/dir" "$scratchdir/file" "$scratchdir/program" "$todir/to/"
+
+# Test some target directories
+umask 0077
-+testit da777 u::rwx,g::rwx,o::rwx rw-rw-rw- rwxrwxrwx
-+testit da775 u::rwx,g::rwx,o::r-x rw-rw-r-- rwxrwxr-x
-+testit da750 u::rwx,g::r-x,o::--- rw-r----- rwxr-x---
-+testit da770mask u::rwx,g::---,m::rwx,o::--- rw-rw---- rwxrwx---
++testit da777 u::7,g::7,o:7 rw-rw-rw- rwxrwxrwx
++testit da775 u::7,g::7,o:5 rw-rw-r-- rwxrwxr-x
++testit da750 u::7,g::5,o:0 rw-r----- rwxr-x---
++testit da770mask u::7,u:0:7,g::0,m:7,o:0 rw-rw---- rwxrwx---
+testit noda1 '' rw------- rwx------
+umask 0000
+testit noda2 '' rw-rw-rw- rwxrwxrwx
+
+# Hooray
+exit 0
+--- old/testsuite/devices.test
++++ new/testsuite/devices.test
+@@ -42,14 +42,14 @@ touch -r "$fromdir/block" "$fromdir/bloc
+ $RSYNC -ai "$fromdir/block" "$todir/block2" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD+++++++ block
++cD+++++++++ block
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 1 failed"
+
+ $RSYNC -ai "$fromdir/block2" "$todir/block" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD+++++++ block2
++cD+++++++++ block2
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 2 failed"
+
+@@ -58,7 +58,7 @@ sleep 1
+ $RSYNC -Di "$fromdir/block3" "$todir/block" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD..T.... block3
++cD..T...... block3
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
+
+@@ -66,15 +66,15 @@ $RSYNC -aiHvv "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ filter_outfile
+ cat <<EOT >"$chkfile"
+-.d..t.... ./
+-cD..t.... block
+-cD block2
+-cD+++++++ block3
+-hD+++++++ block2.5 => block3
+-cD+++++++ char
+-cD+++++++ char2
+-cD+++++++ char3
+-cS+++++++ fifo
++.d..t...... ./
++cD..t...... block
++cD block2
++cD+++++++++ block3
++hD+++++++++ block2.5 => block3
++cD+++++++++ char
++cD+++++++++ char2
++cD+++++++++ char3
++cS+++++++++ fifo
+ EOT
+ if test ! -b "$fromdir/block2.5"; then
+ sed -e '/block2\.5/d' \
+@@ -94,15 +94,15 @@ if test -b "$fromdir/block2.5"; then
+ $RSYNC -aii --link-dest="$todir" "$fromdir/" "$chkdir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd ./
+-hD block
+-hD block2
+-hD block2.5
+-hD block3
+-hD char
+-hD char2
+-hD char3
+-hS fifo
++cd ./
++hD block
++hD block2
++hD block2.5
++hD block3
++hD char
++hD char2
++hD char3
++hS fifo
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 4 failed"
+ fi
+--- old/testsuite/itemize.test
++++ new/testsuite/itemize.test
+@@ -47,16 +47,16 @@ rm -f "$to2dir" "$to2dir.test"
+ $RSYNC -iplr "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ foo/_P30_
+-cd+++++++ bar/baz/
+->f+++++++ bar/baz/rsync
+-cd+++++++ foo/_P29_
+->f+++++++ foo/config1
+->f+++++++ foo/config2
+->f+++++++ foo/extra
+-cL+++++++ foo/sym -> ../bar/baz/rsync
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ foo/_P30_
++cd+++++++++ bar/baz/
++>f+++++++++ bar/baz/rsync
++cd+++++++++ foo/_P29_
++>f+++++++++ foo/config1
++>f+++++++++ foo/config2
++>f+++++++++ foo/extra
++cL+++++++++ foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 1 failed"
+
+@@ -68,10 +68,10 @@ chmod 601 "$fromdir/foo/config2"
+ $RSYNC -iplrH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+->f..T.... bar/baz/rsync
+->f..T.... foo/config1
+->f.sTp... foo/config2
+-hf..T.... foo/extra => foo/config1
++>f..T...... bar/baz/rsync
++>f..T...... foo/config1
++>f.sTp..... foo/config2
++hf..T...... foo/extra => foo/config1
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 2 failed"
+
+@@ -88,12 +88,12 @@ chmod 777 "$todir/bar/baz/rsync"
+ $RSYNC -iplrtc "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-.d..t.... foo/_P30_
+-.f..tp... bar/baz/rsync
+-.d..t.... foo/_P29_
+-.f..t.... foo/config1
+->fcstp... foo/config2
+-cL..T.... foo/sym -> ../bar/baz/rsync
++.d..t...... foo/_P30_
++.f..tp..... bar/baz/rsync
++.d..t...... foo/_P29_
++.f..t...... foo/config1
++>fcstp..... foo/config2
++cL..T...... foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
+
+@@ -118,15 +118,15 @@ $RSYNC -ivvplrtH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ filter_outfile
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-.d ./
+-.d bar/
+-.d bar/baz/
+-.f...p... bar/baz/rsync
+-.d foo/
+-.f foo/config1
+->f..t.... foo/config2
+-hf foo/extra
+-.L foo/sym -> ../bar/baz/rsync
++.d ./
++.d bar/
++.d bar/baz/
++.f...p..... bar/baz/rsync
++.d foo/
++.f foo/config1
++>f..t...... foo/config2
++hf foo/extra
++.L foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 5 failed"
+
+@@ -145,8 +145,8 @@ touch "$todir/foo/config2"
+ $RSYNC -iplrtH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-.f...p... foo/config1
+->f..t.... foo/config2
++.f...p..... foo/config1
++>f..t...... foo/config2
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 7 failed"
+
+@@ -154,15 +154,15 @@ $RSYNC -ivvplrtH --copy-dest=../to "$fro
+ | tee "$outfile"
+ filter_outfile
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-cd ./
+-cd bar/
+-cd bar/baz/
+-cf bar/baz/rsync
+-cd foo/
+-cf foo/config1
+-cf foo/config2
+-hf foo/extra => foo/config1
+-cL foo/sym -> ../bar/baz/rsync
++cd ./
++cd bar/
++cd bar/baz/
++cf bar/baz/rsync
++cd foo/
++cf foo/config1
++cf foo/config2
++hf foo/extra => foo/config1
++cL foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 8 failed"
+
+@@ -170,7 +170,7 @@ rm -rf "$to2dir"
+ $RSYNC -iplrtH --copy-dest=../to "$fromdir/" "$to2dir/" \
+ | tee "$outfile"
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-hf foo/extra => foo/config1
++hf foo/extra => foo/config1
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 9 failed"
+
+@@ -196,15 +196,15 @@ $RSYNC -ivvplrtH --link-dest="$todir" "$
+ | tee "$outfile"
+ filter_outfile
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-cd ./
+-cd bar/
+-cd bar/baz/
+-hf bar/baz/rsync
+-cd foo/
+-hf foo/config1
+-hf foo/config2
+-hf foo/extra => foo/config1
+-$L foo/sym -> ../bar/baz/rsync
++cd ./
++cd bar/
++cd bar/baz/
++hf bar/baz/rsync
++cd foo/
++hf foo/config1
++hf foo/config2
++hf foo/extra => foo/config1
++$L foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 11 failed"
+
+@@ -244,15 +244,15 @@ $RSYNC -ivvplrtH --compare-dest="$todir"
+ | tee "$outfile"
+ filter_outfile
+ sed -e "$sed_cmd" <<EOT >"$chkfile"
+-cd ./
+-cd bar/
+-cd bar/baz/
+-.f bar/baz/rsync
+-cd foo/
+-.f foo/config1
+-.f foo/config2
+-.f foo/extra
+-.L foo/sym -> ../bar/baz/rsync
++cd ./
++cd bar/
++cd bar/baz/
++.f bar/baz/rsync
++cd foo/
++.f foo/config1
++.f foo/config2
++.f foo/extra
++.L foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 15 failed"
+
--- old/uidlist.c
+++ new/uidlist.c
-@@ -34,6 +34,7 @@
- extern int verbose;
+@@ -36,6 +36,7 @@ extern int verbose;
+ extern int am_root;
extern int preserve_uid;
extern int preserve_gid;
+extern int preserve_acls;
extern int numeric_ids;
- extern int am_root;
-@@ -274,7 +275,7 @@ void send_uid_list(int f)
- if (numeric_ids)
- return;
+ struct idlist {
+@@ -272,7 +273,7 @@ void send_uid_list(int f)
+ {
+ struct idlist *list;
- if (preserve_uid) {
+ if (preserve_uid || preserve_acls) {
int len;
/* we send sequences of uid/byte-length/name */
for (list = uidlist; list; list = list->next) {
-@@ -291,7 +292,7 @@ void send_uid_list(int f)
+@@ -289,7 +290,7 @@ void send_uid_list(int f)
write_int(f, 0);
}
int len;
for (list = gidlist; list; list = list->next) {
if (!list->name)
-@@ -312,7 +313,7 @@ void recv_uid_list(int f, struct file_li
+@@ -329,18 +330,28 @@ void recv_uid_list(int f, struct file_li
+ {
int id, i;
- char *name;
- if (preserve_uid && !numeric_ids) {
+ if ((preserve_uid || preserve_acls) && !numeric_ids) {
/* read the uid list */
- while ((id = read_int(f)) != 0) {
- int len = read_byte(f);
-@@ -324,7 +325,7 @@ void recv_uid_list(int f, struct file_li
- }
+ while ((id = read_int(f)) != 0)
+ recv_user_name(f, (uid_t)id);
}
- if (preserve_gid && !numeric_ids) {
+ if ((preserve_gid || preserve_acls) && !numeric_ids) {
/* read the gid list */
- while ((id = read_int(f)) != 0) {
- int len = read_byte(f);
-@@ -336,6 +337,16 @@ void recv_uid_list(int f, struct file_li
- }
+ while ((id = read_int(f)) != 0)
+ recv_group_name(f, (gid_t)id);
}
+#ifdef SUPPORT_ACLS
/* Now convert all the uids/gids from sender values to our values. */
if (am_root && preserve_uid && !numeric_ids) {
for (i = 0; i < flist->count; i++)
+--- old/util.c
++++ new/util.c
+@@ -1467,3 +1467,31 @@ int bitbag_next_bit(struct bitbag *bb, i
+
+ return -1;
+ }
++
++void *expand_item_list(item_list *lp, size_t item_size,
++ const char *desc, int incr)
++{
++ /* First time through, 0 <= 0, so list is expanded. */
++ if (lp->malloced <= lp->count) {
++ void *new_ptr;
++ size_t new_size = lp->malloced;
++ if (incr < 0)
++ new_size -= incr; /* increase slowly */
++ else if (new_size < (size_t)incr)
++ new_size += incr;
++ else
++ new_size *= 2;
++ new_ptr = realloc_array(lp->items, char, new_size * item_size);
++ if (verbose >= 4) {
++ rprintf(FINFO, "[%s] expand %s to %.0f bytes, did%s move\n",
++ who_am_i(), desc, (double)new_size * item_size,
++ new_ptr == lp->items ? " not" : "");
++ }
++ if (!new_ptr)
++ out_of_memory("expand_item_list");
++
++ lp->items = new_ptr;
++ lp->malloced = new_size;
++ }
++ return (char*)lp->items + (lp->count++ * item_size);
++}