-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
-The program currently complains when the --acls (-A) option is used to copy
-from a disk that doesn't support ACLs. This should be changed to silently
-notice that no ACLs are available to copy. Of course, trying to write out
-ACLs to a non-ACL-supporting disk should complain.
+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
popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
--- old/acls.c
+++ new/acls.c
-@@ -0,0 +1,1202 @@
-+/* -*- 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,1098 @@
++/*
++ * 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 preserve_acls;
-+extern int am_root;
+extern int dry_run;
++extern int read_only;
++extern int list_only;
+extern int orig_umask;
++extern int preserve_acls;
++extern unsigned int file_struct_len;
++
++/* === 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;
+
-+static void expand_rsync_acl(rsync_acl *racl)
++/* === Calculations on ACL types === */
++
++static const char *str_acl_type(SMB_ACL_TYPE_T type)
+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
-+ 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 type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS"
++ : type == SMB_ACL_TYPE_DEFAULT ? "SMB_ACL_TYPE_DEFAULT"
++ : "unknown SMB_ACL_TYPE_T";
++}
+
-+ racl->races = new_ptr;
-+ racl->malloced = new_size;
++#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)
+
-+ if (!racl->races)
-+ out_of_memory("expand_rsync_acl");
++static int count_racl_entries(const rsync_acl *racl)
++{
++ 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);
++}
++
++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
++}
++
++/* 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.
-+ * (Diabolical, rsync guys!) */
-+ 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;
++
++ if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) {
++ rsyserr(FERROR, errno, "pack_smb_acl: sys_acl_init()");
++ return False;
++ }
+
-+ racl_list->racls = new_ptr;
-+ racl_list->malloced = new_size;
++ 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) );
+ }
-+ free(racl_list->racls);
-+ racl_list->racls = NULL;
-+ racl_list->malloced = 0;
-+}
++
++#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) );
++ }
++#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)
++/* Return the Access Control List for the given filename. */
++int get_acl(const char *fname, statx *sxp)
+{
-+ rsync_ace *race;
-+ size_t count = racl->count;
-+ write_int(f, count);
-+ for (race = racl->races; count--; race++) {
-+ char 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 (%0x) on ACE; disregarding\n",
-+ race->tag_type);
-+ continue;
-+ }
-+ write_byte(f, ch);
-+ write_byte(f, race->access);
-+ if (isupper((int)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];
++ SMB_ACL_TYPE_T type;
+
++ if (S_ISLNK(sxp->st.st_mode))
++ return 0;
+
-+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";
-+}
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
++ 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;
+
-+/* Generate the ACL(s) for this flist entry;
-+ * ACL(s) are either sent or cleaned-up by send_acl() below. */
++ if (sacl) {
++ BOOL ok = unpack_smb_acl(racl, sacl);
+
-+int make_acl(const struct file_struct *file, const char *fname)
-+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ rsync_acl *curr_racl;
-+ if (!preserve_acls || S_ISLNK(file->mode))
-+ return 1;
-+ for (type = &types[0], curr_racl = &_curr_rsync_acls[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++, curr_racl++) {
-+ SMB_ACL_T sacl;
-+ BOOL ok;
-+ *curr_racl = rsync_acl_initializer;
-+ if (!(sacl = sys_acl_get_file(fname, *type))) {
-+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(*type), strerror(errno));
++ sys_acl_free_acl(sacl);
++ if (!ok) {
++ free_acl(sxp);
++ return -1;
++ }
++ } else if (errno == ENOTSUP) {
++ /* ACLs are not supported, so pretend we have a basic ACL. */
++ if (type == SMB_ACL_TYPE_ACCESS)
++ rsync_acl_fake_perms(racl, sxp->st.st_mode);
++ } else {
++ rsyserr(FERROR, errno, "get_acl: sys_acl_get_file(%s, %s)",
++ fname, str_acl_type(type));
++ free_acl(sxp);
+ return -1;
+ }
-+ ok = unpack_smb_acl(curr_racl, sacl);
-+ sys_acl_free_acl(sacl);
-+ if (!ok)
-+ return -1;
-+ }
-+ 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). */
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
-+void send_acl(const struct file_struct *file, int f)
-+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ rsync_acl *curr_racl;
-+ if (!preserve_acls || S_ISLNK(file->mode))
-+ return;
-+ for (type = &types[0], curr_racl = &_curr_rsync_acls[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++, curr_racl++) {
-+ 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;
-+ }
-+ }
++ return 0;
+}
+
-+/* 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 */
-+};
++/* === Send functions === */
+
-+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.
-+ * (Diabolical, rsync guys!) */
-+ 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)
++/* Send an rsync ACL over the file descriptor. */
++static void send_rsync_acl(int f, const rsync_acl *racl)
+{
-+ free(fileaclidx_list->fileaclidxs);
-+ fileaclidx_list->fileaclidxs = NULL;
-+ fileaclidx_list->malloced = 0;
++ 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);
++ }
++ 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);
++ }
+}
-+#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)
++/* 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)
+{
-+ return type == SMB_ACL_TYPE_ACCESS ? &_smb_acl_lists[0] :
-+ &_smb_acl_lists[1];
-+}
++ SMB_ACL_TYPE_T type;
++ rsync_acl *racl, *new_racl;
++ item_list *racl_list;
++ int ndx;
+
-+static void expand_smb_acl_list(smb_acl_list *sacl_list)
-+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
-+ 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");
++ if (S_ISLNK(sxp->st.st_mode))
++ return;
++
++ 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;
+ }
+
-+ sacl_list->sacls = new_ptr;
-+ sacl_list->malloced = new_size;
++ /* Avoid sending values that can be inferred from other data,
++ * but only when preserve_acls == 1 (it is 2 when we must be
++ * backward compatible with older acls.diff versions). */
++ if (type == SMB_ACL_TYPE_ACCESS && preserve_acls == 1)
++ 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;
++ }
++ racl = sxp->def_acl;
++ racl_list = &default_acl_list;
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
-+ if (!sacl_list->sacls)
-+ out_of_memory("expand_smb_acl_list");
-+ }
++ free_acl(sxp);
+}
+
-+#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++);
-+ }
-+ free(sacl_list->sacls);
-+ sacl_list->sacls = NULL;
-+ sacl_list->malloced = 0;
-+}
-+#endif
++/* === Receive functions === */
+
-+/* build an SMB_ACL_T corresponding to an rsync_acl */
-+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
++static void receive_rsync_acl(rsync_acl *racl, int f, SMB_ACL_TYPE_T type)
+{
-+ size_t count = racl->count;
-+ rsync_ace *race = racl->races;
-+ const char *errfun = NULL;
-+ *smb_acl = sys_acl_init(count);
-+ if (!*smb_acl) {
-+ rprintf(FERROR, "pack_smb_acl: sys_acl_int(): %s\n",
-+ strerror(errno));
-+ return False;
-+ }
-+ 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;
-+ }
-+ if (race->access & 4)
-+ if (sys_acl_add_perm(permset, SMB_ACL_READ)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (race->access & 2)
-+ if (sys_acl_add_perm(permset, SMB_ACL_WRITE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (race->access & 1)
-+ if (sys_acl_add_perm(permset, SMB_ACL_EXECUTE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (sys_acl_set_permset(entry, permset)) {
-+ errfun = "sys_acl_set_permset";
-+ break;
-+ }
-+ }
-+ if (errfun) {
-+ sys_acl_free_acl(*smb_acl);
-+ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
-+ strerror(errno));
-+ return False;
-+ }
-+ return True;
-+}
++ 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;
+
-+static void receive_rsync_acl(rsync_acl *racl, int f)
-+{
-+#if ACLS_NEED_MASK
-+ uchar required_mask_perm = 0;
-+#endif
-+ BOOL saw_mask = False;
-+ BOOL saw_user_obj = False, saw_group_obj = False,
-+ saw_other = False;
-+ size_t count = read_int(f);
-+ rsync_ace *race;
-+ if (!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;
-+ saw_user_obj = True;
-+ 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;
-+ saw_group_obj = True;
-+ 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;
-+ saw_other = True;
++ tag_type = SMB_ACL_GROUP;
+ break;
+ case 'm':
-+ race->tag_type = SMB_ACL_MASK;
-+ saw_mask = True;
-+ break;
-+ 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 (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ race->id = read_int(f);
-+#if ACLS_NEED_MASK
-+ required_mask_perm |= race->access;
-+#endif
-+ }
-+#if ACLS_NEED_MASK
-+ else if (race->tag_type == SMB_ACL_GROUP_OBJ)
-+ required_mask_perm |= race->access;
-+#endif
-+
-+ }
-+ if (!saw_user_obj) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_USER_OBJ;
-+ race->access = 7;
-+ }
-+ if (!saw_group_obj) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_GROUP_OBJ;
-+ race->access = 0;
-+ }
-+ if (!saw_other) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_OTHER;
-+ race->access = 0;
-+ }
-+#if ACLS_NEED_MASK
-+ if (!saw_mask) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_MASK;
-+ race->access = required_mask_perm;
-+ }
-+#else
-+ /* If we, a system without ACLS_NEED_MASK, received data from a
-+ * system that has masks, throw away the extraneous CLASS_OBJs. */
-+ if (saw_mask && racl->count == 4) {
-+ rsync_ace *group_obj_race = NULL, *mask_race = NULL;
-+ rsync_ace *p;
-+ size_t i;
-+ for (i = 0, p = racl->races; i < racl->count; i++, p++) {
-+ if (p->tag_type == SMB_ACL_MASK)
-+ mask_race = p;
-+ else if (p->tag_type == SMB_ACL_GROUP_OBJ)
-+ group_obj_race = p;
-+ }
-+ if (mask_race == NULL || group_obj_race == NULL) {
-+ rprintf(FERROR, "receive_rsync_acl: have four ACES "
-+ "and one's ACL_MASK but missing "
-+ "either it or ACL_GROUP_OBJ, "
-+ "when pruning ACL\n");
-+ } else {
-+ /* mask off group perms with it first */
-+ group_obj_race->access &= mask_race->access;
-+ /* dump mask_race; re-slot any followers-on */
-+ racl->count--;
-+ if (mask_race != &racl->races[racl->count]) {
-+ *mask_race = racl->races[racl->count];
-+ saw_user_obj = False; /* force re-sort */
++ 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);
++ }
++ 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->access = access;
++ ida->id = read_int(f);
++ computed_mask_bits |= access;
+ }
-+#endif
-+#if ACLS_NEED_MASK
-+ if (!(saw_user_obj && saw_group_obj && saw_other && saw_mask))
-+#else
-+ if (!(saw_user_obj && saw_group_obj && saw_other))
-+#endif
-+ sort_rsync_acl(racl);
-+}
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
+
-+/* receive and build the rsync_acl_lists */
++ 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;
++ }
++ } else if (racl->mask == NO_ENTRY) /* Must be non-empty with lists. */
++ racl->mask = computed_mask_bits | (racl->group_obj & 7);
++}
+
++/* 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,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ char *fname;
-+ if (!preserve_acls || S_ISLNK(file->mode))
++ SMB_ACL_TYPE_T type;
++ item_list *racl_list;
++ char *ndx_ptr;
++
++ if (S_ISLNK(file->mode))
+ return;
-+ fname = f_name(file, NULL);
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++) {
-+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(*type);
-+ uchar tag;
-+ expand_file_acl_index_list(fileaclidx_list);
-+
-+ tag = read_byte(f);
++
++ type = SMB_ACL_TYPE_ACCESS;
++ racl_list = &access_acl_list;
++ ndx_ptr = (char*)file + file_struct_len;
++ do {
++ char tag = read_byte(f);
++ int ndx;
++
+ if (tag == 'A' || tag == 'a') {
-+ if (*type != SMB_ACL_TYPE_ACCESS) {
++ 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) {
++ 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, 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;
+ }
-+ }
++ SIVAL(ndx_ptr, 0, ndx);
++ racl_list = &default_acl_list;
++ ndx_ptr += 4;
++ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
+}
+
-+static int file_acl_index_list_sorter(const void *f1, const void *f2)
++/* 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)
+{
-+ 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;
-+}
++ SMB_ACL_TYPE_T type;
++ rsync_acl *racl;
++ item_list *racl_list;
++ char *ndx_ptr;
++ int ndx;
+
-+void sort_file_acl_index_lists()
-+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ if (!preserve_acls)
++ if (S_ISLNK(file->mode))
+ return;
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++)
-+ {
-+ 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);
-+ }
-+}
+
-+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;
++ type = SMB_ACL_TYPE_ACCESS;
++ racl = sxp->acc_acl;
++ racl_list = &access_acl_list;
++ ndx_ptr = (char*)file + file_struct_len;
+ 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;
-+}
++ 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;
++ }
++ SIVAL(ndx_ptr, 0, ndx);
++ racl = sxp->def_acl;
++ racl_list = &default_acl_list;
++ ndx_ptr += 4;
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
-+/* for duplicating ACLs on backups when using backup_dir */
++ free_acl(sxp);
++}
+
-+int dup_acl(const char *orig, const char *bak, mode_t mode)
++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,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ int ret = 0;
-+ if (!preserve_acls)
-+ return 1;
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(mode));
-+ type++) {
-+ 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;
++ SMB_ACL_ENTRY_T entry;
++ const char *errfun;
++ int rc;
++
++ 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
++ }
++
++ 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_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 */
++ 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;
+ }
-+ if (*type == SMB_ACL_TYPE_DEFAULT && !racl_orig.count) {
-+ if (-1 == sys_acl_delete_def_file(bak)) {
-+ rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n",
-+ bak, strerror(errno));
-+ ret = -1;
-+ }
-+ } else if (-1 == sys_acl_set_file(bak, *type, sacl_bak)) {
-+ rprintf(FERROR, "dup_acl: sys_acl_set_file(%s, %s): %s\n",
-+ bak, str_acl_type(*type), strerror(errno));
-+ ret = -1;
++ }
++ if (rc) {
++ error_exit:
++ if (errfun) {
++ rsyserr(FERROR, errno, "change_sacl_perms: %s()",
++ errfun);
+ }
-+ 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);
++ return (mode_t)~0;
+ }
-+ return ret;
-+}
+
-+/* 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 };
++#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)
++ && (old_mode & CHMOD_BITS) == (mode & CHMOD_BITS))
++ old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
++#endif
+
-+void push_keep_backup_acl(const struct file_struct *file,
-+ const char *orig, const char *dest)
-+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ backup_orig_file = file;
-+ backup_orig_fname = orig;
-+ backup_dest_fname = dest;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode))
-+ *sacl = NULL;
-+ else {
-+ 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));
-+ }
-+ }
-+ }
-+ }
++ /* Return the mode of the file on disk, as we will set them. */
++ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
+}
+
-+static int set_keep_backup_acl()
++/* 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)
+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ int ret = 0;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*sacl) {
-+ if (-1 == sys_acl_set_file(backup_dest_fname,
-+ *type, *sacl))
-+ {
-+ 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;
-+ }
-+ }
-+ }
-+ return ret;
-+ }
-+ return 1;
-+}
++ int unchanged = 1;
++ SMB_ACL_TYPE_T type;
++ char *ndx_ptr;
+
-+void cleanup_keep_backup_acl()
-+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ backup_orig_file = NULL;
-+ backup_orig_fname = null_string;
-+ backup_dest_fname = null_string;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*sacl)
-+ sys_acl_free_acl(*sacl);
-+ *sacl = NULL;
-+ }
++ if (!dry_run && (read_only || list_only)) {
++ errno = EROFS;
++ return -1;
+ }
-+}
-+
-+/* set ACL on rsync-ed or keep_backup-ed file */
+
-+int set_acl(const char *fname, const struct file_struct *file)
-+{
-+ int updated = 0;
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ if (dry_run || !preserve_acls || S_ISLNK(file->mode))
++ if (S_ISLNK(file->mode))
+ return 1;
-+ if (file == backup_orig_file) {
-+ if (!strcmp(fname, backup_dest_fname))
-+ return set_keep_backup_acl();
-+ }
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++) {
-+ 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));
-+ updated = -1;
-+ continue;
-+ }
-+ ok = unpack_smb_acl(&racl_orig, sacl_orig);
-+ sys_acl_free_acl(sacl_orig);
-+ if (!ok) {
-+ updated = -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 (-1 == sys_acl_delete_def_file(fname)) {
-+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
-+ fname, strerror(errno));
-+ updated = -1;
++
++ type = SMB_ACL_TYPE_ACCESS;
++ ndx_ptr = (char*)file + file_struct_len;
++ do {
++ acl_duo *duo_item;
++ BOOL eq;
++ int32 ndx = IVAL(ndx_ptr, 0);
++
++ ndx_ptr += 4;
++
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ 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 {
-+ if (!*sacl_new)
-+ if (!pack_smb_acl(sacl_new, racl_new)) {
-+ updated = -1;
++ 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;
+ }
-+ if (-1 == sys_acl_set_file(fname, *type, *sacl_new)) {
-+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
-+ fname, str_acl_type(*type),
-+ strerror(errno));
-+ updated = -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 (!updated)
-+ updated = 1;
-+ }
-+ return updated;
++ if (unchanged == 1)
++ unchanged = 0;
++ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
++
++ 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)
++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;
-+ return 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 0;
++ return NULL;
+}
+
-+static id_t next_acl_list_id(SMB_ACL_TAG_T tag_type)
++static id_t *next_acl_list_id(SMB_ACL_TAG_T tag_type)
+{
+ for (; *enum_racl_list; enum_racl_list++) {
-+ id_t id = next_acl_id(tag_type, *enum_racl_list);
++ id_t *id = next_acl_id(tag_type, *enum_racl_list);
+ if (id)
+ return id;
+ }
+ enum_racl_list = &_enum_racl_lists[0];
-+ return 0;
++ return NULL;
+}
+
-+id_t next_acl_uid()
++id_t *next_acl_uid()
+{
+ return next_acl_list_id(SMB_ACL_USER);
+}
+
-+id_t next_acl_gid()
++id_t *next_acl_gid()
+{
+ return next_acl_list_id(SMB_ACL_GROUP);
+}
+
-+/* referring to the global context enum_entry, sets the entry's id */
-+static void set_acl_id(id_t id)
-+{
-+ (*enum_racl_list)->racls[enum_racl_index].races[enum_race_index++].id = id;
-+}
-+
-+void acl_uid_map(id_t uid)
-+{
-+ set_acl_id(uid);
-+}
-+
-+void acl_gid_map(id_t gid)
-+{
-+ set_acl_id(gid);
-+}
-+
-+#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)
+ }
+
+ /* 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
-@@ -135,6 +135,7 @@ static int make_bak_dir(char *fullpath)
+@@ -29,6 +29,7 @@ extern char *backup_suffix;
+ extern char *backup_dir;
+
+ extern int am_root;
++extern int preserve_acls;
+ extern int preserve_devices;
+ extern int preserve_specials;
+ extern int preserve_links;
+@@ -94,7 +95,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;
+@@ -126,13 +128,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);
-+ (void)DUP_ACL(end, fullpath, st.st_mode);
+- do_lchown(fullpath, st.st_uid, st.st_gid);
+- do_chmod(fullpath, st.st_mode);
++#ifdef SUPPORT_ACLS
++ 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 = '/';
-@@ -188,6 +189,8 @@ static int keep_backup(char *fname)
+@@ -170,15 +183,18 @@ static int robust_move(char *src, char *
+ * We will move the file to be deleted into a parallel directory tree. */
+ static int keep_backup(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 */
+@@ -186,6 +202,13 @@ static int keep_backup(char *fname)
if (!(buf = get_backup_name(fname)))
return 0;
-+ PUSH_KEEP_BACKUP_ACL(file, fname, buf);
++#ifdef SUPPORT_ACLS
++ 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))) {
-@@ -263,6 +266,7 @@ static int keep_backup(char *fname)
- }
- }
- set_file_attrs(buf, file, NULL, 0);
-+ CLEANUP_KEEP_BACKUP_ACL();
- free(file);
-
- if (verbose > 1) {
+@@ -254,7 +277,7 @@ static int keep_backup(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/configure.in
+++ new/configure.in
-@@ -482,6 +482,11 @@ if test x"$ac_cv_func_strcasecmp" = x"no
+@@ -515,6 +515,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)
+@@ -779,6 +784,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
-@@ -967,6 +967,8 @@ static struct file_struct *send_file_nam
- f == -2 ? SERVER_FILTERS : ALL_FILTERS);
- if (!file)
- return NULL;
-+ if (MAKE_ACL(file, fname) < 0)
-+ return NULL;
+@@ -40,6 +40,7 @@ extern int filesfrom_fd;
+ extern int one_file_system;
+ extern int copy_dirlinks;
+ extern int keep_dirlinks;
++extern int preserve_acls;
+ extern int preserve_links;
+ extern int preserve_hard_links;
+ extern int preserve_devices;
+@@ -133,6 +134,8 @@ static void list_file_entry(struct file_
+
+ permstring(permbuf, f->mode);
+
++ /* 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",
+@@ -495,6 +498,9 @@ static struct file_struct *receive_file_
+ char thisname[MAXPATHLEN];
+ unsigned int l1 = 0, l2 = 0;
+ int alloc_len, basename_len, dirname_len, linkname_len, sum_len;
++#ifdef SUPPORT_ACLS
++ int xtra_len;
++#endif
+ OFF_T file_length;
+ char *basename, *dirname, *bp;
+ struct file_struct *file;
+@@ -598,13 +604,27 @@ static struct file_struct *receive_file_
+
+ sum_len = always_checksum && S_ISREG(mode) ? MD4_SUM_LENGTH : 0;
+
++#ifdef SUPPORT_ACLS
++ /* We need one or two index int32s when we're preserving ACLs. */
++ if (preserve_acls)
++ xtra_len = (S_ISDIR(mode) ? 2 : 1) * 4;
++ else
++ xtra_len = 0;
++#endif
++
+ alloc_len = file_struct_len + dirname_len + basename_len
++#ifdef SUPPORT_ACLS
++ + xtra_len
++#endif
+ + linkname_len + sum_len;
+ bp = pool_alloc(flist->file_pool, alloc_len, "receive_file_entry");
+
+ file = (struct file_struct *)bp;
+ memset(bp, 0, file_struct_len);
+ bp += file_struct_len;
++#ifdef SUPPORT_ACLS
++ bp += xtra_len;
++#endif
+
+ file->modtime = modtime;
+ file->length = file_length;
+@@ -699,6 +719,11 @@ static struct file_struct *receive_file_
+ read_buf(f, sum, checksum_len);
+ }
+
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ receive_acl(file, f);
++#endif
++
+ return file;
+ }
+
+@@ -949,6 +974,9 @@ static struct file_struct *send_file_nam
+ unsigned short flags)
+ {
+ struct file_struct *file;
++#ifdef SUPPORT_ACLS
++ statx sx;
++#endif
+ file = make_file(fname, flist, stp, flags,
+ f == -2 ? SERVER_FILTERS : ALL_FILTERS);
+@@ -958,6 +986,15 @@ static struct file_struct *send_file_nam
if (chmod_modes && !S_ISLNK(file->mode))
file->mode = tweak_mode(file->mode, chmod_modes);
-@@ -978,6 +980,10 @@ static struct file_struct *send_file_nam
+
++#ifdef SUPPORT_ACLS
++ if (preserve_acls) {
++ 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);
+@@ -965,6 +1002,15 @@ static struct file_struct *send_file_nam
if (file->basename[0]) {
flist->files[flist->count++] = file;
send_file_entry(file, f);
-+ SEND_ACL(file, f);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ send_acl(&sx, f);
++#endif
+ } else {
-+ /* Cleanup unsent ACL(s). */
-+ SEND_ACL(file, -1);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(&sx);
++#endif
}
return file;
}
-@@ -1366,6 +1372,8 @@ struct file_list *recv_file_list(int f)
- flags |= read_byte(f) << 8;
- file = receive_file_entry(flist, flags, f);
+--- old/generator.c
++++ new/generator.c
+@@ -35,6 +35,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;
+@@ -85,6 +86,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;
+@@ -317,22 +319,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
+- && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS))
++ && (sxp->st.st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS))
+ return 0;
+
+- if (am_root && preserve_uid && st->st_uid != file->uid)
++ if (am_root && preserve_uid && sxp->st.st_uid != file->uid)
+ return 0;
-+ RECEIVE_ACL(file, f);
+- if (preserve_gid && file->gid != GID_NONE && st->st_gid != file->gid)
++ if (preserve_gid && file->gid != GID_NONE && sxp->st.st_gid != file->gid)
++ return 0;
+
- if (S_ISREG(file->mode) || S_ISLNK(file->mode))
- stats.total_size += file->length;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && set_acl(NULL, file, sxp) == 0)
+ return 0;
++#endif
-@@ -1388,6 +1396,8 @@ struct file_list *recv_file_list(int f)
+ return 1;
+ }
- clean_flist(flist, relative_paths, 1);
+-void itemize(struct file_struct *file, int ndx, int statret, STRUCT_STAT *st,
++void itemize(struct file_struct *file, int ndx, int statret, statx *sxp,
+ int32 iflags, uchar fnamecmp_type, char *xname)
+ {
+ if (statret >= 0) { /* A from-dest-dir statret can == 1! */
+@@ -340,19 +347,23 @@ void itemize(struct file_struct *file, i
+ : S_ISDIR(file->mode) ? !omit_dir_times
+ : !S_ISLNK(file->mode);
-+ SORT_FILE_ACL_INDEX_LISTS();
-+
- if (f >= 0) {
- recv_uid_list(f, flist);
+- if (S_ISREG(file->mode) && file->length != st->st_size)
++ if (S_ISREG(file->mode) && file->length != sxp->st.st_size)
+ iflags |= ITEM_REPORT_SIZE;
+ if ((iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !keep_time
+ && (!(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 ((file->mode & CHMOD_BITS) != (st->st_mode & CHMOD_BITS))
++ if ((file->mode & CHMOD_BITS) != (sxp->st.st_mode & CHMOD_BITS))
+ iflags |= ITEM_REPORT_PERMS;
+- if (preserve_uid && am_root && file->uid != st->st_uid)
++ if (preserve_uid && am_root && file->uid != sxp->st.st_uid)
+ iflags |= ITEM_REPORT_OWNER;
+ if (preserve_gid && file->gid != GID_NONE
+- && st->st_gid != file->gid)
++ && sxp->st.st_gid != file->gid)
+ 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;
---- old/generator.c
-+++ new/generator.c
-@@ -755,6 +755,7 @@ static int try_dests_non(struct file_str
+@@ -605,7 +616,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,
+ int maybe_ATTRS_REPORT, enum logcode code)
+ {
+ int best_match = -1;
+@@ -614,7 +625,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:
+@@ -622,16 +633,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 && preserve_times
+- && cmp_time(stp->st_mtime, file->modtime))
++ && cmp_time(sxp->st.st_mtime, file->modtime))
+ continue;
+ best_match = j;
+ match_level = 3;
+@@ -646,14 +661,14 @@ 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;
+ }
+
+ if (match_level == 3 && !copy_dest) {
+ #ifdef SUPPORT_HARD_LINKS
+ if (link_dest) {
+- if (hard_link_one(file, ndx, fname, 0, stp,
++ if (hard_link_one(file, ndx, fname, 0, sxp,
+ cmpbuf, 1,
+ itemizing && verbose > 1,
+ code) < 0)
+@@ -665,8 +680,13 @@ static int try_dests_reg(struct file_str
+ }
+ } 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);
+ }
+@@ -682,8 +702,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)
+@@ -707,13 +732,18 @@ static int try_dests_non(struct file_str
+ enum logcode code)
+ {
+ char fnamebuf[MAXPATHLEN];
+- STRUCT_STAT st;
++ statx sx;
+ int i = 0;
+
+ do {
+ pathjoin(fnamebuf, MAXPATHLEN, basis_dir[i], fname);
+- if (link_stat(fnamebuf, &st, 0) < 0 || S_ISDIR(st.st_mode)
+- || !unchanged_attrs(file, &st))
++ if (link_stat(fnamebuf, &sx.st, 0) < 0 || S_ISDIR(sx.st.st_mode))
++ continue;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(fnamebuf, &sx);
++#endif
++ if (!unchanged_attrs(file, &sx))
+ continue;
+ if (S_ISLNK(file->mode)) {
+ #ifdef SUPPORT_LINKS
+@@ -726,10 +756,10 @@ static int try_dests_non(struct file_str
+ #endif
+ continue;
+ } else if (IS_SPECIAL(file->mode)) {
+- if (!IS_SPECIAL(st.st_mode) || st.st_rdev != file->u.rdev)
++ if (!IS_SPECIAL(sx.st.st_mode) || sx.st.st_rdev != file->u.rdev)
+ continue;
+ } else if (IS_DEVICE(file->mode)) {
+- if (!IS_DEVICE(st.st_mode) || st.st_rdev != file->u.rdev)
++ if (!IS_DEVICE(sx.st.st_mode) || sx.st.st_rdev != file->u.rdev)
+ continue;
+ } else {
+ rprintf(FERROR,
+@@ -760,7 +790,15 @@ static int try_dests_non(struct file_str
+ int changes = compare_dest ? 0 : ITEM_LOCAL_CHANGE
+ + (link_dest ? ITEM_XNAME_FOLLOWS : 0);
+ char *lp = link_dest ? "" : NULL;
+- itemize(file, ndx, 0, &st, changes, 0, lp);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(fname, &sx);
++#endif
++ itemize(file, ndx, 0, &sx, changes, 0, lp);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(&sx);
++#endif
+ }
+ if (verbose > 1 && maybe_ATTRS_REPORT) {
+ rprintf(FCLIENT, "%s is uptodate\n", fname);
+@@ -772,6 +810,7 @@ static int try_dests_non(struct file_str
}
static int phase = 0;
/* 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
-@@ -847,6 +848,10 @@ static void recv_generator(char *fname,
+@@ -793,7 +832,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;
+@@ -849,6 +889,9 @@ static void recv_generator(char *fname,
+ } else if (!dry_run)
+ return;
+ }
++#ifdef SUPPORT_ACLS
++ sx.acc_acl = sx.def_acl = NULL;
++#endif
+ if (dry_run > 1) {
+ statret = -1;
+ stat_errno = ENOENT;
+@@ -856,7 +899,7 @@ static void recv_generator(char *fname,
+ 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",
+@@ -868,6 +911,10 @@ static void recv_generator(char *fname,
}
if (fuzzy_basis)
- fuzzy_dirlist = need_dirlist;
+ need_fuzzy_dirlist = 1;
+#ifdef SUPPORT_ACLS
+ if (!preserve_perms)
+ dflt_perms = default_perms_for_dir(dn);
}
parent_dirname = dn;
-@@ -872,7 +877,8 @@ static void recv_generator(char *fname,
+@@ -876,7 +923,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;
+ }
+@@ -894,8 +941,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);
+- && 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,
++ && 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)) {
-@@ -906,6 +912,10 @@ static void recv_generator(char *fname,
- if (set_file_attrs(fname, file, statret ? NULL : &st, 0)
- && verbose && code && f_out != -1)
- rprintf(code, "%s/\n", fname);
+@@ -904,8 +952,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, del_opts) < 0)
++ if (statret == 0 && !S_ISDIR(sx.st.st_mode)) {
++ if (delete_item(fname, sx.st.st_mode, del_opts) < 0)
+ return;
+ statret = -1;
+ }
+@@ -920,7 +968,11 @@ static void recv_generator(char *fname,
+ sr = -1;
+ new_root_dir = 0;
+ }
+- itemize(file, ndx, sr, &st,
+#ifdef SUPPORT_ACLS
-+ if (f_out == -1)
-+ SET_ACL(fname, file);
++ if (preserve_acls && sr == 0)
++ get_acl(fname, &sx);
+#endif
++ itemize(file, ndx, sr, &sx,
+ sr ? ITEM_LOCAL_CHANGE : 0, 0, NULL);
+ }
+ if (statret != 0 && do_mkdir(fname,file->mode) < 0 && errno != EEXIST) {
+@@ -940,19 +992,19 @@ static void recv_generator(char *fname,
+ return;
+ }
+ }
+- if (set_file_attrs(fname, file, statret ? NULL : &st, 0)
++ if (set_file_attrs(fname, file, statret ? NULL : &sx, 0)
+ && verbose && code != FNONE && f_out != -1)
+ rprintf(code, "%s/\n", fname);
if (delete_during && f_out != -1 && !phase && dry_run < 2
&& (file->flags & FLAG_DEL_HERE))
- delete_in_dir(the_file_list, fname, file, &st);
-@@ -1343,6 +1353,8 @@ void generate_files(int f_out, struct fi
+- delete_in_dir(the_file_list, fname, file, &st);
+- return;
++ delete_in_dir(the_file_list, fname, file, &sx.st);
++ goto cleanup;
+ }
+
+ if (preserve_hard_links && file->link_u.links
+- && hard_link_check(file, ndx, fname, statret, &st,
++ && hard_link_check(file, ndx, fname, statret, &sx,
+ itemizing, code, HL_CHECK_MASTER))
+- return;
++ goto cleanup;
+
+ if (preserve_links && S_ISLNK(file->mode)) {
+ #ifdef SUPPORT_LINKS
+@@ -970,7 +1022,7 @@ static void recv_generator(char *fname,
+ char lnk[MAXPATHLEN];
+ int len;
+
+- if (!S_ISDIR(st.st_mode)
++ if (!S_ISDIR(sx.st.st_mode)
+ && (len = readlink(fname, lnk, MAXPATHLEN-1)) > 0) {
+ lnk[len] = 0;
+ /* A link already pointing to the
+@@ -978,10 +1030,10 @@ static void recv_generator(char *fname,
+ * required. */
+ if (strcmp(lnk, file->u.link) == 0) {
+ if (itemizing) {
+- itemize(file, ndx, 0, &st, 0,
++ itemize(file, ndx, 0, &sx, 0,
+ 0, NULL);
+ }
+- set_file_attrs(fname, file, &st,
++ set_file_attrs(fname, file, &sx,
+ maybe_ATTRS_REPORT);
+ if (preserve_hard_links
+ && file->link_u.links) {
+@@ -996,9 +1048,9 @@ static void recv_generator(char *fname,
+ }
+ /* Not the right symlink (or not a symlink), so
+ * delete it. */
+- if (delete_item(fname, st.st_mode, del_opts) < 0)
++ if (delete_item(fname, sx.st.st_mode, del_opts) < 0)
+ return;
+- if (!S_ISLNK(st.st_mode))
++ if (!S_ISLNK(sx.st.st_mode))
+ statret = -1;
+ } else if (basis_dir[0] != NULL) {
+ if (try_dests_non(file, fname, ndx, itemizing,
+@@ -1015,7 +1067,7 @@ static void recv_generator(char *fname,
+ }
+ }
+ if (preserve_hard_links && file->link_u.links
+- && hard_link_check(file, ndx, fname, -1, &st,
++ && hard_link_check(file, ndx, fname, -1, &sx,
+ itemizing, code, HL_SKIP))
+ return;
+ if (do_symlink(file->u.link,fname) != 0) {
+@@ -1024,7 +1076,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) {
+@@ -1059,18 +1111,22 @@ static void recv_generator(char *fname,
+ code = FNONE;
+ }
+ }
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && statret == 0)
++ get_acl(fname, &sx);
++#endif
+ if (statret != 0
+- || (st.st_mode & ~CHMOD_BITS) != (file->mode & ~CHMOD_BITS)
+- || st.st_rdev != file->u.rdev) {
++ || (sx.st.st_mode & ~CHMOD_BITS) != (file->mode & ~CHMOD_BITS)
++ || sx.st.st_rdev != file->u.rdev) {
+ if (statret == 0
+- && delete_item(fname, st.st_mode, del_opts) < 0)
+- return;
++ && delete_item(fname, sx.st.st_mode, del_opts) < 0)
++ goto cleanup;
+ if (preserve_hard_links && file->link_u.links
+- && hard_link_check(file, ndx, fname, -1, &st,
++ && hard_link_check(file, ndx, fname, -1, &sx,
+ itemizing, code, HL_SKIP))
+- return;
+- if ((IS_DEVICE(file->mode) && !IS_DEVICE(st.st_mode))
+- || (IS_SPECIAL(file->mode) && !IS_SPECIAL(st.st_mode)))
++ goto cleanup;
++ if ((IS_DEVICE(file->mode) && !IS_DEVICE(sx.st.st_mode))
++ || (IS_SPECIAL(file->mode) && !IS_SPECIAL(sx.st.st_mode)))
+ statret = -1;
+ if (verbose > 2) {
+ rprintf(FINFO,"mknod(%s,0%o,0x%x)\n",
+@@ -1083,7 +1139,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)
+@@ -1097,14 +1153,14 @@ static void recv_generator(char *fname,
+ }
+ } else {
+ if (itemizing)
+- itemize(file, ndx, statret, &st, 0, 0, NULL);
+- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT);
++ itemize(file, ndx, statret, &sx, 0, 0, NULL);
++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT);
+ if (preserve_hard_links && file->link_u.links)
+ hard_link_cluster(file, ndx, itemizing, code);
+ if (remove_source_files == 1)
+ goto return_with_success;
+ }
+- return;
++ goto cleanup;
+ }
+
+ if (!S_ISREG(file->mode)) {
+@@ -1138,7 +1194,7 @@ static void recv_generator(char *fname,
+ }
+
+ if (update_only && 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;
+@@ -1147,20 +1203,20 @@ static void recv_generator(char *fname,
+ fnamecmp = fname;
+ fnamecmp_type = FNAMECMP_FNAME;
+
+- if (statret == 0 && !S_ISREG(st.st_mode)) {
+- if (delete_item(fname, st.st_mode, del_opts) != 0)
++ if (statret == 0 && !S_ISREG(sx.st.st_mode)) {
++ if (delete_item(fname, sx.st.st_mode, del_opts) != 0)
+ return;
+ 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, maybe_ATTRS_REPORT, code);
+ if (j == -2) {
+ if (remove_source_files == 1)
+ goto return_with_success;
+- return;
++ goto cleanup;
+ }
+ if (j >= 0) {
+ fnamecmp = fnamecmpbuf;
+@@ -1170,7 +1226,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
+@@ -1189,7 +1245,7 @@ static void recv_generator(char *fname,
+ rprintf(FINFO, "fuzzy basis selected for %s: %s\n",
+ fname, fnamecmpbuf);
+ }
+- st.st_size = fuzzy_file->length;
++ sx.st.st_size = fuzzy_file->length;
+ statret = 0;
+ fnamecmp = fnamecmpbuf;
+ fnamecmp_type = FNAMECMP_FUZZY;
+@@ -1198,7 +1254,7 @@ static void recv_generator(char *fname,
+
+ if (statret != 0) {
+ if (preserve_hard_links && file->link_u.links
+- && hard_link_check(file, ndx, fname, statret, &st,
++ && hard_link_check(file, ndx, fname, statret, &sx,
+ itemizing, code, HL_SKIP))
+ return;
+ if (stat_errno == ENOENT)
+@@ -1208,39 +1264,52 @@ static void recv_generator(char *fname,
+ return;
+ }
+
+- if (append_mode && st.st_size > file->length)
++ if (append_mode && sx.st.st_size > file->length)
+ return;
+
+ 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, real_ret, &real_st,
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && real_ret == 0)
++ get_acl(fnamecmp, &real_sx);
++#endif
++ itemize(file, ndx, real_ret, &real_sx,
+ 0, 0, NULL);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls) {
++ if (fnamecmp_type == FNAMECMP_FNAME) {
++ sx.acc_acl = real_sx.acc_acl;
++ sx.def_acl = real_sx.def_acl;
++ } else
++ free_acl(&real_sx);
++ }
++#endif
+ }
+- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT);
++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT);
+ if (preserve_hard_links && file->link_u.links)
+ hard_link_cluster(file, ndx, itemizing, code);
+ if (remove_source_files != 1)
+- return;
++ goto cleanup;
+ return_with_success:
+ if (!dry_run) {
+ char numbuf[4];
+ SIVAL(numbuf, 0, ndx);
+ send_msg(MSG_SUCCESS, numbuf, 4);
+ }
+- return;
++ goto cleanup;
+ }
+
+ prepare_to_open:
+ if (partialptr) {
+- st = partial_st;
++ sx.st = partial_st;
+ fnamecmp = partialptr;
+ fnamecmp_type = FNAMECMP_PARTIAL_DIR;
+ statret = 0;
+@@ -1264,17 +1333,21 @@ static void recv_generator(char *fname,
+ pretend_missing:
+ /* pretend the file didn't exist */
+ if (preserve_hard_links && file->link_u.links
+- && hard_link_check(file, ndx, fname, statret, &st,
++ && hard_link_check(file, ndx, fname, statret, &sx,
+ itemizing, code, HL_SKIP))
+- return;
++ goto cleanup;
+ statret = real_ret = -1;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && ACL_READY(sx))
++ free_acl(&sx);
++#endif
+ goto notify_others;
+ }
+
+ if (inplace && make_backups && 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);
+@@ -1285,7 +1358,7 @@ static void recv_generator(char *fname,
+ full_fname(backupptr));
+ free(back_file);
+ close(fd);
+- return;
++ goto cleanup;
+ }
+ if ((f_copy = do_open(backupptr,
+ O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) {
+@@ -1293,14 +1366,14 @@ static void recv_generator(char *fname,
+ full_fname(backupptr));
+ free(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);
+ }
+
+ if (verbose > 2)
+@@ -1318,24 +1391,32 @@ 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)
++ free_acl(&real_sx);
++#endif
+ }
+
+ if (!do_xfers) {
+ if (preserve_hard_links && file->link_u.links)
+ hard_link_cluster(file, ndx, itemizing, code);
+- return;
++ goto cleanup;
+ }
+ if (read_batch)
+- return;
++ goto cleanup;
+
+ if (statret != 0 || whole_file) {
+ write_sum_head(f_out, NULL);
+- return;
++ goto cleanup;
+ }
+
+- 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);
+
+ if (f_copy >= 0) {
+ close(f_copy);
+@@ -1348,6 +1429,13 @@ static void recv_generator(char *fname,
+ }
+
+ close(fd);
++
++ cleanup:
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(&sx);
++#endif
++ return;
+ }
+
+ void generate_files(int f_out, struct file_list *flist, char *local_name)
+@@ -1407,6 +1495,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;
for (i = 0; i < flist->count; i++) {
struct file_struct *file = flist->files[i];
+--- old/hlink.c
++++ new/hlink.c
+@@ -26,6 +26,7 @@
+ extern int verbose;
+ extern int do_xfers;
+ extern int link_dest;
++extern int preserve_acls;
+ extern int make_backups;
+ extern int remove_source_files;
+ extern int stdout_format_has_i;
+@@ -147,15 +148,19 @@ void init_hard_links(void)
+
+ #ifdef SUPPORT_HARD_LINKS
+ static int maybe_hard_link(struct file_struct *file, int ndx,
+- char *fname, int statret, STRUCT_STAT *st,
++ char *fname, int statret, statx *sxp,
+ char *toname, STRUCT_STAT *to_st,
+ int itemizing, enum logcode code)
+ {
+ if (statret == 0) {
+- if (st->st_dev == to_st->st_dev
+- && st->st_ino == to_st->st_ino) {
++ if (sxp->st.st_dev == to_st->st_dev
++ && sxp->st.st_ino == to_st->st_ino) {
+ if (itemizing) {
+- itemize(file, ndx, statret, st,
++#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, "");
+ }
+@@ -170,13 +175,13 @@ static int maybe_hard_link(struct file_s
+ return -1;
+ }
+ }
+- return hard_link_one(file, ndx, fname, statret, st, toname,
++ return hard_link_one(file, ndx, fname, statret, sxp, toname,
+ 0, itemizing, code);
+ }
+ #endif
+
+ int hard_link_check(struct file_struct *file, int ndx, char *fname,
+- int statret, STRUCT_STAT *st, int itemizing,
++ int statret, statx *sxp, int itemizing,
+ enum logcode code, int skip)
+ {
+ #ifdef SUPPORT_HARD_LINKS
+@@ -217,7 +222,7 @@ int hard_link_check(struct file_struct *
+ || st2.st_ino != st3.st_ino)
+ continue;
+ statret = 1;
+- st = &st3;
++ sxp->st = st3;
+ if (verbose < 2 || !stdout_format_has_i) {
+ itemizing = 0;
+ code = FNONE;
+@@ -227,12 +232,16 @@ int hard_link_check(struct file_struct *
+ if (!unchanged_file(cmpbuf, file, &st3))
+ continue;
+ statret = 1;
+- st = &st3;
+- if (unchanged_attrs(file, &st3))
++ sxp->st = st3;
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ get_acl(cmpbuf, sxp);
++#endif
++ if (unchanged_attrs(file, sxp))
+ break;
+ } while (basis_dir[++j] != NULL);
+ }
+- maybe_hard_link(file, ndx, fname, statret, st,
++ maybe_hard_link(file, ndx, fname, statret, sxp,
+ toname, &st2, itemizing, code);
+ if (remove_source_files == 1 && do_xfers) {
+ char numbuf[4];
+@@ -250,7 +259,7 @@ int hard_link_check(struct file_struct *
+
+ #ifdef SUPPORT_HARD_LINKS
+ int hard_link_one(struct file_struct *file, int ndx, char *fname,
+- int statret, STRUCT_STAT *st, char *toname, int terse,
++ int statret, statx *sxp, char *toname, int terse,
+ int itemizing, enum logcode code)
+ {
+ if (do_link(toname, fname)) {
+@@ -266,7 +275,11 @@ int hard_link_one(struct file_struct *fi
+ }
+
+ if (itemizing) {
+- itemize(file, ndx, statret, st,
++#ifdef SUPPORT_ACLS
++ 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,
+ terse ? "" : toname);
+ }
+@@ -283,11 +296,12 @@ void hard_link_cluster(struct file_struc
+ #ifdef SUPPORT_HARD_LINKS
+ char hlink1[MAXPATHLEN];
+ char *hlink2;
+- STRUCT_STAT st1, st2;
++ statx sx;
++ STRUCT_STAT st;
+ int statret, ndx = master;
+
+ file->F_HLINDEX = FINISHED_LINK;
+- if (link_stat(f_name(file, hlink1), &st1, 0) < 0)
++ if (link_stat(f_name(file, hlink1), &st, 0) < 0)
+ return;
+ if (!(file->flags & FLAG_HLINK_TOL)) {
+ while (!(file->flags & FLAG_HLINK_EOL)) {
+@@ -301,9 +315,13 @@ void hard_link_cluster(struct file_struc
+ if (file->F_HLINDEX != SKIPPED_LINK)
+ continue;
+ hlink2 = f_name(file, NULL);
+- statret = link_stat(hlink2, &st2, 0);
+- maybe_hard_link(file, ndx, hlink2, statret, &st2,
+- hlink1, &st1, itemizing, code);
++ statret = link_stat(hlink2, &sx.st, 0);
++ maybe_hard_link(file, ndx, hlink2, statret, &sx,
++ hlink1, &st, itemizing, code);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ free_acl(&sx);
++#endif
+ if (remove_source_files == 1 && do_xfers) {
+ char numbuf[4];
+ SIVAL(numbuf, 0, 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
+@@ -606,8 +606,10 @@ static void log_formatted(enum logcode c
+ n[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
+ n[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
+ n[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
+- n[8] = '.';
+- n[9] = '\0';
++ n[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
++ n[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
++ n[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
++ n[11] = '\0';
+ if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
+ char ch = iflags & ITEM_IS_NEW ? '+' : '?';
--- old/options.c
+++ new/options.c
-@@ -44,6 +44,7 @@ int keep_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
+@@ -227,17 +233,16 @@ static void print_rsync_version(enum log
+ RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION);
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, "
+- rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, "
- "%shard links, %ssymlinks, batchfiles,\n",
-+ "%shard links, %sACLs, %ssymlinks, batchfiles,\n",
- (int) (sizeof (OFF_T) * 8),
+- (int) (sizeof (OFF_T) * 8),
- got_socketpair, hardlinks, links);
-+ got_socketpair, hardlinks, acls, links);
++ rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, %shard links, %ssymlinks,\n",
++ (int) (sizeof (OFF_T) * 8), got_socketpair, hardlinks, links);
++
++ rprintf(f, " batchfiles, %sinplace, %sIPv6, %sACLs,\n",
++ have_inplace, ipv6, acls);
/* Note that this field may not have type ino_t. It depends
* on the complicated interaction between largefile feature
-@@ -294,6 +300,9 @@ void usage(enum logcode F)
- rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
+ * macros. */
+- rprintf(f, " %sinplace, %sIPv6, "
+- "%d-bit system inums, %d-bit internal inums\n",
+- have_inplace, ipv6,
++ rprintf(f, " %d-bit system inums, %d-bit internal inums\n",
+ (int) (sizeof dumstat->st_ino * 8),
+ (int) (sizeof (int64) * 8));
+ #ifdef MAINTAINER_MODE
+@@ -284,7 +289,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");
+@@ -306,6 +311,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");
-@@ -409,6 +418,9 @@ static struct poptOption long_options[]
+ rprintf(F," --devices preserve device files (super-user only)\n");
+@@ -425,6 +433,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 },
-@@ -1063,6 +1075,23 @@ int parse_arguments(int *argc, const cha
+@@ -1089,6 +1100,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++;
++ 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. */
-@@ -1503,6 +1532,10 @@ void server_options(char **args,int *arg
+@@ -1530,6 +1559,10 @@ void server_options(char **args,int *arg
if (preserve_hard_links)
argstr[x++] = 'H';
if (preserve_gid)
--- old/receiver.c
+++ new/receiver.c
-@@ -349,6 +349,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;
+@@ -47,6 +47,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 *stdout_format;
+ extern char *tmpdir;
+@@ -350,6 +351,10 @@ int recv_files(int f_in, struct file_lis
+ 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
int i, recv_ok;
if (verbose > 2)
-@@ -546,7 +550,16 @@ int recv_files(int f_in, struct file_lis
+@@ -553,7 +558,16 @@ int recv_files(int f_in, struct file_lis
* mode based on the local permissions and some heuristics. */
if (!preserve_perms) {
int exists = fd1 != -1;
+ 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
-@@ -101,7 +101,8 @@ void free_sums(struct sum_struct *s)
+@@ -32,6 +32,7 @@
+
+ extern int verbose;
+ extern int dry_run;
++extern int preserve_acls;
+ extern int preserve_perms;
+ extern int preserve_executability;
+ extern int preserve_times;
+@@ -47,7 +48,6 @@ extern int preserve_gid;
+ extern int inplace;
+ extern int keep_dirlinks;
+ extern int make_backups;
+-extern mode_t orig_umask;
+ extern struct stats stats;
+ extern struct chmod_mode_struct *daemon_chmod_modes;
+
+@@ -100,7 +100,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 +117,7 @@ mode_t dest_mode(mode_t flist_mode, mode
- cur_mode |= (cur_mode & 0444) >> 2;
+@@ -117,56 +118,65 @@ mode_t dest_mode(mode_t flist_mode, mode
+ new_mode |= (new_mode & 0444) >> 2;
}
- } else
-- cur_mode = flist_mode & ACCESSPERMS & ~orig_umask;
-+ cur_mode = (flist_mode & ACCESSPERMS & dflt_perms) | S_IWUSR;
- 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);
-@@ -217,6 +218,14 @@ int set_file_attrs(char *fname, struct f
+ } 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 != file->uid;
++ change_uid = am_root && preserve_uid && sxp->st.st_uid != file->uid;
+ change_gid = preserve_gid && file->gid != GID_NONE
+- && st->st_gid != file->gid;
++ && sxp->st.st_gid != file->gid;
+ #if !defined HAVE_LCHOWN && !defined CHOWN_MODIFIES_SYMLINK
+- if (S_ISLNK(st->st_mode))
++ if (S_ISLNK(sxp->st.st_mode))
+ ;
+ else
#endif
+@@ -176,45 +186,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)file->uid);
++ (long)sxp->st.st_uid, (long)file->uid);
+ }
+ if (change_gid) {
+ rprintf(FINFO,
+ "set gid of %s from %ld to %ld\n",
+ fname,
+- (long)st->st_gid, (long)file->gid);
++ (long)sxp->st.st_gid, (long)file->gid);
+ }
+ }
+ if (do_lchown(fname,
+- change_uid ? file->uid : st->st_uid,
+- change_gid ? file->gid : st->st_gid) != 0) {
++ change_uid ? file->uid : sxp->st.st_uid,
++ change_gid ? file->gid : 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));
+ }
+ updated = 1;
+ }
-+ /* If this is a directory, SET_ACL() will be called on the cleanup
-+ * receive_generator() pass (if we called it here, we might clobber
-+ * writability on the directory). Everything else is OK to do now. */
-+ if (!S_ISDIR(st->st_mode)) {
-+ if (SET_ACL(fname, file) == 0)
-+ 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 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
+
- if (verbose > 1 && flags & ATTRS_REPORT) {
- enum logcode code = daemon_log_format_has_i || dry_run
- ? FCLIENT : FINFO;
+ #ifdef HAVE_CHMOD
+- if ((st->st_mode & CHMOD_BITS) != (new_mode & CHMOD_BITS)) {
++ if ((sxp->st.st_mode & CHMOD_BITS) != (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;
+@@ -227,6 +249,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
-@@ -657,6 +657,44 @@ struct chmod_mode_struct;
-
- #define UNUSED(x) x __attribute__((__unused__))
+@@ -492,6 +492,14 @@ struct idev {
+ #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
+
+ #define GID_NONE ((gid_t)-1)
+
+ #define HL_CHECK_MASTER 0
+@@ -653,6 +661,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"
+@@ -669,6 +688,16 @@ struct chmod_mode_struct;
+ #define UNUSED(x) x __attribute__((__unused__))
+ #define NORETURN __attribute__((__noreturn__))
+
++typedef struct {
++ STRUCT_STAT st;
+#ifdef SUPPORT_ACLS
-+#ifdef HAVE_SYS_ACL_H
-+#include <sys/acl.h>
++ struct rsync_acl *acc_acl; /* access ACL */
++ struct rsync_acl *def_acl; /* default ACL */
+#endif
-+#define MAKE_ACL(file, fname) make_acl(file, fname)
-+#define SEND_ACL(file, f) send_acl(file, f)
-+#define RECEIVE_ACL(file, f) receive_acl(file, f)
-+#define SORT_FILE_ACL_INDEX_LISTS() sort_file_acl_index_lists()
-+#define SET_ACL(fname, file) set_acl(fname, file)
-+#define NEXT_ACL_UID() next_acl_uid()
-+#define ACL_UID_MAP(uid) acl_uid_map(uid)
-+#define PUSH_KEEP_BACKUP_ACL(file, orig, dest) \
-+ push_keep_backup_acl(file, orig, dest)
-+#define CLEANUP_KEEP_BACKUP_ACL() cleanup_keep_backup_acl()
-+#define DUP_ACL(orig, dest, mode) dup_acl(orig, dest, mode)
-+#else /* SUPPORT_ACLS */
-+#define MAKE_ACL(file, fname) 1 /* checked return value */
-+#define SEND_ACL(file, f)
-+#define RECEIVE_ACL(file, f)
-+#define SORT_FILE_ACL_INDEX_LISTS()
-+#define SET_ACL(fname, file) 1 /* checked return value */
-+#define NEXT_ACL_UID()
-+#define ACL_UID_MAP(uid)
-+#define PUSH_KEEP_BACKUP_ACL(file, orig, dest)
-+#define CLEANUP_KEEP_BACKUP_ACL()
-+#define DUP_ACL(src, orig, mode) 1 /* checked return value */
-+#endif /* SUPPORT_ACLS */
-+#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
-@@ -319,6 +319,7 @@ to the detailed description below for a
- -K, --keep-dirlinks treat symlinked dir on receiver as dir
+@@ -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
-@@ -705,7 +706,9 @@ quote(itemize(
+ --devices preserve device files (super-user only)
+@@ -753,7 +754,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.
))
-@@ -736,9 +739,11 @@ The preservation of the destination's se
+@@ -784,9 +787,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
-@@ -756,6 +761,10 @@ quote(itemize(
+@@ -804,6 +809,15 @@ 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).
++
++Note also that an optimization of the ACL-sending protocol used by this
++version makes it incompatible with sending files to an older ACL-enabled
++rsync unless you double the bf(--acls) option (e.g. bf(-AA)). This
++doubling is not needed when pulling files from an older rsync.
+
dit(bf(--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
+@@ -1389,8 +1403,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.
+@@ -1439,7 +1453,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
+#ifndef _SMB_ACLS_H
+#define _SMB_ACLS_H
+
-+#if defined(HAVE_POSIX_ACLS)
++#if defined HAVE_POSIX_ACLS
+
+/* This is an identity mapping (just remove the SMB_). */
+
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_TRU64_ACLS)
++#elif defined HAVE_TRU64_ACLS
+
+/* This is for DEC/Compaq Tru64 UNIX */
+
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_UNIXWARE_ACLS) || defined(HAVE_SOLARIS_ACLS)
++#elif defined HAVE_UNIXWARE_ACLS || defined HAVE_SOLARIS_ACLS
+/*
+ * Donated by Michael Davidson <md@sco.COM> for UnixWare / OpenUNIX.
+ * Modified by Toomas Soome <tsoome@ut.ee> for Solaris.
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
-+#elif defined(HAVE_HPUX_ACLS)
++#ifdef __CYGWIN__
++#define SMB_ACL_LOSES_SPECIAL_MODE_BITS
++#endif
++
++#elif defined HAVE_HPUX_ACLS
+
+/*
+ * Based on the Solaris & UnixWare code.
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
-+#elif defined(HAVE_IRIX_ACLS)
++#elif defined HAVE_IRIX_ACLS
+
+#define SMB_ACL_TAG_T acl_tag_t
+#define SMB_ACL_TYPE_T acl_type_t
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_AIX_ACLS)
++#elif defined HAVE_AIX_ACLS
+
+/* Donated by Medha Date, mdate@austin.ibm.com, for IBM */
+
+
+#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/"
+ $RSYNC -rvv "$scratchdir/file" "$todir/to/anotherfile"
+ check_perms "$todir/to/anotherfile" $3 "Target $1"
+ # Make sure we obey default ACLs when not transferring a regular file
-+ $RSYNC -rvv "$scratchdir/dir" "$todir/to/anotherdir"
++ $RSYNC -rvv "$scratchdir/dir/" "$todir/to/anotherdir/"
+ check_perms "$todir/to/anotherdir" $4 "Target $1"
+}
+
+
+# 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' \
+--- old/testsuite/itemize.test
++++ new/testsuite/itemize.test
+@@ -29,15 +29,15 @@ ln "$fromdir/foo/config1" "$fromdir/foo/
+ $RSYNC -iplr "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+->f+++++++ bar/baz/rsync
+-cd+++++++ foo/
+->f+++++++ foo/config1
+->f+++++++ foo/config2
+->f+++++++ foo/extra
+-cL+++++++ 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
++cL+++++++++ foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 1 failed"
+
+@@ -49,10 +49,10 @@ chmod 601 "$fromdir/foo/config2"
+ $RSYNC -iplrH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<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"
+
+@@ -69,11 +69,11 @@ chmod 777 "$todir/bar/baz/rsync"
+ $RSYNC -iplrtc "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-.f..tp... bar/baz/rsync
+-.d..t.... foo/
+-.f..t.... foo/config1
+->fcstp... foo/config2
+-cL..T.... foo/sym -> ../bar/baz/rsync
++.f..tp..... bar/baz/rsync
++.d..t...... foo/
++.f..t...... foo/config1
++>fcstp..... foo/config2
++cL..T...... foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
+
+@@ -98,15 +98,15 @@ $RSYNC -ivvplrtH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ filter_outfile
+ cat <<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"
+
+@@ -125,8 +125,8 @@ touch "$todir/foo/config2"
+ $RSYNC -iplrtH "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<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"
+
+@@ -135,15 +135,15 @@ $RSYNC -ivvplrtH --copy-dest=../ld "$fro
+ | tee "$outfile"
+ filter_outfile
+ cat <<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..T.... 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..T...... foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 8 failed"
+
+@@ -151,11 +151,11 @@ rm -rf "$todir"
+ $RSYNC -iplrtH --copy-dest=../ld "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+-cd+++++++ foo/
+-hf foo/extra => foo/config1
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ bar/baz/
++cd+++++++++ foo/
++hf foo/extra => foo/config1
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 9 failed"
+
+@@ -182,15 +182,15 @@ $RSYNC -ivvplrtH --link-dest="$lddir" "$
+ | tee "$outfile"
+ filter_outfile
+ cat <<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
+-hL 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
++hL foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 11 failed"
+
+@@ -198,10 +198,10 @@ rm -rf "$todir"
+ $RSYNC -iplrtH --dry-run --link-dest=../ld "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+-cd+++++++ foo/
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ bar/baz/
++cd+++++++++ foo/
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 12 failed"
+
+@@ -209,10 +209,10 @@ rm -rf "$todir"
+ $RSYNC -iplrtH --link-dest=../ld "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+-cd+++++++ foo/
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ bar/baz/
++cd+++++++++ foo/
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 13 failed"
+
+@@ -240,14 +240,14 @@ filter_outfile
+ # TODO fix really-old problem when combining -H with --compare-dest:
+ # missing output for foo/extra hard-link (and it might not be updated)!
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+-.f bar/baz/rsync
+-cd+++++++ foo/
+-.f foo/config1
+-.f foo/config2
+-.L foo/sym -> ../bar/baz/rsync
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ bar/baz/
++.f bar/baz/rsync
++cd+++++++++ foo/
++.f foo/config1
++.f foo/config2
++.L foo/sym -> ../bar/baz/rsync
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 15 failed"
+
+@@ -255,10 +255,10 @@ rm -rf "$todir"
+ $RSYNC -iplrtH --compare-dest="$lddir" "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cd+++++++ ./
+-cd+++++++ bar/
+-cd+++++++ bar/baz/
+-cd+++++++ foo/
++cd+++++++++ ./
++cd+++++++++ bar/
++cd+++++++++ bar/baz/
++cd+++++++++ foo/
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 16 failed"
+
--- old/uidlist.c
+++ new/uidlist.c
-@@ -34,6 +34,7 @@
+@@ -35,6 +35,7 @@
extern int verbose;
extern int preserve_uid;
extern int preserve_gid;
extern int numeric_ids;
extern int am_root;
-@@ -274,7 +275,7 @@ void send_uid_list(int f)
+@@ -273,7 +274,7 @@ void send_uid_list(int f)
if (numeric_ids)
return;
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)
+@@ -290,7 +291,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
+@@ -311,7 +312,7 @@ void recv_uid_list(int f, struct file_li
int id, i;
char *name;
/* 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
+@@ -323,7 +324,7 @@ void recv_uid_list(int f, struct file_li
}
}
/* read the gid list */
while ((id = read_int(f)) != 0) {
int len = read_byte(f);
-@@ -336,6 +337,18 @@ void recv_uid_list(int f, struct file_li
+@@ -335,6 +336,16 @@ void recv_uid_list(int f, struct file_li
}
}
+#ifdef SUPPORT_ACLS
+ if (preserve_acls && !numeric_ids) {
-+ id_t id;
-+ /* The enumerations don't return 0 except to flag the last
-+ * entry, since uidlist doesn't munge 0 anyway. */
-+ while ((id = next_acl_uid(flist)) != 0)
-+ acl_uid_map(match_uid(id));
-+ while ((id = next_acl_gid(flist)) != 0)
-+ acl_gid_map(match_gid(id));
++ id_t *id;
++ while ((id = next_acl_uid(flist)) != NULL)
++ *id = match_uid(*id);
++ while ((id = next_acl_gid(flist)) != NULL)
++ *id = match_gid(*id);
+ }
-+#endif /* SUPPORT_ACLS */
++#endif
+
/* 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
+@@ -1468,3 +1468,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);
++}