popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
--- old/acls.c
+++ new/acls.c
-@@ -0,0 +1,1196 @@
+@@ -0,0 +1,1238 @@
+/* -*- 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
+typedef struct {
+ id_t id;
+ uchar access;
-+ SMB_ACL_TAG_T tag_type;
-+} rsync_ace;
++} id_access;
+
+typedef struct {
+ size_t count;
+ size_t malloced;
-+ rsync_ace *races;
++ id_access *idas;
++} ida_list;
++
++#define NO_ENTRY ((uchar)0x80)
++typedef struct {
++ ida_list users;
++ ida_list groups;
++ /* These will be NO_ENTRY if there's no such entry. */
++ uchar user_obj;
++ uchar group_obj;
++ uchar mask;
++ uchar other;
+} rsync_acl;
+
-+static const rsync_acl rsync_acl_initializer = { 0, 0, NULL };
++static const rsync_acl rsync_acl_initializer =
++ { {0, 0, NULL}, {0, 0, NULL}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY};
+
+#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)
+
-+#define PERMS_SPLICE(perms,newbits,where) (((perms) & ~(7 << (where))) | ((newbits) << (where)))
++/* a few useful calculations */
++
++static int rsync_acl_count_entries(const rsync_acl *racl) {
++ return racl->users.count + racl->groups.count
++ + (racl->user_obj != NO_ENTRY)
++ + (racl->group_obj != NO_ENTRY)
++ + (racl->mask != NO_ENTRY)
++ + (racl->other != NO_ENTRY);
++}
++
++static int 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 void rsync_acl_strip_perms(rsync_acl *racl) {
++ racl->user_obj = NO_ENTRY;
++ if (racl->mask == NO_ENTRY)
++ racl->group_obj = NO_ENTRY;
++ else
++ racl->mask = NO_ENTRY;
++ racl->other = NO_ENTRY;
++}
+
-+static void expand_rsync_acl(rsync_acl *racl)
++static void expand_ida_list(ida_list *idal)
+{
+ /* First time through, 0 <= 0, so list is expanded. */
-+ if (racl->malloced <= racl->count) {
-+ rsync_ace *new_ptr;
-+ size_t new_size = racl->malloced + 10;
-+ new_ptr = realloc_array(racl->races, rsync_ace, new_size);
++ if (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 racl->races[0],
-+ racl->races ? "" : " not");
++ (double) new_size * sizeof idal->idas[0],
++ idal->idas ? "" : " not");
+ }
+
-+ racl->races = new_ptr;
-+ racl->malloced = new_size;
++ idal->idas = new_ptr;
++ idal->malloced = new_size;
+
-+ if (!racl->races)
-+ out_of_memory("expand_rsync_acl");
++ if (!idal->idas)
++ out_of_memory("expand_ida_list");
+ }
+}
+
++static void ida_list_free(ida_list *idal)
++{
++ free(idal->idas);
++ idal->idas = NULL;
++ idal->count = 0;
++ idal->malloced = 0;
++}
++
+static void rsync_acl_free(rsync_acl *racl)
+{
-+ free(racl->races);
-+ racl->races = NULL;
-+ racl->count = 0;
-+ racl->malloced = 0;
++ ida_list_free(&racl->users);
++ ida_list_free(&racl->groups);
+}
+
-+static int rsync_ace_sorter(const void *r1, const void *r2)
++static int id_access_sorter(const void *r1, const void *r2)
+{
-+ rsync_ace *race1 = (rsync_ace *)r1;
-+ SMB_ACL_TAG_T rtag1 = race1->tag_type;
-+ id_t rid1 = race1->id;
-+ rsync_ace *race2 = (rsync_ace *)r2;
-+ SMB_ACL_TAG_T rtag2 = race2->tag_type;
-+ id_t rid2 = race2->id;
-+ /* start at the extrema */
-+ if (rtag1 == SMB_ACL_USER_OBJ || rtag2 == SMB_ACL_MASK)
-+ return -1;
-+ if (rtag2 == SMB_ACL_USER_OBJ || rtag1 == SMB_ACL_MASK)
-+ return 1;
-+ /* work inwards */
-+ if (rtag1 == SMB_ACL_OTHER)
-+ return 1;
-+ if (rtag2 == SMB_ACL_OTHER)
-+ return -1;
-+ /* only SMB_ACL_USERs and SMB_ACL_GROUP*s left */
-+ if (rtag1 == SMB_ACL_USER) {
-+ switch (rtag2) {
-+ case SMB_ACL_GROUP:
-+ case SMB_ACL_GROUP_OBJ:
-+ case SMB_ACL_OTHER:
-+ return -1;
-+ }
-+ /* both USER */
-+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
-+ }
-+ if (rtag2 == SMB_ACL_USER)
-+ return 1;
-+ /* only SMB_ACL_GROUP*s to worry about; kick out GROUP_OBJs first */
-+ if (rtag1 == SMB_ACL_GROUP_OBJ)
-+ return -1;
-+ if (rtag2 == SMB_ACL_GROUP_OBJ)
-+ return 1;
-+ /* only SMB_ACL_GROUPs left */
++ id_access *ida1 = (id_access *)r1;
++ id_access *ida2 = (id_access *)r2;
++ id_t rid1 = ida1->id, rid2 = ida2->id;
+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
+}
+
-+static void sort_rsync_acl(rsync_acl *racl)
++static void sort_ida_list(ida_list *idal)
+{
-+ if (!racl->count)
++ if (!idal->count)
+ return;
-+ qsort((void **)racl->races, racl->count, sizeof racl->races[0],
-+ &rsync_ace_sorter);
++ qsort((void **)idal->idas, idal->count, sizeof idal->idas[0],
++ &id_access_sorter);
+}
+
+static BOOL unpack_smb_acl(rsync_acl *racl, SMB_ACL_T sacl)
+{
+ SMB_ACL_ENTRY_T entry;
-+ int rc;
+ 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;
+ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
+ SMB_ACL_PERMSET_T permset;
++ uchar access;
+ void *qualifier;
-+ rsync_ace *race;
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ if ((rc = sys_acl_get_tag_type(entry, &race->tag_type))) {
++ id_access *ida;
++ ida_list *idal;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
-+ race->access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
-+ switch (race->tag_type) {
++ access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
++ /* continue == done with entry; break == store in given idal */
++ switch (tag_type) {
++ case SMB_ACL_USER_OBJ:
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_USER:
++ idal = &racl->users;
++ break;
++ case SMB_ACL_GROUP_OBJ:
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_GROUP:
++ idal = &racl->groups;
+ break;
++ case SMB_ACL_MASK:
++ if (racl->mask == NO_ENTRY)
++ racl->mask = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate MASK entry ignored\n");
++ continue;
++ case SMB_ACL_OTHER:
++ if (racl->other == NO_ENTRY)
++ racl->other = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n");
++ continue;
+ default:
++ rprintf(FINFO, "unpack_smb_acl: warning: entry with unrecognized tag type ignored\n");
+ continue;
+ }
+ if (!(qualifier = sys_acl_get_qualifier(entry))) {
+ rc = EINVAL;
+ break;
+ }
-+ race->id = *((id_t *)qualifier);
-+ sys_acl_free_qualifier(qualifier, race->tag_type);
++ expand_ida_list(idal);
++ ida = &idal->idas[idal->count++];
++ 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",
+ rsync_acl_free(racl);
+ return False;
+ }
-+ sort_rsync_acl(racl);
++
++ sort_ida_list(&racl->users);
++ sort_ida_list(&racl->groups);
++
+ return True;
+}
+
-+static BOOL rsync_acls_equal(const rsync_acl *racl1, const rsync_acl *racl2)
++static BOOL ida_lists_equal(const ida_list *ial1, const ida_list *ial2)
+{
-+ rsync_ace *race1, *race2;
-+ size_t count = racl1->count;
-+ if (count != racl2->count)
++ id_access *ida1, *ida2;
++ size_t count = ial1->count;
++ if (count != ial2->count)
+ return False;
-+ race1 = racl1->races;
-+ race2 = racl2->races;
-+ for (; count--; race1++, race2++) {
-+ if (race1->tag_type != race2->tag_type
-+ || race1->access != race2->access
-+ || ((race1->tag_type == SMB_ACL_USER
-+ || race1->tag_type == SMB_ACL_GROUP)
-+ && race1->id != race2->id))
++ 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;
+ }
+}
+
-+#if 0
-+static void free_rsync_acl_list(rsync_acl_list *racl_list)
-+{
-+ /* Run this in reverse, so references are freed before referents,
-+ * although not currently necessary. */
-+ while (racl_list->count--) {
-+ rsync_acl *racl = &racl_list->racls[racl_list->count];
-+ if (racl)
-+ rsync_acl_free(racl);
-+ }
-+ free(racl_list->racls);
-+ racl_list->racls = NULL;
-+ racl_list->malloced = 0;
-+}
-+#endif
-+
+static int find_matching_rsync_acl(SMB_ACL_TYPE_T type,
+ const rsync_acl_list *racl_list,
+ const rsync_acl *racl)
+ * 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);
++ }
++}
++
+static void send_rsync_acl(int f, const rsync_acl *racl)
+{
-+ rsync_ace *race;
-+ size_t count = racl->count;
++ size_t count = rsync_acl_count_entries(racl);
+ write_int(f, count);
-+ for (race = racl->races; count--; race++) {
-+ int ch;
-+ switch (race->tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ ch = 'u';
-+ break;
-+ case SMB_ACL_USER:
-+ ch = 'U';
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ ch = 'g';
-+ break;
-+ case SMB_ACL_GROUP:
-+ ch = 'G';
-+ break;
-+ case SMB_ACL_OTHER:
-+ ch = 'o';
-+ break;
-+ case SMB_ACL_MASK:
-+ ch = 'm';
-+ break;
-+ default:
-+ rprintf(FERROR,
-+ "send_rsync_acl: unknown tag_type (%02x) on ACE; disregarding\n",
-+ race->tag_type);
-+ continue;
-+ }
-+ write_byte(f, ch);
-+ write_byte(f, race->access);
-+ if (isupper(ch)) {
-+ write_int(f, race->id);
-+ /* FIXME: sorta wasteful: we should maybe buffer as
-+ * many ids as max(ACL_USER + ACL_GROUP) objects to
-+ * keep from making so many calls. */
-+ if (ch == 'U')
-+ add_uid(race->id);
-+ else
-+ add_gid(race->id);
-+ }
++ 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);
+ }
+}
+
+ "unknown SMB_ACL_TYPE_T";
+}
+
-+/* Replace racl with a new three-entry ACL from the given permissions. */
-+static void perms_to_acl(int perms, rsync_acl *racl)
-+{
-+ racl->count = 0;
-+ expand_rsync_acl(racl); /* ensures at least 10 slots */
-+ racl->races[racl->count].tag_type = SMB_ACL_USER_OBJ;
-+ racl->races[racl->count++].access = (perms >> 6) & 7;
-+ racl->races[racl->count].tag_type = SMB_ACL_GROUP_OBJ;
-+ racl->races[racl->count++].access = (perms >> 3) & 7;
-+ racl->races[racl->count].tag_type = SMB_ACL_OTHER;
-+ racl->races[racl->count++].access = (perms >> 0) & 7;
-+}
-+
+/* Generate the ACL(s) for this flist entry;
+ * ACL(s) are either sent or cleaned-up by send_acl() below. */
+int make_acl(const struct file_struct *file, const char *fname)
+ 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. Invent an access ACL from
-+ * permissions; let the default ACL default to empty. */
++ /* ACLs are not supported. Leave list empty. */
+ *curr_racl = rsync_acl_initializer;
-+ if (type == SMB_ACL_TYPE_ACCESS)
-+ perms_to_acl(file->mode & ACCESSPERMS, curr_racl);
+ } else {
+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n",
+ fname, str_acl_type(type), strerror(errno));
+ }
+}
+
-+#if 0
-+static void free_file_acl_index_list(file_acl_index_list *fileaclidx_list)
-+{
-+ free(fileaclidx_list->fileaclidxs);
-+ fileaclidx_list->fileaclidxs = NULL;
-+ fileaclidx_list->malloced = 0;
-+}
-+#endif
-+
+/* lists to hold the SMB_ACL_Ts corresponding to the rsync_acl_list entries */
+
+typedef struct {
+ }
+}
+
-+#if 0
-+static void free_smb_acl_list(SMB_ACL_TYPE_T type)
++#define COE(func,args) /* call or error */ \
++ do { \
++ if (func args) { \
++ errfun = #func; \
++ goto error_exit; \
++ } \
++ } while (0)
++
++static BOOL store_access_in_entry(uchar access, SMB_ACL_ENTRY_T entry)
+{
-+ smb_acl_list *sacl_list = smb_acl_lists(type);
-+ SMB_ACL_T *sacl = sacl_list->sacls;
-+ while (sacl_list->count--) {
-+ if (*sacl)
-+ sys_acl_free_acl(*sacl++);
-+ }
-+ free(sacl_list->sacls);
-+ sacl_list->sacls = NULL;
-+ sacl_list->malloced = 0;
++ const char *errfun = NULL;
++ SMB_ACL_PERMSET_T permset;
++
++ COE( sys_acl_get_permset,(entry, &permset) );
++ COE( sys_acl_clear_perms,(permset) );
++ if (access & 4)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_READ) );
++ if (access & 2)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_WRITE) );
++ if (access & 1)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_EXECUTE) );
++ COE( sys_acl_set_permset,(entry, permset) );
++
++ return True;
++
++error_exit:
++ rprintf(FERROR, "store_access_in_entry %s(): %s\n", errfun,
++ strerror(errno));
++ return False;
+}
-+#endif
+
+/* build an SMB_ACL_T corresponding to an rsync_acl */
-+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl, int perms)
++static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
+{
-+ size_t count = racl->count;
-+ rsync_ace *race = racl->races;
++ size_t count;
++ id_access *ida;
+ const char *errfun = NULL;
-+ int bits;
++ SMB_ACL_ENTRY_T entry;
+
-+ *smb_acl = sys_acl_init(count);
-+ if (!*smb_acl) {
++ if (!(*smb_acl = sys_acl_init(rsync_acl_count_entries(racl)))) {
+ rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n",
+ strerror(errno));
+ return False;
+ }
+
-+ for (; count--; race++) {
-+ SMB_ACL_ENTRY_T entry;
-+ SMB_ACL_PERMSET_T permset;
-+ if (sys_acl_create_entry(smb_acl, &entry)) {
-+ errfun = "sys_acl_create)";
-+ break;
-+ }
-+ if (sys_acl_set_tag_type(entry, race->tag_type)) {
-+ errfun = "sys_acl_set_tag";
-+ break;
-+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ if (sys_acl_set_qualifier(entry, (void*)&race->id)) {
-+ errfun = "sys_acl_set_qualfier";
-+ break;
-+ }
-+ }
-+ if (sys_acl_get_permset(entry, &permset)) {
-+ errfun = "sys_acl_get_permset";
-+ break;
-+ }
-+ if (sys_acl_clear_perms(permset)) {
-+ errfun = "sys_acl_clear_perms";
-+ break;
-+ }
-+ switch (perms >= 0 ? race->tag_type : SMB_ACL_USER_OBJ) {
-+ case SMB_ACL_GROUP_OBJ:
-+ bits = racl->count > 3 ? race->access : (perms >> 3) & 7;
-+ break;
-+ case SMB_ACL_MASK:
-+ bits = (perms >> 3) & 7;
-+ break;
-+ case SMB_ACL_OTHER:
-+ bits = perms & 7;
-+ break;
-+ default:
-+ bits = race->access;
-+ break;
-+ }
-+ if (bits & 4) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_READ)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ }
-+ if (bits & 2) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_WRITE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ }
-+ if (bits & 1) {
-+ if (sys_acl_add_perm(permset, SMB_ACL_EXECUTE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ }
-+ if (sys_acl_set_permset(entry, permset)) {
-+ errfun = "sys_acl_set_permset";
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) );
++ if (!store_access_in_entry(racl->user_obj, entry))
++ goto free_exit;
++
++ 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) );
++ if (!store_access_in_entry(ida->access, entry))
++ goto free_exit;
++ }
++
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP_OBJ) );
++ if (!store_access_in_entry(racl->group_obj, entry))
++ goto free_exit;
++
++ 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) );
++ if (!store_access_in_entry(ida->access, entry))
++ goto free_exit;
++ }
++ if (racl->mask != NO_ENTRY) {
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_MASK) );
++ if (!store_access_in_entry(racl->mask, entry))
++ goto free_exit;
++ }
++
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_OTHER) );
++ if (!store_access_in_entry(racl->other, entry))
++ goto free_exit;
++
++#ifdef DEBUG
++ if (sys_acl_valid(*smb_acl) < 0)
++ rprintf(FINFO, "pack_smb_acl: warning: system says the ACL I packed is invalid\n");
++#endif
++
++ return True;
++
++error_exit:
++ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
++ strerror(errno));
++free_exit:
++ 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)
++{
++ SMB_ACL_ENTRY_T entry;
++ int group_id = mask != NO_ENTRY ? SMB_ACL_MASK : SMB_ACL_GROUP_OBJ;
++ const char *errfun;
++ int rc;
++
++ if (S_ISDIR(mode)) {
++ /* If the sticky bit is going on, it's not safe to allow all
++ * the new ACLs to go into effect before it gets set. */
++ if (!(old_mode & S_ISVTX) && 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;
++ }
++
++ 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 (tag_type == SMB_ACL_USER_OBJ)
++ store_access_in_entry((mode >> 6) & 7, entry);
++ else if (tag_type == group_id)
++ store_access_in_entry((mode >> 3) & 7, entry);
++ else if (tag_type == SMB_ACL_OTHER)
++ store_access_in_entry(mode & 7, entry);
+ }
-+ if (errfun) {
-+ sys_acl_free_acl(*smb_acl);
-+ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
-+ strerror(errno));
-+ return False;
++ if (rc) {
++ rprintf(FERROR, "change_sacl_perms: %s(): %s\n",
++ errfun, strerror(errno));
+ }
-+ return True;
++
++ /* return the mode of the file on disk, as we set them */
++ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS);
+}
+
+static void receive_rsync_acl(rsync_acl *racl, int f)
+{
-+#ifdef ACLS_NEED_MASK
-+ uchar required_mask_perm = 0;
-+#endif
-+ rsync_ace *user = NULL, *group = NULL, *other = NULL, *mask = NULL;
-+ rsync_ace *race;
-+ BOOL need_sort = 0;
++ uchar missing_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);
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
++ uchar access = read_byte(f);
++ if (access & ~ (4 | 2 | 1)) {
++ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
++ access);
++ exit_cleanup(RERR_STREAMIO);
++ }
+ switch (tag) {
+ case 'u':
-+ race->tag_type = SMB_ACL_USER_OBJ;
-+ user = race;
-+ break;
++ if (racl->user_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate USER_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->user_obj = access;
++ continue;
+ case 'U':
-+ race->tag_type = SMB_ACL_USER;
++ idal = &racl->users;
+ break;
+ case 'g':
-+ race->tag_type = SMB_ACL_GROUP_OBJ;
-+ group = race;
-+ break;
++ if (racl->group_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate GROUP_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->group_obj = access;
++ continue;
+ case 'G':
-+ race->tag_type = SMB_ACL_GROUP;
-+ break;
-+ case 'o':
-+ race->tag_type = SMB_ACL_OTHER;
-+ other = race;
++ idal = &racl->groups;
+ break;
+ case 'm':
-+ race->tag_type = SMB_ACL_MASK;
-+ mask = race;
-+ break;
++ if (racl->mask != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate MASK entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->mask = access;
++ continue;
++ case 'o':
++ if (racl->other != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate OTHER entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->other = access;
++ continue;
+ default:
+ rprintf(FERROR, "receive_rsync_acl: unknown tag %c\n",
+ tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
-+ race->access = read_byte(f);
-+ if (race->access & ~ (4 | 2 | 1)) {
-+ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
-+ race->access);
-+ exit_cleanup(RERR_STREAMIO);
++ expand_ida_list(idal);
++ ida = &idal->idas[idal->count++];
++ ida->access = access;
++ ida->id = read_int(f);
++ missing_mask_bits |= access;
++ }
++
++ /* 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 (racl->mask != NO_ENTRY) {
++ /* mask off group perms with it first */
++ racl->group_obj &= racl->mask;
++ racl->mask = NO_ENTRY;
+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ race->id = read_int(f);
-+#if ACLS_NEED_MASK
-+ required_mask_perm |= race->access;
++ } else
+#endif
-+ }
-+#if ACLS_NEED_MASK
-+ else if (race->tag_type == SMB_ACL_GROUP_OBJ)
-+ required_mask_perm |= race->access;
-+#endif
-+ }
-+ if (!user) {
-+ expand_rsync_acl(racl);
-+ user = &racl->races[racl->count++];
-+ user->tag_type = SMB_ACL_USER_OBJ;
-+ user->access = 7;
-+ need_sort = True;
-+ }
-+ if (!group) {
-+ expand_rsync_acl(racl);
-+ group = &racl->races[racl->count++];
-+ group->tag_type = SMB_ACL_GROUP_OBJ;
-+ group->access = 0;
-+ need_sort = True;
-+ }
-+ if (!other) {
-+ expand_rsync_acl(racl);
-+ other = &racl->races[racl->count++];
-+ other->tag_type = SMB_ACL_OTHER;
-+ other->access = 0;
-+ need_sort = True;
-+ }
-+#if ACLS_NEED_MASK
-+ if (!mask) {
-+ expand_rsync_acl(racl);
-+ mask = &racl->races[racl->count++];
-+ mask->tag_type = SMB_ACL_MASK;
-+ mask->access = required_mask_perm;
-+ need_sort = True;
-+ }
-+#else
-+ /* If we, a system without ACLS_NEED_MASK, received data from a
-+ * system that has masks, throw away the extraneous CLASS_OBJs. */
-+ if (mask && racl->count == 4) {
-+ /* mask off group perms with it first */
-+ group->access &= mask->access;
-+ /* dump mask; re-slot any followers-on */
-+ racl->count--;
-+ if (mask != &racl->races[racl->count]) {
-+ *mask = racl->races[racl->count];
-+ need_sort = True;
-+ }
-+ }
-+#endif
-+ if (need_sort)
-+ sort_rsync_acl(racl);
++ if (racl->mask == NO_ENTRY)
++ racl->mask = missing_mask_bits | racl->other;
+}
+
+/* receive and build the rsync_acl_lists */
+ exit_cleanup(RERR_STREAMIO);
+ }
+ if (tag == 'A' || tag == 'D') {
-+ rsync_acl racl = rsync_acl_initializer;
++ 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].
+ } else {
+ ; /* presume they're unequal */
+ }
-+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_orig.count) {
++ 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));
+ } while (BUMP_TYPE(type));
+}
+
-+/* set ACL on rsync-ed or keep_backup-ed file */
-+int set_acl(const char *fname, const struct file_struct *file)
++/* set ACL on rsync-ed or keep_backup-ed file
++ *
++ * 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.
++ *
++ * 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)
+{
+ int unchanged = 1;
+ SMB_ACL_TYPE_T type;
+
-+ if (dry_run || S_ISLNK(file->mode))
-+ return 1; /* FIXME: --dry-run needs to compute this value */
++ if (S_ISLNK(file->mode))
++ return 1;
+
+ if (file == backup_orig_file) {
+ if (!strcmp(fname, backup_dest_fname))
+ }
+ type = SMB_ACL_TYPE_ACCESS;
+ 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);
-+ BOOL ok;
++ 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);
+ unchanged = -1;
+ continue;
+ }
-+ ok = rsync_acls_equal(&racl_orig, racl_new);
++ 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)
+ continue;
-+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_new->count) {
-+ if (sys_acl_delete_def_file(fname) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
-+ fname, strerror(errno));
-+ unchanged = -1;
-+ continue;
-+ }
-+ } else {
-+ /* For an access ACL, we substitute the permissions
-+ * from the file-list because --chmod might have
-+ * changed them. */
-+ int perms = type == SMB_ACL_TYPE_ACCESS
-+ ? (int)(file->mode & CHMOD_BITS) : -1;
-+ if (!*sacl_new
-+ && !pack_smb_acl(sacl_new, racl_new, perms)) {
-+ unchanged = -1;
-+ continue;
-+ }
-+ if (sys_acl_set_file(fname, type, *sacl_new) < 0) {
-+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
-+ fname, str_acl_type(type),
-+ strerror(errno));
-+ unchanged = -1;
-+ continue;
++ if (!dry_run && p_mode) {
++ if (type == SMB_ACL_TYPE_DEFAULT
++ && rsync_acl_count_entries(racl_new) == 0) {
++ if (sys_acl_delete_def_file(fname) < 0) {
++ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
++ fname, strerror(errno));
++ unchanged = -1;
++ continue;
++ }
++ } else {
++ mode_t cur_mode = *p_mode;
++ if (!*sacl_new
++ && !pack_smb_acl(sacl_new, racl_new)) {
++ unchanged = -1;
++ continue;
++ }
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ cur_mode = change_sacl_perms(*sacl_new, racl_new->mask,
++ cur_mode, file->mode);
++ }
++ if (sys_acl_set_file(fname, type, *sacl_new) < 0) {
++ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
++ fname, str_acl_type(type),
++ strerror(errno));
++ unchanged = -1;
++ continue;
++ }
++ if (type == SMB_ACL_TYPE_ACCESS)
++ *p_mode = cur_mode;
+ }
+ }
+ if (unchanged == 1)
+
+static rsync_acl_list **enum_racl_list = &_enum_racl_lists[0];
+static size_t enum_racl_index = 0;
-+static size_t enum_race_index = 0;
++static size_t enum_ida_index = 0;
+
+/* This returns the next tag_type id from the given acl for the next entry,
+ * or it returns 0 if there are no more tag_type ids in the acl. */
+static id_t *next_ace_id(SMB_ACL_TAG_T tag_type, const rsync_acl *racl)
+{
-+ for (; enum_race_index < racl->count; enum_race_index++) {
-+ rsync_ace *race = &racl->races[enum_race_index++];
-+ if (race->tag_type == tag_type)
-+ return &race->id;
++ const ida_list *idal = (tag_type == SMB_ACL_USER ?
++ &racl->users : &racl->groups);
++ if (enum_ida_index < idal->count) {
++ id_access *ida = &idal->idas[enum_ida_index++];
++ return &ida->id;
+ }
-+ enum_race_index = 0;
++ enum_ida_index = 0;
+ return NULL;
+}
+
+{
+ rsync_acl racl;
+ SMB_ACL_T sacl;
-+ BOOL ok, saw_mask = False;
-+ size_t i;
++ BOOL ok;
+ int perms;
+
+ if (dir == NULL)
+ return perms;
+ }
+
-+ /* Look at each default ACL entry and possibly modify three bits of `perms' accordingly.
-+ * If there's "no" default ACL, there will be zero entries and the umask-based perms is unchanged. */
-+ for (i = 0; i < racl.count; i++) {
-+ switch (racl.races[i].tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 6);
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ if (!saw_mask)
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_MASK:
-+ saw_mask = True;
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_OTHER:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 0);
-+ break;
-+ default:
-+ break;
-+ }
++ /* Apply the permission-bit entries of the default ACL, if any. */
++ if (rsync_acl_count_entries(&racl) > 0) {
++ perms = rsync_acl_get_perms(&racl);
++ if (verbose > 2)
++ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ }
++
+ rsync_acl_free(&racl);
-+ if (verbose > 2)
-+ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ return perms;
+}
+
extern struct stats stats;
extern dev_t filesystem_dev;
extern char *backup_dir;
-@@ -753,6 +754,7 @@ static int try_dests_non(struct file_str
+@@ -321,6 +322,8 @@ static void do_delete_pass(struct file_l
+
+ int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st)
+ {
++ /* FIXME: Flag ACL changes. */
++
+ if (preserve_perms
+ && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS))
+ return 0;
+@@ -355,6 +358,7 @@ void itemize(struct file_struct *file, i
+ if (preserve_gid && file->gid != GID_NONE
+ && st->st_gid != file->gid)
+ iflags |= ITEM_REPORT_GROUP;
++ /* FIXME: Itemize ACL changes. ITEM_REPORT_XATTR? */
+ } else
+ iflags |= ITEM_IS_NEW;
+
+@@ -753,6 +757,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 +846,10 @@ static void recv_generator(char *fname,
+@@ -844,6 +849,10 @@ static void recv_generator(char *fname,
}
if (fuzzy_basis)
need_fuzzy_dirlist = 1;
}
parent_dirname = dn;
-@@ -871,7 +877,8 @@ static void recv_generator(char *fname,
+@@ -871,7 +880,8 @@ static void recv_generator(char *fname,
if (!preserve_perms) {
int exists = statret == 0
&& S_ISDIR(st.st_mode) == S_ISDIR(file->mode);
}
if (S_ISDIR(file->mode)) {
-@@ -1343,6 +1350,8 @@ void generate_files(int f_out, struct fi
+@@ -1343,6 +1353,8 @@ void generate_files(int f_out, struct fi
* notice that and let us know via the redo pipe (or its closing). */
ignore_timeout = 1;
if (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);
-@@ -205,7 +207,19 @@ int set_file_attrs(char *fname, struct f
+@@ -203,9 +205,21 @@ int set_file_attrs(char *fname, struct f
+ updated = 1;
+ }
++#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)
++ 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;
-+#ifdef SUPPORT_ACLS
-+ /* If we're preserving ACLs, we only want to turn off any
-+ * newly-disabled bits here and make sure that the special
-+ * permissions are set correctly. We'll let set_acl() turn
-+ * on any new access bits as it installs the full ACL. */
-+ if (preserve_acls) {
-+ mode &= ((S_ISUID | S_ISGID | S_ISVTX)
-+ | (st->st_mode & CHMOD_BITS));
-+ }
-+#endif
-+ ret = do_chmod(fname, mode);
++ int ret = do_chmod(fname, mode);
if (ret < 0) {
rsyserr(FERROR, errno,
"failed to set permissions on %s",
-@@ -217,6 +231,13 @@ int set_file_attrs(char *fname, struct f
- }
- #endif
-
-+#ifdef SUPPORT_ACLS
-+ /* It's OK to call set_acl() now, even for a dir, as the generator
-+ * will enable owner-writability using chmod, if necessary. */
-+ if (preserve_acls && set_acl(fname, file) == 0)
-+ updated = 1;
-+#endif
-+
- if (verbose > 1 && flags & ATTRS_REPORT) {
- enum logcode code = daemon_log_format_has_i || dry_run
- ? FCLIENT : FINFO;
--- old/rsync.h
+++ new/rsync.h
@@ -658,6 +658,20 @@ struct chmod_mode_struct;