./configure --enable-acl-support
make
-This code does not yet itemize changes in ACL information (see --itemize),
-and it has a bug where some user/group ACL changes might not be propagated
-from the sender to the receiver if the receiver already has a version of
-the file that does not need any other attribute updates.
+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,1242 @@
-+/* -*- c-file-style: "linux" -*-
-+ Copyright (C) Andrew Tridgell 1996
-+ Copyright (C) Paul Mackerras 1996
-+ Copyright (C) Matt McCutchen 2006
-+ Copyright (C) Wayne Davison 2006
-+
-+ 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,1099 @@
++/*
++ * 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"
+
+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;
+} id_access;
+
+typedef struct {
-+ size_t count;
-+ size_t malloced;
+ id_access *idas;
-+} ida_list;
++ int count;
++} ida_entries;
+
+#define NO_ENTRY ((uchar)0x80)
-+typedef struct {
-+ ida_list users;
-+ ida_list groups;
++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 other;
+} rsync_acl;
+
-+static const rsync_acl rsync_acl_initializer =
-+ { {0, 0, NULL}, {0, 0, NULL}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY};
++typedef struct {
++ rsync_acl racl;
++ SMB_ACL_T sacl;
++} acl_duo;
++
++static const rsync_acl empty_rsync_acl = {
++ {NULL, 0}, {NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY
++};
++
++static item_list access_acl_list = EMPTY_ITEM_LIST;
++static item_list default_acl_list = EMPTY_ITEM_LIST;
++
++/* === Calculations on ACL types === */
++
++static const char *str_acl_type(SMB_ACL_TYPE_T type)
++{
++ return type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS"
++ : type == SMB_ACL_TYPE_DEFAULT ? "SMB_ACL_TYPE_DEFAULT"
++ : "unknown SMB_ACL_TYPE_T";
++}
+
+#define OTHER_TYPE(t) (SMB_ACL_TYPE_ACCESS+SMB_ACL_TYPE_DEFAULT-(t))
+#define BUMP_TYPE(t) ((t = OTHER_TYPE(t)) == SMB_ACL_TYPE_DEFAULT)
+
-+/* a few useful calculations */
-+
-+static int rsync_acl_count_entries(const rsync_acl *racl) {
++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->other != NO_ENTRY);
+}
+
-+static int rsync_acl_get_perms(const rsync_acl *racl) {
-+ /* Note that (NO_ENTRY & 7) is 0. */
-+ return ((racl->user_obj & 7) << 6)
-+ + (((racl->mask != NO_ENTRY ? racl->mask : racl->group_obj) & 7) << 3)
-+ + (racl->other & 7);
++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;
+}
+
-+static void rsync_acl_strip_perms(rsync_acl *racl) {
++/* 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
++ else {
++ if (racl->group_obj == racl->mask)
++ racl->group_obj = NO_ENTRY;
+ racl->mask = NO_ENTRY;
++ }
+ racl->other = NO_ENTRY;
+}
+
-+static void expand_ida_list(ida_list *idal)
++/* Given an empty rsync_acl, fake up the permission bits. */
++static void rsync_acl_fake_perms(rsync_acl *racl, mode_t mode)
+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (idal->malloced <= idal->count) {
-+ id_access *new_ptr;
-+ size_t new_size = idal->malloced + 10;
-+ new_ptr = realloc_array(idal->idas, id_access, new_size);
-+ if (verbose >= 4) {
-+ rprintf(FINFO, "expand rsync_acl to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof idal->idas[0],
-+ idal->idas ? "" : " not");
-+ }
++ racl->user_obj = (mode >> 6) & 7;
++ racl->group_obj = (mode >> 3) & 7;
++ racl->other = mode & 7;
++}
+
-+ idal->idas = new_ptr;
-+ idal->malloced = new_size;
++/* === Rsync ACL functions === */
+
-+ if (!idal->idas)
-+ out_of_memory("expand_ida_list");
++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 void ida_list_free(ida_list *idal)
++static BOOL rsync_acl_equal(const rsync_acl *racl1, const rsync_acl *racl2)
+{
-+ free(idal->idas);
-+ idal->idas = NULL;
-+ idal->count = 0;
-+ idal->malloced = 0;
++ 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)
+{
-+ ida_list_free(&racl->users);
-+ ida_list_free(&racl->groups);
++ if (racl->users.idas)
++ free(racl->users.idas);
++ if (racl->groups.idas)
++ free(racl->groups.idas);
++ *racl = empty_rsync_acl;
++}
++
++void free_acl(statx *sxp)
++{
++ if (sxp->acc_acl) {
++ rsync_acl_free(sxp->acc_acl);
++ free(sxp->acc_acl);
++ sxp->acc_acl = NULL;
++ }
++ 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)
+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
+}
+
-+static void sort_ida_list(ida_list *idal)
++static void sort_ida_entries(ida_entries *idal)
+{
+ if (!idal->count)
+ return;
-+ qsort((void **)idal->idas, idal->count, sizeof idal->idas[0],
-+ &id_access_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;
+ const char *errfun;
+ int rc;
+
-+ *racl = rsync_acl_initializer;
+ errfun = "sys_acl_get_entry";
+ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
+ rc == 1;
+ uchar access;
+ void *qualifier;
+ id_access *ida;
-+ ida_list *idal;
+ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
+ errfun = "sys_acl_get_tag_type";
+ break;
+ 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 given idal */
++ /* continue == done with entry; break == store in temporary ida list */
+ switch (tag_type) {
+ case SMB_ACL_USER_OBJ:
+ if (racl->user_obj == NO_ENTRY)
+ rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n");
+ continue;
+ case SMB_ACL_USER:
-+ idal = &racl->users;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ if (racl->group_obj == NO_ENTRY)
+ rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n");
+ continue;
+ case SMB_ACL_GROUP:
-+ idal = &racl->groups;
+ break;
+ case SMB_ACL_MASK:
+ if (racl->mask == NO_ENTRY)
+ rc = EINVAL;
+ break;
+ }
-+ expand_ida_list(idal);
-+ ida = &idal->idas[idal->count++];
++ 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;
+ }
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
+
-+ sort_ida_list(&racl->users);
-+ sort_ida_list(&racl->groups);
-+
-+ return True;
-+}
-+
-+static BOOL ida_lists_equal(const ida_list *ial1, const ida_list *ial2)
-+{
-+ id_access *ida1, *ida2;
-+ size_t 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_acls_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_lists_equal(&racl1->users, &racl2->users)
-+ && ida_lists_equal(&racl1->groups, &racl2->groups));
-+}
-+
-+static BOOL rsync_acl_extended_parts_equal(const rsync_acl *racl1, const rsync_acl *racl2)
-+{
-+ /* We ignore any differences that chmod() can take care of. */
-+ if ((racl1->mask ^ racl2->mask) & NO_ENTRY)
-+ return False;
-+ if (racl1->mask != NO_ENTRY && racl1->group_obj != racl2->group_obj)
-+ return False;
-+ return ida_lists_equal(&racl1->users, &racl2->users)
-+ && ida_lists_equal(&racl1->groups, &racl2->groups);
-+}
-+
-+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ rsync_acl *racls;
-+} rsync_acl_list;
-+
-+static rsync_acl_list _rsync_acl_lists[] = {
-+ { 0, 0, NULL }, /* SMB_ACL_TYPE_ACCESS */
-+ { 0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */
-+};
-+
-+static inline rsync_acl_list *rsync_acl_lists(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ? &_rsync_acl_lists[0]
-+ : &_rsync_acl_lists[1];
-+}
-+
-+static void expand_rsync_acl_list(rsync_acl_list *racl_list)
-+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (racl_list->malloced <= racl_list->count) {
-+ rsync_acl *new_ptr;
-+ size_t new_size;
-+ if (racl_list->malloced < 1000)
-+ new_size = racl_list->malloced + 1000;
-+ else
-+ new_size = racl_list->malloced * 2;
-+ new_ptr = realloc_array(racl_list->racls, rsync_acl, new_size);
-+ if (verbose >= 3) {
-+ rprintf(FINFO, "expand_rsync_acl_list to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof racl_list->racls[0],
-+ racl_list->racls ? "" : " not");
-+ }
-+
-+ racl_list->racls = new_ptr;
-+ racl_list->malloced = new_size;
-+
-+ if (!racl_list->racls)
-+ out_of_memory("expand_rsync_acl_list");
-+ }
-+}
-+
-+static int find_matching_rsync_acl(SMB_ACL_TYPE_T type,
-+ const rsync_acl_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;
-+ 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))
-+ 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_ida_list(int f, const ida_list *idal, uchar 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
-+ add_gid(ida->id);
-+ }
-+}
++ sort_ida_entries(&racl->users);
++ sort_ida_entries(&racl->groups);
+
-+static void send_rsync_acl(int f, const rsync_acl *racl)
-+{
-+ size_t count = rsync_acl_count_entries(racl);
-+ write_int(f, count);
-+ if (racl->user_obj != NO_ENTRY) {
-+ write_byte(f, 'u');
-+ write_byte(f, racl->user_obj);
-+ }
-+ send_ida_list(f, &racl->users, 'U');
-+ if (racl->group_obj != NO_ENTRY) {
-+ write_byte(f, 'g');
-+ write_byte(f, racl->group_obj);
-+ }
-+ send_ida_list(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);
-+ }
-+}
-+
-+static rsync_acl _curr_rsync_acls[2];
-+
-+static const char *str_acl_type(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS" :
-+ type == SMB_ACL_TYPE_DEFAULT ? "SMB_ACL_TYPE_DEFAULT" :
-+ "unknown SMB_ACL_TYPE_T";
-+}
-+
-+/* Generate the ACL(s) for this flist entry;
-+ * ACL(s) are either sent or cleaned-up by send_acl() below. */
-+int make_acl(const struct file_struct *file, const char *fname)
-+{
-+ SMB_ACL_TYPE_T type;
-+ rsync_acl *curr_racl;
-+
-+ if (S_ISLNK(file->mode))
-+ return 1;
-+
-+ curr_racl = &_curr_rsync_acls[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ SMB_ACL_T sacl;
-+ BOOL ok;
-+ if ((sacl = sys_acl_get_file(fname, type)) != 0) {
-+ ok = unpack_smb_acl(curr_racl, sacl);
-+ sys_acl_free_acl(sacl);
-+ if (!ok)
-+ return -1;
-+ /* Strip access ACLs of permission-bit entries. */
-+ if (type == SMB_ACL_TYPE_ACCESS)
-+ rsync_acl_strip_perms(curr_racl);
-+ } else if (errno == ENOTSUP) {
-+ /* ACLs are not supported. Leave list empty. */
-+ *curr_racl = rsync_acl_initializer;
-+ } else {
-+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(type), strerror(errno));
-+ return -1;
-+ }
-+ curr_racl++;
-+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
-+
-+ return 0;
-+}
-+
-+/* Send the make_acl()-generated ACLs for this flist entry,
-+ * or clean up after an flist entry that's not being sent (f == -1). */
-+void send_acl(const struct file_struct *file, int f)
-+{
-+ SMB_ACL_TYPE_T type;
-+ rsync_acl *curr_racl;
-+
-+ if (S_ISLNK(file->mode))
-+ return;
-+
-+ curr_racl = &_curr_rsync_acls[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ int index;
-+ rsync_acl_list *racl_list = rsync_acl_lists(type);
-+ if (f == -1) {
-+ rsync_acl_free(curr_racl);
-+ continue;
-+ }
-+ if ((index = find_matching_rsync_acl(type, racl_list, curr_racl))
-+ != -1) {
-+ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'a' : 'd');
-+ write_int(f, index);
-+ rsync_acl_free(curr_racl);
-+ } else {
-+ write_byte(f, type == SMB_ACL_TYPE_ACCESS ? 'A' : 'D');
-+ send_rsync_acl(f, curr_racl);
-+ expand_rsync_acl_list(racl_list);
-+ racl_list->racls[racl_list->count++] = *curr_racl;
-+ }
-+ curr_racl++;
-+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
-+}
-+
-+/* The below stuff is only used by the receiver: */
-+
-+/* structure to hold index to rsync_acl_list member corresponding to
-+ * flist->files[i] */
-+
-+typedef struct {
-+ const struct file_struct *file;
-+ int aclidx;
-+} file_acl_index;
-+
-+typedef struct {
-+ size_t count;
-+ size_t malloced;
-+ file_acl_index *fileaclidxs;
-+} file_acl_index_list;
-+
-+static file_acl_index_list _file_acl_index_lists[] = {
-+ {0, 0, NULL },/* SMB_ACL_TYPE_ACCESS */
-+ {0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */
-+};
-+
-+static inline file_acl_index_list *file_acl_index_lists(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ?
-+ &_file_acl_index_lists[0] : &_file_acl_index_lists[1];
-+}
-+
-+static void expand_file_acl_index_list(file_acl_index_list *fileaclidx_list)
-+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (fileaclidx_list->malloced <= fileaclidx_list->count) {
-+ file_acl_index *new_ptr;
-+ size_t new_size;
-+ if (fileaclidx_list->malloced < 1000)
-+ new_size = fileaclidx_list->malloced + 1000;
-+ 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");
++#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;
+ }
-+}
-+
-+/* 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 */
-+};
++#endif
+
-+static inline smb_acl_list *smb_acl_lists(SMB_ACL_TYPE_T type)
-+{
-+ return type == SMB_ACL_TYPE_ACCESS ? &_smb_acl_lists[0] :
-+ &_smb_acl_lists[1];
++ return True;
+}
+
-+static void expand_smb_acl_list(smb_acl_list *sacl_list)
-+{
-+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (sacl_list->malloced <= sacl_list->count) {
-+ SMB_ACL_T *new_ptr;
-+ size_t new_size;
-+ if (sacl_list->malloced < 1000)
-+ new_size = sacl_list->malloced + 1000;
-+ else
-+ new_size = sacl_list->malloced * 2;
-+ new_ptr = realloc_array(sacl_list->sacls, SMB_ACL_T, new_size);
-+ if (verbose >= 3) {
-+ rprintf(FINFO, "expand_smb_acl_list to %.0f bytes, did%s move\n",
-+ (double) new_size * sizeof sacl_list->sacls[0],
-+ sacl_list->sacls ? "" : " not");
-+ }
-+
-+ sacl_list->sacls = new_ptr;
-+ sacl_list->malloced = new_size;
-+
-+ if (!sacl_list->sacls)
-+ out_of_memory("expand_smb_acl_list");
-+ }
-+}
++/* Synactic sugar for system calls */
+
+#define CALL_OR_ERROR(func,args,str) \
+ do { \
+#define COE(func,args) CALL_OR_ERROR(func,args,#func)
+#define COE2(func,args) CALL_OR_ERROR(func,args,NULL)
+
++/* Store the permissions in the system ACL entry. */
+static int store_access_in_entry(uchar access, SMB_ACL_ENTRY_T entry)
+{
+ const char *errfun = NULL;
+ return 0;
+
+ error_exit:
-+ rprintf(FERROR, "store_access_in_entry %s(): %s\n", errfun,
-+ strerror(errno));
++ rsyserr(FERROR, errno, "store_access_in_entry %s()", errfun);
+ return -1;
+}
+
-+/* build an SMB_ACL_T corresponding to an rsync_acl */
++/* Pack rsync ACL -> system ACL verbatim. Return whether we succeeded. */
+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
+{
++#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(rsync_acl_count_entries(racl)))) {
-+ rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n",
-+ strerror(errno));
++ if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) {
++ rsyserr(FERROR, errno, "pack_smb_acl: sys_acl_init()");
+ return False;
+ }
+
+ COE( sys_acl_create_entry,(smb_acl, &entry) );
+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) );
-+ COE2( store_access_in_entry,(racl->user_obj, entry) );
++ COE2( store_access_in_entry,(racl->user_obj & 7, entry) );
+
-+ for (ida = racl->users.idas, count = racl->users.count;
-+ count--; ida++) {
++ 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) );
+
+ 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, entry) );
++ COE2( store_access_in_entry,(racl->group_obj & 7, entry) );
+
-+ for (ida = racl->groups.idas, count = racl->groups.count;
-+ count--; ida++) {
++ for (ida = racl->groups.idas, count = racl->groups.count; count--; ida++) {
+ COE( sys_acl_create_entry,(smb_acl, &entry) );
+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP) );
+ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) );
+ COE2( store_access_in_entry,(ida->access, entry) );
+ }
++
++#ifdef ACLS_NEED_MASK
++ mask_bits = racl->mask == NO_ENTRY ? racl->group_obj & 7 : racl->mask;
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_MASK) );
++ COE2( store_access_in_entry,(mask_bits, entry) );
++#else
+ if (racl->mask != NO_ENTRY) {
+ COE( sys_acl_create_entry,(smb_acl, &entry) );
+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_MASK) );
+ COE2( store_access_in_entry,(racl->mask, entry) );
+ }
++#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, entry) );
++ COE2( store_access_in_entry,(racl->other & 7, entry) );
+
+#ifdef DEBUG
+ if (sys_acl_valid(*smb_acl) < 0)
-+ rprintf(FINFO, "pack_smb_acl: warning: system says the ACL I packed is invalid\n");
++ rprintf(FERROR, "pack_smb_acl: warning: system says the ACL I packed is invalid\n");
+#endif
+
+ return True;
+
+ error_exit:
+ if (errfun) {
-+ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
-+ strerror(errno));
++ rsyserr(FERROR, errno, "pack_smb_acl %s()", errfun);
+ }
+ sys_acl_free_acl(*smb_acl);
+ return False;
+}
+
-+static mode_t change_sacl_perms(SMB_ACL_T sacl, uchar mask, mode_t old_mode, mode_t mode)
++static int find_matching_rsync_acl(SMB_ACL_TYPE_T type,
++ const item_list *racl_list,
++ const rsync_acl *racl)
+{
-+ SMB_ACL_ENTRY_T entry;
-+ int group_id = mask != NO_ENTRY ? SMB_ACL_MASK : SMB_ACL_GROUP_OBJ;
-+ const char *errfun;
-+ int rc;
++ static int access_match = -1, default_match = -1;
++ int *match = type == SMB_ACL_TYPE_ACCESS ? &access_match : &default_match;
++ size_t count = racl_list->count;
+
-+ if (S_ISDIR(mode)) {
-+ /* If the sticky bit is going on, it's not safe to allow all
-+ * the new ACLs to go into effect before it gets set. */
-+ 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 ACLs 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;
++ /* 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--) {
++ 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;
++}
++
++/* Return the Access Control List for the given filename. */
++int get_acl(const char *fname, statx *sxp)
++{
++ SMB_ACL_TYPE_T type;
++
++ if (S_ISLNK(sxp->st.st_mode))
++ return 0;
++
++ 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;
++
++ if (sacl) {
++ BOOL ok = unpack_smb_acl(racl, sacl);
++
++ 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;
++ }
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
++
++ return 0;
++}
++
++/* === Send functions === */
++
++/* 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. */
++
++/* 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
++ add_gid(ida->id);
++ }
++}
++
++/* Send an rsync ACL over the file descriptor. */
++static void send_rsync_acl(int f, const rsync_acl *racl)
++{
++ 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);
+ }
++}
+
-+ 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;
++/* 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)
++{
++ SMB_ACL_TYPE_T type;
++ rsync_acl *racl, *new_racl;
++ item_list *racl_list;
++ int ndx;
++
++ 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;
+ }
-+ if (tag_type == SMB_ACL_USER_OBJ)
-+ COE2( store_access_in_entry,((mode >> 6) & 7, entry) );
-+ else if (tag_type == group_id)
-+ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
-+ else if (tag_type == SMB_ACL_OTHER)
-+ COE2( store_access_in_entry,(mode & 7, entry) );
-+ }
-+ if (rc) {
-+ error_exit:
-+ if (errfun) {
-+ rprintf(FERROR, "change_sacl_perms: %s(): %s\n",
-+ errfun, strerror(errno));
++
++ /* 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;
+ }
-+ return ~0u;
-+ }
++ racl = sxp->def_acl;
++ racl_list = &default_acl_list;
++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode));
+
-+ /* Return the mode of the file on disk, as we will set them. */
-+ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
++ free_acl(sxp);
+}
+
-+static void receive_rsync_acl(rsync_acl *racl, int f)
++/* === Receive functions === */
++
++static void receive_rsync_acl(rsync_acl *racl, int f, SMB_ACL_TYPE_T type)
+{
++ 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;
-+ ida_list *idal = NULL;
+ id_access *ida;
+ size_t count;
+
-+ *racl = rsync_acl_initializer;
-+
+ if (!(count = read_int(f)))
+ return;
+
+ while (count--) {
-+ uchar tag = read_byte(f);
++ char tag = read_byte(f);
+ uchar access = read_byte(f);
+ if (access & ~ (4 | 2 | 1)) {
+ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
+ racl->user_obj = access;
+ continue;
+ case 'U':
-+ idal = &racl->users;
++ tag_type = SMB_ACL_USER;
+ break;
+ case 'g':
+ if (racl->group_obj != NO_ENTRY) {
+ racl->group_obj = access;
+ continue;
+ case 'G':
-+ idal = &racl->groups;
++ tag_type = SMB_ACL_GROUP;
+ break;
+ case 'm':
+ if (racl->mask != NO_ENTRY) {
+ tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
-+ expand_ida_list(idal);
-+ ida = &idal->idas[idal->count++];
++ 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;
+ }
++ if (prior_list_type)
++ save_idas(&temp_ida_list, racl, prior_list_type);
++
++ if (type == SMB_ACL_TYPE_DEFAULT) {
++ /* Ensure that these are never unset. */
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = 7;
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = 0;
++ if (racl->other == NO_ENTRY)
++ racl->other = 0;
++ }
+
-+ /* 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;
-+#ifndef ACLS_NEED_MASK
+ if (!racl->users.count && !racl->groups.count) {
-+ /* If we, a system without ACLS_NEED_MASK, received a
-+ * superfluous mask, throw it away. */
++ /* If we received a superfluous mask, throw it away. */
+ if (racl->mask != NO_ENTRY) {
-+ /* mask off group perms with it first */
-+ racl->group_obj &= racl->mask;
++ /* Mask off the group perms with it first. */
++ racl->group_obj &= racl->mask | NO_ENTRY;
+ racl->mask = NO_ENTRY;
+ }
-+ } else
-+#endif
-+ if (racl->mask == NO_ENTRY)
-+ racl->mask = computed_mask_bits | racl->group_obj;
++ } else if (racl->mask == NO_ENTRY) /* Must be non-empty with lists. */
++ racl->mask = computed_mask_bits | (racl->group_obj & 7);
+}
+
-+/* receive and build the rsync_acl_lists */
++/* Receive the ACL info the sender has included for this file-list entry. */
+void receive_acl(struct file_struct *file, int f)
+{
+ SMB_ACL_TYPE_T type;
-+ char *fname;
++ item_list *racl_list;
++ char *ndx_ptr;
+
+ if (S_ISLNK(file->mode))
+ return;
+
-+ fname = f_name(file, NULL);
+ type = SMB_ACL_TYPE_ACCESS;
++ racl_list = &access_acl_list;
++ ndx_ptr = (char*)file + file_struct_len;
+ do {
-+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(type);
-+ uchar tag;
-+ expand_file_acl_index_list(fileaclidx_list);
++ char tag = read_byte(f);
++ int ndx;
+
-+ tag = read_byte(f);
+ if (tag == 'A' || tag == 'a') {
+ if (type != SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: duplicate access ACL\n",
-+ fname);
++ f_name(file, NULL));
+ exit_cleanup(RERR_STREAMIO);
+ }
+ } else if (tag == 'D' || tag == 'd') {
+ if (type == SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: expecting access ACL; got default\n",
-+ fname);
++ f_name(file, NULL));
+ exit_cleanup(RERR_STREAMIO);
+ }
+ } else {
+ rprintf(FERROR, "receive_acl %s: unknown ACL type tag: %c\n",
-+ fname, tag);
++ f_name(file, NULL), tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
+ if (tag == 'A' || tag == 'D') {
-+ rsync_acl racl;
-+ rsync_acl_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)
-+{
-+ const file_acl_index *fileaclidx1 = (const file_acl_index *)f1;
-+ const file_acl_index *fileaclidx2 = (const file_acl_index *)f2;
-+ return fileaclidx1->file == fileaclidx2->file ? 0 :
-+ fileaclidx1->file < fileaclidx2->file ? -1 : 1;
-+}
-+
-+void sort_file_acl_index_lists()
++/* Turn the ACL data in statx into cached ACL data, setting the index
++ * values in the file struct. */
++void cache_acl(struct file_struct *file, statx *sxp)
+{
+ SMB_ACL_TYPE_T type;
++ rsync_acl *racl;
++ item_list *racl_list;
++ char *ndx_ptr;
++ int ndx;
+
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(type);
-+ if (!fileaclidx_list->count)
-+ continue;
-+ qsort(fileaclidx_list->fileaclidxs, fileaclidx_list->count,
-+ sizeof fileaclidx_list->fileaclidxs[0],
-+ &file_acl_index_list_sorter);
-+ } while (BUMP_TYPE(type));
-+}
-+
-+static int find_file_acl_index(const file_acl_index_list *fileaclidx_list,
-+ const struct file_struct *file) {
-+ int low = 0, high = fileaclidx_list->count;
-+ const struct file_struct *file_mid;
-+ if (!high--)
-+ return -1;
-+ do {
-+ int mid = (high + low) / 2;
-+ file_mid = fileaclidx_list->fileaclidxs[mid].file;
-+ if (file_mid == file)
-+ return fileaclidx_list->fileaclidxs[mid].aclidx;
-+ if (file_mid > file)
-+ high = mid - 1;
-+ else
-+ low = mid + 1;
-+ } while (low < high);
-+ if (low == high) {
-+ file_mid = fileaclidx_list->fileaclidxs[low].file;
-+ if (file_mid == file)
-+ return fileaclidx_list->fileaclidxs[low].aclidx;
-+ }
-+ rprintf(FERROR,
-+ "find_file_acl_index: can't find entry for file in list\n");
-+ exit_cleanup(RERR_STREAMIO);
-+ return -1;
-+}
-+
-+/* for duplicating ACLs on backups when using backup_dir */
-+int dup_acl(const char *orig, const char *bak, mode_t mode)
-+{
-+ SMB_ACL_TYPE_T type;
-+ int ret = 0;
++ if (S_ISLNK(file->mode))
++ return;
+
+ type = SMB_ACL_TYPE_ACCESS;
++ racl = sxp->acc_acl;
++ racl_list = &access_acl_list;
++ ndx_ptr = (char*)file + file_struct_len;
+ do {
-+ SMB_ACL_T sacl_orig, sacl_bak;
-+ rsync_acl racl_orig, racl_bak;
-+ if (!(sacl_orig = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ orig, str_acl_type(type), strerror(errno));
-+ ret = -1;
-+ continue;
-+ }
-+ if (!(sacl_bak = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s. ignoring\n",
-+ bak, str_acl_type(type), strerror(errno));
-+ ret = -1;
-+ /* try to forge on through */
-+ }
-+ if (!unpack_smb_acl(&racl_orig, sacl_orig)) {
-+ ret = -1;
-+ goto out_with_sacls;
-+ }
-+ if (sacl_bak) {
-+ if (!unpack_smb_acl(&racl_bak, sacl_bak)) {
-+ ret = -1;
-+ goto out_with_one_racl;
-+ }
-+ if (rsync_acls_equal(&racl_orig, &racl_bak))
-+ goto out_with_all;
-+ } else {
-+ ; /* presume they're unequal */
-+ }
-+ if (type == SMB_ACL_TYPE_DEFAULT
-+ && rsync_acl_count_entries(&racl_orig) == 0) {
-+ if (sys_acl_delete_def_file(bak) < 0) {
-+ rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n",
-+ bak, strerror(errno));
-+ ret = -1;
-+ }
-+ } else if (sys_acl_set_file(bak, type, sacl_bak) < 0) {
-+ rprintf(FERROR, "dup_acl: sys_acl_set_file(%s, %s): %s\n",
-+ bak, str_acl_type(type), strerror(errno));
-+ ret = -1;
++ 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;
+ }
-+ out_with_all:
-+ if (sacl_bak)
-+ rsync_acl_free(&racl_bak);
-+ out_with_one_racl:
-+ rsync_acl_free(&racl_orig);
-+ out_with_sacls:
-+ if (sacl_bak)
-+ sys_acl_free_acl(sacl_bak);
-+ /* out_with_one_sacl: */
-+ if (sacl_orig)
-+ sys_acl_free_acl(sacl_orig);
-+ } while (BUMP_TYPE(type) && S_ISDIR(mode));
++ 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));
+
-+ return ret;
++ free_acl(sxp);
+}
+
-+/* Stuff for redirecting calls to set_acl() from set_file_attrs()
-+ * for keep_backup(). */
-+static const struct file_struct *backup_orig_file = NULL;
-+static const char null_string[] = "";
-+static const char *backup_orig_fname = null_string;
-+static const char *backup_dest_fname = null_string;
-+static SMB_ACL_T _backup_sacl[] = { NULL, NULL };
-+
-+void push_keep_backup_acl(const struct file_struct *file,
-+ const char *orig, const char *dest)
++static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode)
+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
++ SMB_ACL_ENTRY_T entry;
++ const char *errfun;
++ int rc;
+
-+ backup_orig_file = file;
-+ backup_orig_fname = orig;
-+ backup_dest_fname = dest;
++ if (S_ISDIR(mode)) {
++ /* If the sticky bit is going on, it's not safe to allow all
++ * the new ACL to go into effect before it gets set. */
++#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
++ if (mode & S_ISVTX)
++ mode &= ~0077;
++#else
++ if (mode & S_ISVTX && !(old_mode & S_ISVTX))
++ mode &= ~0077;
++ } else {
++ /* If setuid or setgid is going off, it's not safe to allow all
++ * the new ACL to go into effect before they get cleared. */
++ if ((old_mode & S_ISUID && !(mode & S_ISUID))
++ || (old_mode & S_ISGID && !(mode & S_ISGID)))
++ mode &= ~0077;
++#endif
++ }
+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode)) {
-+ *sacl = NULL;
++ errfun = "sys_acl_get_entry";
++ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
++ rc == 1;
++ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
++ errfun = "sys_acl_get_tag_type";
+ break;
+ }
-+ if (!(*sacl = sys_acl_get_file(orig, type))) {
-+ rprintf(FERROR,
-+ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ orig, str_acl_type(type),
-+ strerror(errno));
++ switch (tag_type) {
++ case SMB_ACL_USER_OBJ:
++ COE2( store_access_in_entry,((mode >> 6) & 7, entry) );
++ break;
++ case SMB_ACL_GROUP_OBJ:
++ /* group is only empty when identical to group perms. */
++ if (racl->group_obj != NO_ENTRY)
++ break;
++ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
++ break;
++ case SMB_ACL_MASK:
++#ifndef ACLS_NEED_MASK
++ /* mask is only empty when we don't need it. */
++ if (racl->mask == NO_ENTRY)
++ break;
++#endif
++ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
++ break;
++ case SMB_ACL_OTHER:
++ COE2( store_access_in_entry,(mode & 7, entry) );
++ break;
+ }
-+ } while (BUMP_TYPE(type));
-+}
-+
-+static int set_keep_backup_acl()
-+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
-+ int ret = 0;
-+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (*sacl
-+ && sys_acl_set_file(backup_dest_fname, type, *sacl) < 0) {
-+ rprintf(FERROR,
-+ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ backup_dest_fname,
-+ str_acl_type(type),
-+ strerror(errno));
-+ ret = -1;
++ }
++ if (rc) {
++ error_exit:
++ if (errfun) {
++ rsyserr(FERROR, errno, "change_sacl_perms: %s()",
++ errfun);
+ }
-+ } while (BUMP_TYPE(type));
-+
-+ return ret;
-+}
-+
-+void cleanup_keep_backup_acl()
-+{
-+ SMB_ACL_TYPE_T type;
-+ SMB_ACL_T *sacl;
++ return (mode_t)~0;
++ }
+
-+ backup_orig_file = NULL;
-+ backup_orig_fname = null_string;
-+ backup_dest_fname = null_string;
++#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS
++ /* Ensure that chmod() will be called to restore any lost setid bits. */
++ if (old_mode & (S_ISUID | S_ISGID | S_ISVTX)
++ && (old_mode & CHMOD_BITS) == (mode & CHMOD_BITS))
++ old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX);
++#endif
+
-+ sacl = &_backup_sacl[0];
-+ type = SMB_ACL_TYPE_ACCESS;
-+ do {
-+ if (*sacl) {
-+ sys_acl_free_acl(*sacl);
-+ *sacl = NULL;
-+ }
-+ } while (BUMP_TYPE(type));
++ /* Return the mode of the file on disk, as we will set them. */
++ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
+}
+
-+/* set ACL on rsync-ed or keep_backup-ed file
++/* Set ACL on indicated filename.
+ *
-+ * This sets extended access ACL entries and default ACLs. If convenient,
-+ * it sets permission bits along with the access ACLs and signals having
-+ * done so by modifying p_mode, which should point into the stat buffer.
++ * 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
-+ * Pass NULL for p_mode to get the return code without changing anything. */
-+int set_acl(const char *fname, const struct file_struct *file, mode_t *p_mode)
++ * Returns 1 for unchanged, 0 for changed, -1 for failed. Call this
++ * with fname set to NULL to just check if the ACL is unchanged. */
++int set_acl(const char *fname, const struct file_struct *file, statx *sxp)
+{
+ int unchanged = 1;
+ SMB_ACL_TYPE_T type;
++ char *ndx_ptr;
++
++ if (!dry_run && (read_only || list_only)) {
++ errno = EROFS;
++ return -1;
++ }
+
+ if (S_ISLNK(file->mode))
+ return 1;
+
-+ if (file == backup_orig_file) {
-+ if (!strcmp(fname, backup_dest_fname))
-+ return set_keep_backup_acl();
-+ }
+ type = SMB_ACL_TYPE_ACCESS;
++ ndx_ptr = (char*)file + file_struct_len;
+ do {
-+ BOOL ok;
-+ 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);
-+
-+ racl_new = &(rsync_acl_lists(type)->racls[aclidx]);
-+ sacl_new = &(smb_acl_lists(type)->sacls[aclidx]);
-+ sacl_orig = sys_acl_get_file(fname, type);
-+ if (!sacl_orig) {
-+ rprintf(FERROR, "set_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(type), strerror(errno));
-+ unchanged = -1;
-+ continue;
-+ }
-+ ok = unpack_smb_acl(&racl_orig, sacl_orig);
-+ sys_acl_free_acl(sacl_orig);
-+ if (!ok) {
-+ unchanged = -1;
-+ continue;
++ 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 (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 (type == SMB_ACL_TYPE_ACCESS)
-+ ok = rsync_acl_extended_parts_equal(&racl_orig, racl_new);
-+ else
-+ ok = rsync_acls_equal(&racl_orig, racl_new);
-+ rsync_acl_free(&racl_orig);
-+ if (ok)
++ if (eq)
+ continue;
-+ if (!dry_run && p_mode) {
++ if (!dry_run && fname) {
+ if (type == SMB_ACL_TYPE_DEFAULT
-+ && rsync_acl_count_entries(racl_new) == 0) {
++ && duo_item->racl.user_obj == NO_ENTRY) {
+ if (sys_acl_delete_def_file(fname) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
-+ fname, strerror(errno));
++ rsyserr(FERROR, errno, "set_acl: sys_acl_delete_def_file(%s)",
++ fname);
+ unchanged = -1;
+ continue;
+ }
+ } else {
-+ mode_t cur_mode = *p_mode;
-+ if (!*sacl_new
-+ && !pack_smb_acl(sacl_new, racl_new)) {
++ 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(*sacl_new, racl_new->mask,
++ cur_mode = change_sacl_perms(duo_item->sacl, &duo_item->racl,
+ cur_mode, file->mode);
-+ if (cur_mode == ~0u)
++ if (cur_mode == (mode_t)~0)
+ continue;
+ }
-+ if (sys_acl_set_file(fname, type, *sacl_new) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
-+ fname, str_acl_type(type),
-+ strerror(errno));
++ 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)
-+ *p_mode = cur_mode;
++ sxp->st.st_mode = cur_mode;
+ }
+ }
+ if (unchanged == 1)
+ return unchanged;
+}
+
-+/* Enumeration functions for uid mapping: */
++/* === Enumeration functions for uid mapping === */
+
+/* Context -- one and only one. Should be cycled through once on uid
+ * mapping and once on gid mapping. */
-+static rsync_acl_list *_enum_racl_lists[] = {
-+ &_rsync_acl_lists[0], &_rsync_acl_lists[1], NULL
++static item_list *_enum_racl_lists[] = {
++ &access_acl_list, &default_acl_list, NULL
+};
+
-+static rsync_acl_list **enum_racl_list = &_enum_racl_lists[0];
++static item_list **enum_racl_list = &_enum_racl_lists[0];
++static int enum_ida_index = 0;
+static size_t enum_racl_index = 0;
-+static size_t enum_ida_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)
+{
-+ const ida_list *idal = (tag_type == SMB_ACL_USER ?
-+ &racl->users : &racl->groups);
++ 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;
+ return NULL;
+}
+
-+static id_t *next_acl_id(SMB_ACL_TAG_T tag_type, const rsync_acl_list *racl_list)
++static id_t *next_acl_id(SMB_ACL_TAG_T tag_type, const item_list *racl_list)
+{
+ for (; enum_racl_index < racl_list->count; enum_racl_index++) {
-+ rsync_acl *racl = &racl_list->racls[enum_racl_index];
-+ id_t *id = next_ace_id(tag_type, racl);
-+ if (id)
++ id_t *id;
++ acl_duo *duo_item = racl_list->items;
++ duo_item += enum_racl_index;
++ if ((id = next_ace_id(tag_type, &duo_item->racl)) != NULL)
+ return id;
+ }
+ enum_racl_index = 0;
+ return next_acl_list_id(SMB_ACL_GROUP);
+}
+
++/* This is used by dest_mode(). */
+int default_perms_for_dir(const char *dir)
+{
+ rsync_acl racl;
+ }
+
+ /* Convert it. */
++ racl = empty_rsync_acl;
+ ok = unpack_smb_acl(&racl, sacl);
+ sys_acl_free_acl(sacl);
+ if (!ok) {
+ }
+
+ /* Apply the permission-bit entries of the default ACL, if any. */
-+ if (rsync_acl_count_entries(&racl) > 0) {
++ 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);
+#endif /* SUPPORT_ACLS */
--- old/backup.c
+++ new/backup.c
-@@ -28,6 +28,7 @@ extern char *backup_suffix;
+@@ -29,6 +29,7 @@ extern char *backup_suffix;
extern char *backup_dir;
extern int am_root;
extern int preserve_devices;
extern int preserve_specials;
extern int preserve_links;
-@@ -132,6 +133,10 @@ static int make_bak_dir(char *fullpath)
+@@ -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);
+- 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)
-+ dup_acl(end, fullpath, st.st_mode);
++ if (preserve_acls) {
++ get_acl(rel, &sx);
++ cache_acl(file, &sx);
++ }
+#endif
++ set_file_attrs(fullpath, file, NULL, 0);
++ free(file);
}
}
*p = '/';
-@@ -185,6 +190,11 @@ static int keep_backup(char *fname)
+@@ -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;
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ push_keep_backup_acl(file, fname, buf);
++ if (preserve_acls) {
++ get_acl(fname, &sx);
++ cache_acl(file, &sx);
++ }
+#endif
+
/* Check to see if this is a device file, or link */
if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) {
-@@ -260,6 +270,10 @@ static int keep_backup(char *fname)
- }
- }
- set_file_attrs(buf, file, NULL, 0);
-+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ cleanup_keep_backup_acl();
-+#endif
- free(file);
-
- 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
-@@ -44,6 +44,7 @@ extern int filesfrom_fd;
+@@ -40,6 +40,7 @@ extern int filesfrom_fd;
extern int one_file_system;
extern int copy_dirlinks;
extern int keep_dirlinks;
extern int preserve_links;
extern int preserve_hard_links;
extern int preserve_devices;
-@@ -970,6 +971,11 @@ static struct file_struct *send_file_nam
+@@ -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);
+#ifdef SUPPORT_ACLS
-+ if (preserve_acls && make_acl(file, fname) < 0)
-+ return NULL;
++ 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);
-@@ -977,6 +983,16 @@ static struct file_struct *send_file_nam
+@@ -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);
+#ifdef SUPPORT_ACLS
+ if (preserve_acls)
-+ send_acl(file, f);
++ send_acl(&sx, f);
+#endif
+ } else {
+#ifdef SUPPORT_ACLS
-+ /* Cleanup unsent ACL(s). */
+ if (preserve_acls)
-+ send_acl(file, -1);
++ free_acl(&sx);
+#endif
}
return file;
}
-@@ -1365,6 +1381,11 @@ struct file_list *recv_file_list(int f)
- flags |= read_byte(f) << 8;
- file = receive_file_entry(flist, flags, f);
-
-+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ receive_acl(file, f);
-+#endif
-+
- if (S_ISREG(file->mode) || S_ISLNK(file->mode))
- stats.total_size += file->length;
-
-@@ -1387,6 +1408,11 @@ struct file_list *recv_file_list(int f)
-
- clean_flist(flist, relative_paths, 1);
-
-+#ifdef SUPPORT_ACLS
-+ if (preserve_acls)
-+ sort_file_acl_index_lists();
-+#endif
-+
- if (f >= 0) {
- recv_uid_list(f, flist);
-
--- old/generator.c
+++ new/generator.c
-@@ -85,6 +85,7 @@ extern long block_size; /* "long" becaus
+@@ -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 struct stats stats;
extern dev_t filesystem_dev;
extern char *backup_dir;
-@@ -321,6 +322,8 @@ static void do_delete_pass(struct file_l
+@@ -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, STRUCT_STAT *st)
++int unchanged_attrs(struct file_struct *file, statx *sxp)
{
-+ /* FIXME: Flag ACL changes. */
-+
if (preserve_perms
- && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS))
+- && (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;
+
+- 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;
++
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && set_acl(NULL, file, sxp) == 0)
return 0;
-@@ -355,6 +358,7 @@ void itemize(struct file_struct *file, i
++#endif
+
+ return 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);
+
+- 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)
+- && st->st_gid != file->gid)
++ && sxp->st.st_gid != file->gid)
iflags |= ITEM_REPORT_GROUP;
-+ /* FIXME: Itemize ACL changes. ITEM_REPORT_XATTR? */
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && set_acl(NULL, file, sxp) == 0)
++ iflags |= ITEM_REPORT_ACL;
++#endif
} else
iflags |= ITEM_IS_NEW;
-@@ -753,6 +757,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
-@@ -844,6 +849,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)
need_fuzzy_dirlist = 1;
}
parent_dirname = dn;
-@@ -871,7 +880,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)) {
-@@ -1343,6 +1353,8 @@ void generate_files(int f_out, struct fi
+@@ -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 (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);
+- 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
-@@ -45,6 +45,7 @@ int copy_dirlinks = 0;
+@@ -47,6 +47,7 @@ int copy_dirlinks = 0;
int copy_links = 0;
int preserve_links = 0;
int preserve_hard_links = 0;
int preserve_perms = 0;
int preserve_executability = 0;
int preserve_devices = 0;
-@@ -194,6 +195,7 @@ static void print_rsync_version(enum log
+@@ -199,6 +200,7 @@ static void print_rsync_version(enum log
char const *got_socketpair = "no ";
char const *have_inplace = "no ";
char const *hardlinks = "no ";
char const *links = "no ";
char const *ipv6 = "no ";
STRUCT_STAT *dumstat;
-@@ -210,6 +212,10 @@ static void print_rsync_version(enum log
+@@ -215,6 +217,10 @@ static void print_rsync_version(enum log
hardlinks = "";
#endif
#ifdef SUPPORT_LINKS
links = "";
#endif
-@@ -223,9 +229,9 @@ static void print_rsync_version(enum log
+@@ -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
-@@ -295,6 +301,9 @@ void usage(enum logcode F)
- rprintf(F," -H, --hard-links preserve hard links\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");
-@@ -410,6 +419,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 },
-@@ -1068,6 +1080,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. */
-@@ -1511,6 +1540,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
-@@ -46,6 +46,7 @@ extern int keep_partial;
+@@ -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 *log_format;
+ extern char *stdout_format;
extern char *tmpdir;
-@@ -344,6 +345,10 @@ int recv_files(int f_in, struct file_lis
- int itemizing = am_daemon ? daemon_log_format_has_i
- : !am_server && log_format_has_i;
+@@ -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)
-@@ -541,7 +546,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
-@@ -33,6 +33,7 @@
+@@ -32,6 +32,7 @@
+
extern int verbose;
extern int dry_run;
- extern int daemon_log_format_has_i;
+extern int preserve_acls;
extern int preserve_perms;
extern int preserve_executability;
extern int preserve_times;
-@@ -101,7 +102,8 @@ void free_sums(struct sum_struct *s)
+@@ -100,7 +101,8 @@ void free_sums(struct sum_struct *s)
/* This is only called when we aren't preserving permissions. Figure out what
* the permissions should be and return them merged back into the mode. */
--mode_t dest_mode(mode_t flist_mode, mode_t cur_mode, int exists)
-+mode_t dest_mode(mode_t flist_mode, mode_t cur_mode, int dflt_perms,
+-mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int exists)
++mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
+ int exists)
{
+ int new_mode;
/* If the file already exists, we'll return the local permissions,
- * possibly tweaked by the --executability option. */
-@@ -116,7 +118,7 @@ mode_t dest_mode(mode_t flist_mode, mode
- cur_mode |= (cur_mode & 0444) >> 2;
+@@ -117,56 +119,65 @@ mode_t dest_mode(mode_t flist_mode, mode
+ new_mode |= (new_mode & 0444) >> 2;
+ }
+ } else {
+- /* Apply the umask and turn off special permissions. */
+- new_mode = flist_mode & (~CHMOD_BITS | (ACCESSPERMS & ~orig_umask));
++ /* Apply destination default permissions and turn
++ * off special permissions. */
++ new_mode = flist_mode & (~CHMOD_BITS | dflt_perms);
+ }
+ return new_mode;
+ }
+
+-int set_file_attrs(char *fname, struct file_struct *file, STRUCT_STAT *st,
++int set_file_attrs(char *fname, struct file_struct *file, statx *sxp,
+ int flags)
+ {
+ int updated = 0;
+- STRUCT_STAT st2;
++ statx sx2;
+ int change_uid, change_gid;
+ mode_t new_mode = file->mode;
+
+- if (!st) {
++ if (!sxp) {
+ if (dry_run)
+ return 1;
+- if (link_stat(fname, &st2, 0) < 0) {
++ if (link_stat(fname, &sx2.st, 0) < 0) {
+ rsyserr(FERROR, errno, "stat %s failed",
+ full_fname(fname));
+ return 0;
+ }
+- st = &st2;
++#ifdef SUPPORT_ACLS
++ sx2.acc_acl = sx2.def_acl = NULL;
++#endif
+ if (!preserve_perms && S_ISDIR(new_mode)
+- && st->st_mode & S_ISGID) {
++ && sx2.st.st_mode & S_ISGID) {
+ /* We just created this directory and its setgid
+ * bit is on, so make sure it stays on. */
+ new_mode |= S_ISGID;
+ }
++ sxp = &sx2;
+ }
+
+- if (!preserve_times || (S_ISDIR(st->st_mode) && omit_dir_times))
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && !ACL_READY(*sxp))
++ get_acl(fname, sxp);
++#endif
++
++ if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && omit_dir_times))
+ flags |= ATTRS_SKIP_MTIME;
+ if (!(flags & ATTRS_SKIP_MTIME)
+- && cmp_time(st->st_mtime, file->modtime) != 0) {
+- int ret = set_modtime(fname, file->modtime, st->st_mode);
++ && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
++ int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
+ if (ret < 0) {
+ rsyserr(FERROR, errno, "failed to set times on %s",
+ full_fname(fname));
+- return 0;
++ goto cleanup;
+ }
+ if (ret == 0) /* ret == 1 if symlink could not be set */
+ updated = 1;
+ }
+
+- change_uid = am_root && preserve_uid && st->st_uid != 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 +187,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));
}
- } else
-- cur_mode = flist_mode & ACCESSPERMS & ~orig_umask;
-+ cur_mode = flist_mode & ACCESSPERMS & dflt_perms;
- if (daemon_chmod_modes && !S_ISLNK(flist_mode))
- cur_mode = tweak_mode(cur_mode, daemon_chmod_modes);
- return (flist_mode & ~CHMOD_BITS) | (cur_mode & CHMOD_BITS);
-@@ -203,9 +205,21 @@ int set_file_attrs(char *fname, struct f
updated = 1;
}
+ if (daemon_chmod_modes && !S_ISLNK(new_mode))
+ new_mode = tweak_mode(new_mode, daemon_chmod_modes);
++
+#ifdef SUPPORT_ACLS
+ /* It's OK to call set_acl() now, even for a dir, as the generator
+ * will enable owner-writability using chmod, if necessary.
+ *
-+ * If set_acl changes permission bits in the process of setting
-+ * an access ACL, it changes st->st_mode so we know whether we
-+ * need to chmod. */
-+ if (preserve_acls && set_acl(fname, file, &st->st_mode) == 0)
++ * If set_acl() changes permission bits in the process of setting
++ * an access ACL, it changes sxp->st.st_mode so we know whether we
++ * need to chmod(). */
++ if (preserve_acls && set_acl(fname, file, sxp) == 0)
+ updated = 1;
+#endif
+
#ifdef HAVE_CHMOD
- if ((st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) {
-- int ret = do_chmod(fname, file->mode);
-+ mode_t mode = file->mode;
-+ int ret = do_chmod(fname, mode);
+- 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 +250,11 @@ int set_file_attrs(char *fname, struct f
+ else
+ rprintf(FCLIENT, "%s is uptodate\n", fname);
+ }
++ cleanup:
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && sxp == &sx2)
++ free_acl(&sx2);
++#endif
+ return updated;
+ }
+
--- old/rsync.h
+++ new/rsync.h
-@@ -658,6 +658,20 @@ struct chmod_mode_struct;
-
- #define UNUSED(x) x __attribute__((__unused__))
+@@ -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
+
-+#if defined SUPPORT_ACLS && defined HAVE_SYS_ACL_H
-+#include <sys/acl.h>
+ #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
++ struct rsync_acl *acc_acl; /* access ACL */
++ struct rsync_acl *def_acl; /* default ACL */
+#endif
-+#include "smb_acls.h"
++} statx;
++
++#define ACL_READY(sx) ((sx).acc_acl != NULL)
+
#include "proto.h"
/* We have replacement versions of these if they're missing. */
--- old/rsync.yo
+++ new/rsync.yo
-@@ -321,6 +321,7 @@ to the detailed description below for a
- -H, --hard-links preserve hard links
+@@ -301,7 +301,7 @@ to the detailed description below for a
+ -q, --quiet suppress non-error messages
+ --no-motd suppress daemon-mode MOTD (see caveat)
+ -c, --checksum skip based on checksum, not mod-time & size
+- -a, --archive archive mode; same as -rlptgoD (no -H)
++ -a, --archive archive mode; same as -rlptgoD (no -H, -A)
+ --no-OPTION turn off an implied OPTION (e.g. --no-D)
+ -r, --recursive recurse into directories
+ -R, --relative use relative path names
+@@ -323,6 +323,7 @@ to the detailed description below for a
-p, --perms preserve permissions
-E, --executability preserve executability
+ --chmod=CHMOD affect file and/or directory permissions
+ -A, --acls preserve ACLs (implies -p) [non-standard]
- --chmod=CHMOD change destination permissions
-o, --owner preserve owner (super-user only)
-g, --group preserve group
-@@ -742,7 +743,9 @@ quote(itemize(
+ --devices preserve device files (super-user only)
+@@ -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.
))
-@@ -773,9 +776,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
-@@ -793,6 +798,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
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
++#ifdef __CYGWIN__
++#define SMB_ACL_LOSES_SPECIAL_MODE_BITS
++#endif
++
+#elif defined HAVE_HPUX_ACLS
+
+/*
+
+#endif /* No ACLs. */
+#endif /* _SMB_ACLS_H */
+--- old/testsuite/acls.test
++++ new/testsuite/acls.test
+@@ -0,0 +1,34 @@
++#! /bin/sh
++
++# This program is distributable under the terms of the GNU GPL (see
++# COPYING).
++
++# Test that rsync handles basic ACL preservation.
++
++. $srcdir/testsuite/rsync.fns
++
++$RSYNC --version | grep ", ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
++case "$setfacl_nodef" in
++true) test_skipped "I don't know how to use your setfacl command" ;;
++esac
++
++makepath "$fromdir/foo"
++echo something >"$fromdir/file1"
++echo else >"$fromdir/file2"
++
++files='foo file1 file2'
++
++setfacl -m u:0:7 "$fromdir/foo" || test_skipped "Your filesystem has ACLs disabled"
++setfacl -m u:0:5 "$fromdir/file1"
++setfacl -m u:0:5 "$fromdir/file2"
++
++$RSYNC -avvA "$fromdir/" "$todir/"
++
++cd "$fromdir"
++getfacl $files >"$scratchdir/acls.txt"
++
++cd "$todir"
++getfacl $files | diff $diffopt "$scratchdir/acls.txt" -
++
++# The script would have aborted on error, so getting here means we've won.
++exit 0
--- old/testsuite/default-acls.test
+++ new/testsuite/default-acls.test
-@@ -0,0 +1,55 @@
+@@ -0,0 +1,65 @@
+#! /bin/sh
+
-+# This program is distributable under the terms of the GNU GPL see
++# This program is distributable under the terms of the GNU GPL (see
+# COPYING).
+
+# Test that rsync obeys default ACLs. -- Matt McCutchen
+. $srcdir/testsuite/rsync.fns
+
+$RSYNC --version | grep ", ACLs" >/dev/null || test_skipped "Rsync is configured without ACL support"
-+setfacl -dm u::rwx,g::---,o::--- "$scratchdir" || test_skipped "Your filesystem has ACLs disabled"
++case "$setfacl_nodef" in
++true) test_skipped "I don't know how to use your setfacl command" ;;
++*-k*) opts='-dm u::7,g::5,o:5' ;;
++*) opts='-m d:u::7,d:g::5,d:o:5' ;;
++esac
++setfacl $opts "$scratchdir" || test_skipped "Your filesystem has ACLs disabled"
+
+# Call as: testit <dirname> <default-acl> <file-expected> <program-expected>
+testit() {
+ todir="$scratchdir/$1"
+ mkdir "$todir"
-+ # FIXME This doesn't work on solaris...
-+ setfacl -k "$todir"
-+ [ "$2" ] && setfacl -dm "$2" "$todir"
++ $setfacl_nodef "$todir"
++ if [ "$2" ]; then
++ case "$setfacl_nodef" in
++ *-k*) opts="-dm $2" ;;
++ *) opts="-m `echo $2 | sed 's/\([ugom]:\)/d:\1/g'`"
++ esac
++ setfacl $opts "$todir"
++ fi
+ # Make sure we obey ACLs when creating a directory to hold multiple transferred files,
+ # even though the directory itself is outside the transfer
+ $RSYNC -rvv "$scratchdir/dir" "$scratchdir/file" "$scratchdir/program" "$todir/to/"
+
+# Test some target directories
+umask 0077
-+testit da777 u::rwx,g::rwx,o::rwx rw-rw-rw- rwxrwxrwx
-+testit da775 u::rwx,g::rwx,o::r-x rw-rw-r-- rwxrwxr-x
-+testit da750 u::rwx,g::r-x,o::--- rw-r----- rwxr-x---
-+testit da770mask u::rwx,g::---,m::rwx,o::--- rw-rw---- rwxrwx---
++testit da777 u::7,g::7,o:7 rw-rw-rw- rwxrwxrwx
++testit da775 u::7,g::7,o:5 rw-rw-r-- rwxrwxr-x
++testit da750 u::7,g::5,o:0 rw-r----- rwxr-x---
++testit da770mask u::7,u:0:7,g::0,m:7,o:0 rw-rw---- rwxrwx---
+testit noda1 '' rw------- rwx------
+umask 0000
+testit noda2 '' rw-rw-rw- rwxrwxrwx
+
+# Hooray
+exit 0
+--- old/testsuite/devices.test
++++ new/testsuite/devices.test
+@@ -42,14 +42,14 @@ touch -r "$fromdir/block" "$fromdir/bloc
+ $RSYNC -ai "$fromdir/block" "$todir/block2" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD+++++++ block
++cD+++++++++ block
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 1 failed"
+
+ $RSYNC -ai "$fromdir/block2" "$todir/block" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD+++++++ block2
++cD+++++++++ block2
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 2 failed"
+
+@@ -58,7 +58,7 @@ sleep 1
+ $RSYNC -Di "$fromdir/block3" "$todir/block" \
+ | tee "$outfile"
+ cat <<EOT >"$chkfile"
+-cD..T.... block3
++cD..T...... block3
+ EOT
+ diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
+
+@@ -66,15 +66,15 @@ $RSYNC -aiHvv "$fromdir/" "$todir/" \
+ | tee "$outfile"
+ filter_outfile
+ cat <<EOT >"$chkfile"
+-.d..t.... ./
+-cD..t.... block
+-cD....... block2
+-cD+++++++ block3
+-hD+++++++ block2.5 => block3
+-cD+++++++ char
+-cD+++++++ char2
+-cD+++++++ char3
+-cS+++++++ fifo
++.d..t...... ./
++cD..t...... block
++cD......... block2
++cD+++++++++ block3
++hD+++++++++ block2.5 => block3
++cD+++++++++ char
++cD+++++++++ char2
++cD+++++++++ char3
++cS+++++++++ fifo
+ EOT
+ if test ! -b "$fromdir/block2.5"; then
+ sed -e '/block2\.5/d' \
+--- 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,16 @@ void recv_uid_list(int f, struct file_li
+@@ -335,6 +336,16 @@ void recv_uid_list(int f, struct file_li
}
}
/* 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);
++}