X-Git-Url: https://mattmccutchen.net/rsync/rsync-patches.git/blobdiff_plain/936b488af94a058a55abcfadd80ed251889db6ed..a302c04819c87d365481d9a8b516d3c20df8bf46:/acls.diff diff --git a/acls.diff b/acls.diff index be2e799..c86aa66 100644 --- a/acls.diff +++ b/acls.diff @@ -1,5 +1,6 @@ -After applying this patch, run these commands for a successful build: +To use this patch, run these commands for a successful build: + patch -p1 users.count + racl->groups.count -+ + (racl->user_obj != ACL_NO_ENTRY) -+ + (racl->group_obj != ACL_NO_ENTRY) -+ + (racl->mask != ACL_NO_ENTRY) -+ + (racl->other != ACL_NO_ENTRY); ++ + (racl->user_obj != NO_ENTRY) ++ + (racl->group_obj != NO_ENTRY) ++ + (racl->mask != NO_ENTRY) ++ + (racl->other != NO_ENTRY); +} + +static int calc_sacl_entries(const rsync_acl *racl) +{ ++ /* A System ACL always gets user/group/other permission entries. */ + return racl->users.count + racl->groups.count +#ifdef ACLS_NEED_MASK + + 4; +#else -+ + (racl->mask != ACL_NO_ENTRY) + 3; ++ + (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) +{ -+ /* Note that (ACL_NO_ENTRY & 7) is 0. */ -+ return ((racl->user_obj & 7) << 6) -+ + (((racl->mask != ACL_NO_ENTRY ? racl->mask : racl->group_obj) & 7) << 3) -+ + (racl->other & 7); ++ return (racl->user_obj << 6) ++ + ((racl->mask != NO_ENTRY ? racl->mask : racl->group_obj) << 3) ++ + racl->other; +} + ++/* Removes the permission-bit entries from the ACL because these ++ * can be reconstructed from the file's mode. */ +static void rsync_acl_strip_perms(rsync_acl *racl) +{ -+ racl->user_obj = ACL_NO_ENTRY; -+ if (racl->mask == ACL_NO_ENTRY) -+ racl->group_obj = ACL_NO_ENTRY; -+ else -+ racl->mask = ACL_NO_ENTRY; -+ racl->other = ACL_NO_ENTRY; ++ racl->user_obj = NO_ENTRY; ++ if (racl->mask == NO_ENTRY) ++ racl->group_obj = NO_ENTRY; ++ else { ++ if (racl->group_obj == racl->mask) ++ racl->group_obj = NO_ENTRY; ++ racl->mask = NO_ENTRY; ++ } ++ racl->other = NO_ENTRY; +} + -+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) @@ -184,21 +248,53 @@ TODO items: + 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; @@ -208,7 +304,6 @@ TODO items: + 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; @@ -220,34 +315,32 @@ TODO items: + 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 == ACL_NO_ENTRY) ++ 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 == ACL_NO_ENTRY) ++ 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 == ACL_NO_ENTRY) ++ 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 == ACL_NO_ENTRY) ++ if (racl->other == NO_ENTRY) + racl->other = access; + else + rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n"); @@ -261,117 +354,147 @@ TODO items: + 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; -+} ++ sort_ida_entries(&racl->users); ++ sort_ida_entries(&racl->groups); + -+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; ++#ifdef ACLS_NEED_MASK ++ if (!racl->users.count && !racl->groups.count && racl->mask != NO_ENTRY) { ++ /* Throw away a superfluous mask, but mask off the ++ * group perms with it first. */ ++ racl->group_obj &= racl->mask; ++ racl->mask = NO_ENTRY; + } ++#endif ++ + return True; +} + -+static BOOL rsync_acls_equal(const rsync_acl *racl1, const rsync_acl *racl2) ++/* Synactic sugar for system calls */ ++ ++#define CALL_OR_ERROR(func,args,str) \ ++ do { \ ++ if (func args) { \ ++ errfun = str; \ ++ goto error_exit; \ ++ } \ ++ } while (0) ++ ++#define COE(func,args) CALL_OR_ERROR(func,args,#func) ++#define COE2(func,args) CALL_OR_ERROR(func,args,NULL) ++ ++/* Store the permissions in the system ACL entry. */ ++static int store_access_in_entry(uchar access, SMB_ACL_ENTRY_T entry) +{ -+ 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)); ++ const char *errfun = NULL; ++ SMB_ACL_PERMSET_T permset; ++ ++ COE( sys_acl_get_permset,(entry, &permset) ); ++ COE( sys_acl_clear_perms,(permset) ); ++ if (access & 4) ++ COE( sys_acl_add_perm,(permset, SMB_ACL_READ) ); ++ if (access & 2) ++ COE( sys_acl_add_perm,(permset, SMB_ACL_WRITE) ); ++ if (access & 1) ++ COE( sys_acl_add_perm,(permset, SMB_ACL_EXECUTE) ); ++ COE( sys_acl_set_permset,(entry, permset) ); ++ ++ return 0; ++ ++ error_exit: ++ rsyserr(FERROR, errno, "store_access_in_entry %s()", errfun); ++ return -1; +} + -+/* The first parameter will always be a fully-populated rsync_acl. -+ * The second parameter will usually be a condensed rsync_acl, which means -+ * that it might have several of its access objects set to ACL_NO_ENTRY. */ -+static BOOL rsync_acl_extended_parts_equal(const rsync_acl *racl1, -+ const rsync_acl *racl2, mode_t m) ++/* Pack rsync ACL -> system ACL verbatim. Return whether we succeeded. */ ++static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl) +{ -+ /* We ignore any differences that chmod() can take care of. */ -+ if ((racl1->mask ^ racl2->mask) & ACL_NO_ENTRY) ++#ifdef ACLS_NEED_MASK ++ uchar mask_bits; ++#endif ++ size_t count; ++ id_access *ida; ++ const char *errfun = NULL; ++ SMB_ACL_ENTRY_T entry; ++ ++ if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) { ++ rsyserr(FERROR, errno, "pack_smb_acl: sys_acl_init()"); + return False; -+ if (racl1->mask != ACL_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 == ACL_NO_ENTRY) { -+ if (racl1->group_obj != ((m >> 3) & 7)) -+ return False; -+ } else if (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; ++ COE( sys_acl_create_entry,(smb_acl, &entry) ); ++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) ); ++ COE2( store_access_in_entry,(racl->user_obj & 7, entry) ); + -+static rsync_acl_list _rsync_acl_lists[] = { -+ { 0, 0, NULL }, /* SMB_ACL_TYPE_ACCESS */ -+ { 0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */ -+}; ++ for (ida = racl->users.idas, count = racl->users.count; count--; ida++) { ++ COE( sys_acl_create_entry,(smb_acl, &entry) ); ++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER) ); ++ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) ); ++ COE2( store_access_in_entry,(ida->access, entry) ); ++ } + -+static inline rsync_acl_list *rsync_acl_lists(SMB_ACL_TYPE_T type) -+{ -+ return &_rsync_acl_lists[type != SMB_ACL_TYPE_ACCESS]; -+} ++ COE( sys_acl_create_entry,(smb_acl, &entry) ); ++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP_OBJ) ); ++ COE2( store_access_in_entry,(racl->group_obj & 7, entry) ); + -+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"); -+ } ++ 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 & 7, entry) ); ++ ++#ifdef DEBUG ++ if (sys_acl_valid(*smb_acl) < 0) ++ rprintf(FERROR, "pack_smb_acl: warning: system says the ACL I packed is invalid\n"); ++#endif + -+ racl_list->racls = new_ptr; -+ racl_list->malloced = new_size; ++ return True; + -+ if (!racl_list->racls) -+ out_of_memory("expand_rsync_acl_list"); ++ error_exit: ++ if (errfun) { ++ rsyserr(FERROR, errno, "pack_smb_acl %s()", errfun); + } ++ sys_acl_free_acl(*smb_acl); ++ return False; +} + +static int find_matching_rsync_acl(SMB_ACL_TYPE_T type, -+ const rsync_acl_list *racl_list, ++ const item_list *racl_list, + const rsync_acl *racl) +{ + static int access_match = -1, default_match = -1; @@ -384,7 +507,8 @@ TODO items: + if (*match == -1) + *match = racl_list->count - 1; + while (count--) { -+ if (rsync_acls_equal(&racl_list->racls[*match], racl)) ++ rsync_acl *base = racl_list->items; ++ if (rsync_acl_equal(base + *match, racl)) + return *match; + if (!(*match)--) + *match = racl_list->count - 1; @@ -394,13 +518,61 @@ TODO items: + 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 || errno == ENOSYS) { ++ /* 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 ++ * 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, char tag_char) ++ ++/* 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; @@ -418,398 +590,90 @@ TODO items: + } +} + ++/* 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 != ACL_NO_ENTRY) { ++ 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 != ACL_NO_ENTRY) { ++ 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_list(f, &racl->groups, 'G'); -+ if (racl->mask != ACL_NO_ENTRY) { ++ send_ida_entries(f, &racl->groups, 'G'); ++ if (racl->mask != NO_ENTRY) { + write_byte(f, 'm'); + write_byte(f, racl->mask); + } -+ if (racl->other != ACL_NO_ENTRY) { ++ 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) ++/* 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 *curr_racl; ++ rsync_acl *racl, *new_racl; ++ item_list *racl_list; ++ int ndx; + -+ if (S_ISLNK(file->mode)) -+ return 1; ++ if (S_ISLNK(sxp->st.st_mode)) ++ return; + -+ curr_racl = &_curr_rsync_acls[0]; + type = SMB_ACL_TYPE_ACCESS; ++ racl = sxp->acc_acl; ++ racl_list = &access_acl_list; + 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; -+ /* Avoid sending a redundant group/mask value. */ -+ if (curr_racl->group_obj == curr_racl->mask -+ && (preserve_acls == 1 -+ || (!curr_racl->users.count -+ && !curr_racl->groups.count))) -+ curr_racl->mask = ACL_NO_ENTRY; -+ /* Strip access ACLs of permission-bit entries. */ -+ if (type == SMB_ACL_TYPE_ACCESS && preserve_acls == 1) -+ rsync_acl_strip_perms(curr_racl); -+ } else if (errno == ENOTSUP) { -+ /* ACLs are not supported. Leave list empty. */ -+ *curr_racl = rsync_acl_initializer; ++ 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; ++ } ++ ++ /* Avoid sending values that can be inferred from other data. */ ++ if (type == SMB_ACL_TYPE_ACCESS) ++ 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 { -+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n", -+ fname, str_acl_type(type), strerror(errno)); -+ return -1; ++ 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; + } -+ curr_racl++; -+ } while (BUMP_TYPE(type) && S_ISDIR(file->mode)); ++ racl = sxp->def_acl; ++ racl_list = &default_acl_list; ++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode)); + -+ return 0; ++ free_acl(sxp); +} + -+/* 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) ++/* === Receive functions === */ ++ ++static void receive_rsync_acl(rsync_acl *racl, int f, SMB_ACL_TYPE_T type) +{ -+ SMB_ACL_TYPE_T type; -+ rsync_acl *curr_racl; ++ static item_list temp_ida_list = EMPTY_ITEM_LIST; ++ SMB_ACL_TAG_T tag_type = 0, prior_list_type = 0; ++ uchar computed_mask_bits = 0; ++ id_access *ida; ++ size_t count; + -+ if (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 *fais; -+} 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 &_file_acl_index_lists[type != SMB_ACL_TYPE_ACCESS]; -+} -+ -+static void expand_file_acl_index_list(file_acl_index_list *flst) -+{ -+ /* First time through, 0 <= 0, so list is expanded. */ -+ if (flst->malloced <= flst->count) { -+ file_acl_index *new_ptr; -+ size_t new_size; -+ -+ if (flst->malloced < 1000) -+ new_size = flst->malloced + 1000; -+ else -+ new_size = flst->malloced * 2; -+ new_ptr = realloc_array(flst->fais, 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 flst->fais[0], -+ flst->fais ? "" : " not"); -+ } -+ -+ flst->fais = new_ptr; -+ flst->malloced = new_size; -+ -+ if (!flst->fais) -+ out_of_memory("expand_file_acl_index_list"); -+ } -+} -+ -+/* lists to hold the SMB_ACL_Ts corresponding to the rsync_acl_list entries */ -+ -+typedef struct { -+ size_t count; -+ size_t malloced; -+ SMB_ACL_T *sacls; -+} smb_acl_list; -+ -+static smb_acl_list _smb_acl_lists[] = { -+ { 0, 0, NULL }, /* SMB_ACL_TYPE_ACCESS */ -+ { 0, 0, NULL } /* SMB_ACL_TYPE_DEFAULT */ -+}; -+ -+static inline smb_acl_list *smb_acl_lists(SMB_ACL_TYPE_T type) -+{ -+ return &_smb_acl_lists[type != SMB_ACL_TYPE_ACCESS]; -+} -+ -+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"); -+ } -+} -+ -+#define CALL_OR_ERROR(func,args,str) \ -+ do { \ -+ if (func args) { \ -+ errfun = str; \ -+ goto error_exit; \ -+ } \ -+ } while (0) -+ -+#define COE(func,args) CALL_OR_ERROR(func,args,#func) -+#define COE2(func,args) CALL_OR_ERROR(func,args,NULL) -+ -+static int store_access_in_entry(uchar access, SMB_ACL_ENTRY_T entry) -+{ -+ const char *errfun = NULL; -+ SMB_ACL_PERMSET_T permset; -+ -+ COE( sys_acl_get_permset,(entry, &permset) ); -+ COE( sys_acl_clear_perms,(permset) ); -+ if (access & 4) -+ COE( sys_acl_add_perm,(permset, SMB_ACL_READ) ); -+ if (access & 2) -+ COE( sys_acl_add_perm,(permset, SMB_ACL_WRITE) ); -+ if (access & 1) -+ COE( sys_acl_add_perm,(permset, SMB_ACL_EXECUTE) ); -+ COE( sys_acl_set_permset,(entry, permset) ); -+ -+ return 0; -+ -+ error_exit: -+ rprintf(FERROR, "store_access_in_entry %s(): %s\n", errfun, -+ strerror(errno)); -+ return -1; -+} -+ -+/* build an SMB_ACL_T corresponding to an rsync_acl */ -+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl) -+{ -+ size_t count; -+ id_access *ida; -+ const char *errfun = NULL; -+ SMB_ACL_ENTRY_T entry; -+ -+ if (!(*smb_acl = sys_acl_init(calc_sacl_entries(racl)))) { -+ rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n", -+ strerror(errno)); -+ return False; -+ } -+ -+ COE( sys_acl_create_entry,(smb_acl, &entry) ); -+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) ); -+ COE2( store_access_in_entry,(racl->user_obj & 7, entry) ); -+ -+ for (ida = racl->users.idas, count = racl->users.count; -+ count--; ida++) { -+ COE( sys_acl_create_entry,(smb_acl, &entry) ); -+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER) ); -+ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) ); -+ COE2( store_access_in_entry,(ida->access, entry) ); -+ } -+ -+ COE( sys_acl_create_entry,(smb_acl, &entry) ); -+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP_OBJ) ); -+ COE2( store_access_in_entry,(racl->group_obj & 7, entry) ); -+ -+ for (ida = racl->groups.idas, count = racl->groups.count; -+ count--; ida++) { -+ COE( sys_acl_create_entry,(smb_acl, &entry) ); -+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_GROUP) ); -+ COE( sys_acl_set_qualifier,(entry, (void*)&ida->id) ); -+ COE2( store_access_in_entry,(ida->access, entry) ); -+ } -+#ifndef ACLS_NEED_MASK -+ if (racl->mask != ACL_NO_ENTRY) { -+#endif -+ 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) ); -+#ifndef ACLS_NEED_MASK -+ } -+#endif -+ -+ COE( sys_acl_create_entry,(smb_acl, &entry) ); -+ COE( sys_acl_set_tag_type,(entry, SMB_ACL_OTHER) ); -+ COE2( store_access_in_entry,(racl->other & 7, entry) ); -+ -+#ifdef DEBUG -+ if (sys_acl_valid(*smb_acl) < 0) -+ rprintf(FINFO, "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)); -+ } -+ sys_acl_free_acl(*smb_acl); -+ return False; -+} -+ -+static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode) -+{ -+ SMB_ACL_ENTRY_T entry; -+ const char *errfun; -+ int rc; -+ -+ if (S_ISDIR(mode)) { -+ /* If the sticky bit is going on, it's not safe to allow all -+ * the new ACLs 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 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; -+#endif -+ } -+ -+ errfun = "sys_acl_get_entry"; -+ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); -+ rc == 1; -+ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) { -+ SMB_ACL_TAG_T tag_type; -+ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) { -+ errfun = "sys_acl_get_tag_type"; -+ break; -+ } -+ 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 != ACL_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 == ACL_NO_ENTRY) -+ break; -+#endif -+ COE2( store_access_in_entry,((mode >> 3) & 7, entry) ); -+ break; -+ case SMB_ACL_OTHER: -+ COE2( store_access_in_entry,(mode & 7, entry) ); -+ break; -+ } -+ } -+ if (rc) { -+ error_exit: -+ if (errfun) { -+ rprintf(FERROR, "change_sacl_perms: %s(): %s\n", -+ errfun, strerror(errno)); -+ } -+ return ~0u; -+ } -+ -+#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 -+ -+ /* Return the mode of the file on disk, as we will set them. */ -+ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS); -+} -+ -+static void receive_rsync_acl(rsync_acl *racl, int f, SMB_ACL_TYPE_T type) -+{ -+ uchar computed_mask_bits = 0; -+ ida_list *idal = NULL; -+ id_access *ida; -+ size_t count; -+ -+ *racl = rsync_acl_initializer; -+ -+ if (!(count = read_int(f))) ++ if (!(count = read_int(f))) + return; + + while (count--) { @@ -822,34 +686,34 @@ TODO items: + } + switch (tag) { + case 'u': -+ if (racl->user_obj != ACL_NO_ENTRY) { ++ 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': -+ idal = &racl->users; ++ tag_type = SMB_ACL_USER; + break; + case 'g': -+ if (racl->group_obj != ACL_NO_ENTRY) { ++ 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': -+ idal = &racl->groups; ++ tag_type = SMB_ACL_GROUP; + break; + case 'm': -+ if (racl->mask != ACL_NO_ENTRY) { ++ 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 != ACL_NO_ENTRY) { ++ if (racl->other != NO_ENTRY) { + rprintf(FERROR, "receive_rsync_acl: error: duplicate OTHER entry\n"); + exit_cleanup(RERR_STREAMIO); + } @@ -860,371 +724,284 @@ TODO items: + 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 == ACL_NO_ENTRY) ++ if (racl->user_obj == NO_ENTRY) + racl->user_obj = 7; -+ if (racl->group_obj == ACL_NO_ENTRY) ++ if (racl->group_obj == NO_ENTRY) + racl->group_obj = 0; -+ if (racl->other == ACL_NO_ENTRY) ++ 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 != ACL_NO_ENTRY) { -+ /* mask off group perms with it first */ -+ racl->group_obj &= racl->mask | ACL_NO_ENTRY; -+ racl->mask = ACL_NO_ENTRY; ++ /* If we received a superfluous mask, throw it away. */ ++ if (racl->mask != NO_ENTRY) { ++ /* Mask off the group perms with it first. */ ++ racl->group_obj &= racl->mask | NO_ENTRY; ++ racl->mask = NO_ENTRY; + } -+ } else -+#endif -+ if (racl->mask == ACL_NO_ENTRY) /* Always non-empty when needed. */ ++ } else if (racl->mask == NO_ENTRY) /* Must be non-empty with lists. */ + racl->mask = computed_mask_bits | (racl->group_obj & 7); +} + -+/* receive and build the rsync_acl_lists */ ++/* Receive the ACL info the sender has included for this file-list entry. */ +void receive_acl(struct file_struct *file, int f) +{ + SMB_ACL_TYPE_T type; -+ char *fname; ++ item_list *racl_list; + + if (S_ISLNK(file->mode)) + return; + -+ fname = f_name(file, NULL); + type = SMB_ACL_TYPE_ACCESS; ++ racl_list = &access_acl_list; + do { -+ char tag; -+ file_acl_index_list *flst = file_acl_index_lists(type); -+ -+ expand_file_acl_index_list(flst); ++ 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); -+ flst->fais[flst->count].aclidx = racl_list->count; -+ flst->fais[flst->count++].file = file; -+ receive_rsync_acl(&racl, f, type); -+ 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); + } -+ flst->fais[flst->count].aclidx = index; -+ flst->fais[flst->count++].file = file; + } ++ if (type == SMB_ACL_TYPE_ACCESS) ++ F_ACL(file) = ndx; ++ else ++ F_DEF_ACL(file) = ndx; ++ racl_list = &default_acl_list; + } while (BUMP_TYPE(type) && S_ISDIR(file->mode)); +} + -+static int file_acl_index_list_sorter(const void *f1, const void *f2) -+{ -+ const file_acl_index *fileaclidx1 = (const file_acl_index *)f1; -+ const file_acl_index *fileaclidx2 = (const file_acl_index *)f2; -+ return fileaclidx1->file == fileaclidx2->file ? 0 -+ : fileaclidx1->file < fileaclidx2->file ? -1 : 1; -+} -+ -+void sort_file_acl_index_lists() ++/* Turn the ACL data in statx into cached ACL data, setting the index ++ * values in the file struct. */ ++void cache_acl(struct file_struct *file, statx *sxp) +{ + SMB_ACL_TYPE_T type; ++ rsync_acl *racl; ++ item_list *racl_list; ++ int ndx; + -+ type = SMB_ACL_TYPE_ACCESS; -+ do { -+ file_acl_index_list *flst = file_acl_index_lists(type); -+ -+ if (!flst->count) -+ continue; -+ -+ qsort(flst->fais, flst->count, sizeof flst->fais[0], -+ &file_acl_index_list_sorter); -+ } while (BUMP_TYPE(type)); -+} -+ -+static int find_file_acl_index(const file_acl_index_list *flst, -+ const struct file_struct *file) -+{ -+ int low = 0, high = flst->count; -+ const struct file_struct *file_mid; -+ -+ if (!high--) -+ return -1; -+ do { -+ int mid = (high + low) / 2; -+ file_mid = flst->fais[mid].file; -+ if (file_mid == file) -+ return flst->fais[mid].aclidx; -+ if (file_mid > file) -+ high = mid - 1; -+ else -+ low = mid + 1; -+ } while (low < high); -+ if (low == high) { -+ file_mid = flst->fais[low].file; -+ if (file_mid == file) -+ return flst->fais[low].aclidx; -+ } -+ rprintf(FERROR, -+ "find_file_acl_index: can't find entry for file in list\n"); -+ exit_cleanup(RERR_STREAMIO); -+ return -1; -+} -+ -+/* for duplicating ACLs on backups when using backup_dir */ -+int dup_acl(const char *orig, const char *bak, mode_t mode) -+{ -+ SMB_ACL_TYPE_T type; -+ int ret = 0; ++ if (S_ISLNK(file->mode)) ++ return; + + type = SMB_ACL_TYPE_ACCESS; ++ racl = sxp->acc_acl; ++ racl_list = &access_acl_list; + do { -+ SMB_ACL_T sacl_orig, sacl_bak; -+ rsync_acl racl_orig, racl_bak; -+ if (!(sacl_orig = sys_acl_get_file(orig, type))) { -+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s\n", -+ orig, str_acl_type(type), strerror(errno)); -+ ret = -1; -+ continue; -+ } -+ if (!(sacl_bak = sys_acl_get_file(orig, type))) { -+ rprintf(FERROR, "dup_acl: sys_acl_get_file(%s, %s): %s. ignoring\n", -+ bak, str_acl_type(type), strerror(errno)); -+ ret = -1; -+ /* try to forge on through */ ++ if (!racl) ++ ndx = -1; ++ else if ((ndx = find_matching_rsync_acl(type, racl_list, racl)) == -1) { ++ acl_duo *new_duo; ++ ndx = racl_list->count; ++ new_duo = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000); ++ new_duo->racl = *racl; ++ new_duo->sacl = NULL; ++ *racl = empty_rsync_acl; + } -+ if (!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 -+ && racl_orig.user_obj == ACL_NO_ENTRY) { -+ if (sys_acl_delete_def_file(bak) < 0) { -+ rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n", -+ bak, strerror(errno)); -+ ret = -1; -+ } -+ } else if (sys_acl_set_file(bak, type, sacl_bak) < 0) { -+ rprintf(FERROR, "dup_acl: sys_acl_set_file(%s, %s): %s\n", -+ bak, str_acl_type(type), strerror(errno)); -+ ret = -1; -+ } -+ out_with_all: -+ if (sacl_bak) -+ rsync_acl_free(&racl_bak); -+ out_with_one_racl: -+ rsync_acl_free(&racl_orig); -+ out_with_sacls: -+ if (sacl_bak) -+ sys_acl_free_acl(sacl_bak); -+ if (sacl_orig) -+ sys_acl_free_acl(sacl_orig); -+ } while (BUMP_TYPE(type) && S_ISDIR(mode)); ++ if (type == SMB_ACL_TYPE_ACCESS) ++ F_ACL(file) = ndx; ++ else ++ F_DEF_ACL(file) = ndx; ++ racl = sxp->def_acl; ++ racl_list = &default_acl_list; ++ } while (BUMP_TYPE(type) && S_ISDIR(sxp->st.st_mode)); + -+ return ret; ++ free_acl(sxp); +} + -+/* Stuff for redirecting calls to set_acl() from set_file_attrs() -+ * for keep_backup(). */ -+static const struct file_struct *backup_orig_file = NULL; -+static const char null_string[] = ""; -+static const char *backup_orig_fname = null_string; -+static const char *backup_dest_fname = null_string; -+static SMB_ACL_T _backup_sacl[] = { NULL, NULL }; -+ -+void push_keep_backup_acl(const struct file_struct *file, -+ const char *orig, const char *dest) ++static mode_t change_sacl_perms(SMB_ACL_T sacl, rsync_acl *racl, mode_t old_mode, mode_t mode) +{ -+ SMB_ACL_TYPE_T type; -+ SMB_ACL_T *sacl; ++ SMB_ACL_ENTRY_T entry; ++ const char *errfun; ++ int rc; + -+ backup_orig_file = file; -+ backup_orig_fname = orig; -+ backup_dest_fname = dest; ++ if (S_ISDIR(mode)) { ++ /* If the sticky bit is going on, it's not safe to allow all ++ * the new ACL to go into effect before it gets set. */ ++#ifdef SMB_ACL_LOSES_SPECIAL_MODE_BITS ++ if (mode & S_ISVTX) ++ mode &= ~0077; ++#else ++ if (mode & S_ISVTX && !(old_mode & S_ISVTX)) ++ mode &= ~0077; ++ } else { ++ /* If setuid or setgid is going off, it's not safe to allow all ++ * the new ACL to go into effect before they get cleared. */ ++ if ((old_mode & S_ISUID && !(mode & S_ISUID)) ++ || (old_mode & S_ISGID && !(mode & S_ISGID))) ++ mode &= ~0077; ++#endif ++ } + -+ sacl = &_backup_sacl[0]; -+ type = SMB_ACL_TYPE_ACCESS; -+ do { -+ if (type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode)) { -+ *sacl = NULL; ++ errfun = "sys_acl_get_entry"; ++ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); ++ rc == 1; ++ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) { ++ SMB_ACL_TAG_T tag_type; ++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) { ++ errfun = "sys_acl_get_tag_type"; + break; + } -+ if (!(*sacl = sys_acl_get_file(orig, type))) { -+ rprintf(FERROR, -+ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n", -+ orig, str_acl_type(type), -+ strerror(errno)); ++ 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) ++ && BITS_EQUAL(old_mode, mode, CHMOD_BITS)) ++ old_mode &= ~(S_ISUID | S_ISGID | S_ISVTX); ++#endif + -+ sacl = &_backup_sacl[0]; -+ type = SMB_ACL_TYPE_ACCESS; -+ do { -+ if (*sacl) { -+ sys_acl_free_acl(*sacl); -+ *sacl = NULL; -+ } -+ } while (BUMP_TYPE(type)); ++ /* Return the mode of the file on disk, as we will set them. */ ++ return (old_mode & ~ACCESSPERMS) | (mode & ACCESSPERMS); +} + -+/* set ACL on rsync-ed or keep_backup-ed file ++/* 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 mode_p, 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 mode_p to get the return code without changing anything. */ -+int set_acl(const char *fname, const struct file_struct *file, mode_t *mode_p) ++ * Returns 1 for unchanged, 0 for changed, -1 for failed. Call this ++ * with fname set to NULL to just check if the ACL is unchanged. */ ++int set_acl(const char *fname, const struct file_struct *file, statx *sxp) +{ + int unchanged = 1; + SMB_ACL_TYPE_T type; + ++ if (!dry_run && (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; + 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; ++ int32 ndx; ++ BOOL eq; ++ ++ if (type == SMB_ACL_TYPE_ACCESS) { ++ ndx = F_ACL(file); ++ if (ndx < 0 || (size_t)ndx >= access_acl_list.count) ++ continue; ++ duo_item = access_acl_list.items; ++ duo_item += ndx; ++ eq = sxp->acc_acl ++ && rsync_acl_equal_enough(sxp->acc_acl, &duo_item->racl, file->mode); ++ } else { ++ ndx = F_DEF_ACL(file); ++ if (ndx < 0 || (size_t)ndx >= default_acl_list.count) ++ continue; ++ duo_item = default_acl_list.items; ++ duo_item += ndx; ++ eq = sxp->def_acl ++ && rsync_acl_equal(sxp->def_acl, &duo_item->racl); + } -+ if (type == SMB_ACL_TYPE_ACCESS) -+ ok = rsync_acl_extended_parts_equal(&racl_orig, racl_new, file->mode); -+ else -+ ok = rsync_acls_equal(&racl_orig, racl_new); -+ rsync_acl_free(&racl_orig); -+ if (ok) ++ if (eq) + continue; -+ if (!dry_run && mode_p) { ++ if (!dry_run && fname) { + if (type == SMB_ACL_TYPE_DEFAULT -+ && racl_new->user_obj == ACL_NO_ENTRY) { ++ && 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 = *mode_p; -+ 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, ++ 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) -+ *mode_p = cur_mode; ++ sxp->st.st_mode = cur_mode; + } + } + if (unchanged == 1) @@ -1234,23 +1011,23 @@ TODO items: + 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; @@ -1259,12 +1036,13 @@ TODO items: + 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; @@ -1292,6 +1070,7 @@ TODO items: + 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; @@ -1308,7 +1087,8 @@ TODO items: + /* Couldn't get an ACL. Darn. */ + switch (errno) { + case ENOTSUP: -+ /* ACLs are disabled. We could yell at the user to turn them on, but... */ ++ case ENOSYS: ++ /* No ACLs are available. */ + break; + case ENOENT: + if (dry_run) { @@ -1325,6 +1105,7 @@ TODO items: + } + + /* Convert it. */ ++ racl = empty_rsync_acl; + ok = unpack_smb_acl(&racl, sacl); + sys_acl_free_acl(sacl); + if (!ok) { @@ -1333,7 +1114,7 @@ TODO items: + } + + /* Apply the permission-bit entries of the default ACL, if any. */ -+ if (racl.user_obj != ACL_NO_ENTRY) { ++ 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); @@ -1346,51 +1127,111 @@ TODO items: +#endif /* SUPPORT_ACLS */ --- old/backup.c +++ new/backup.c -@@ -28,6 +28,7 @@ extern char *backup_suffix; - extern char *backup_dir; +@@ -23,6 +23,7 @@ + extern int verbose; extern int am_root; +extern int preserve_acls; extern int preserve_devices; extern int preserve_specials; extern int preserve_links; -@@ -132,6 +133,10 @@ static int make_bak_dir(char *fullpath) +@@ -93,7 +94,8 @@ path + ****************************************************************************/ + static int make_bak_dir(char *fullpath) + { +- STRUCT_STAT st; ++ statx sx; ++ struct file_struct *file; + char *rel = fullpath + backup_dir_len; + char *end = rel + strlen(rel); + char *p = end; +@@ -125,15 +127,24 @@ static int make_bak_dir(char *fullpath) + if (p >= rel) { + /* Try to transfer the directory settings of the + * actual dir that the files are coming from. */ +- if (do_stat(rel, &st) < 0) { ++ if (do_stat(rel, &sx.st) < 0) { + rsyserr(FERROR, errno, + "make_bak_dir stat %s failed", + full_fname(rel)); } else { - do_lchown(fullpath, st.st_uid, st.st_gid); - do_chmod(fullpath, st.st_mode); +- do_lchown(fullpath, st.st_uid, st.st_gid); +-#ifdef HAVE_CHMOD +- do_chmod(fullpath, st.st_mode); +#ifdef SUPPORT_ACLS -+ if (preserve_acls) -+ dup_acl(end, fullpath, st.st_mode); ++ sx.acc_acl = sx.def_acl = NULL; +#endif ++ if (!(file = make_file(rel, NULL, NULL, 0, NO_FILTERS))) ++ continue; ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) { ++ get_acl(rel, &sx); ++ cache_acl(file, &sx); ++ } + #endif ++ set_file_attrs(fullpath, file, NULL, 0); ++ free(file); } } *p = '/'; -@@ -185,6 +190,11 @@ static int keep_backup(char *fname) - if (!(buf = get_backup_name(fname))) +@@ -171,15 +182,18 @@ static int robust_move(const char *src, + * We will move the file to be deleted into a parallel directory tree. */ + static int keep_backup(const char *fname) + { +- STRUCT_STAT st; ++ statx sx; + struct file_struct *file; + char *buf; + int kept = 0; + int ret_code; + + /* return if no file to keep */ +- if (do_lstat(fname, &st) < 0) ++ if (do_lstat(fname, &sx.st) < 0) + return 1; ++#ifdef SUPPORT_ACLS ++ sx.acc_acl = sx.def_acl = NULL; ++#endif + + if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) + return 1; /* the file could have disappeared */ +@@ -189,6 +203,13 @@ static int keep_backup(const char *fname return 0; + } +#ifdef SUPPORT_ACLS -+ if (preserve_acls) -+ push_keep_backup_acl(file, fname, buf); ++ if (preserve_acls) { ++ get_acl(fname, &sx); ++ cache_acl(file, &sx); ++ } +#endif + /* Check to see if this is a device file, or link */ if ((am_root && preserve_devices && IS_DEVICE(file->mode)) || (preserve_specials && IS_SPECIAL(file->mode))) { -@@ -260,6 +270,10 @@ static int keep_backup(char *fname) - } - } - set_file_attrs(buf, file, NULL, 0); -+#ifdef SUPPORT_ACLS -+ if (preserve_acls) -+ cleanup_keep_backup_acl(); -+#endif - free(file); +@@ -260,7 +281,7 @@ static int keep_backup(const char *fname + if (robust_move(fname, buf) != 0) { + rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"", + full_fname(fname), buf); +- } else if (st.st_nlink > 1) { ++ } else if (sx.st.st_nlink > 1) { + /* If someone has hard-linked the file into the backup + * dir, rename() might return success but do nothing! */ + robust_unlink(fname); /* Just in case... */ +--- old/compat.c ++++ new/compat.c +@@ -50,6 +50,8 @@ void setup_protocol(int f_out,int f_in) + preserve_uid = ++file_extra_cnt; + if (preserve_gid) + preserve_gid = ++file_extra_cnt; ++ if (preserve_acls && !am_sender) ++ preserve_acls = ++file_extra_cnt; - if (verbose > 1) { + if (remote_protocol == 0) { + if (!read_batch) --- old/configure.in +++ new/configure.in -@@ -482,6 +482,11 @@ if test x"$ac_cv_func_strcasecmp" = x"no +@@ -542,6 +542,11 @@ if test x"$ac_cv_func_strcasecmp" = x"no AC_CHECK_LIB(resolv, strcasecmp) fi @@ -1402,11 +1243,11 @@ TODO items: 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. -@@ -746,6 +751,78 @@ AC_SUBST(OBJ_RESTORE) +@@ -806,6 +811,78 @@ AC_SUBST(OBJ_RESTORE) AC_SUBST(CC_SHOBJ_FLAG) AC_SUBST(BUILD_POPT) -+AC_CHECK_HEADERS(sys/acl.h) ++AC_CHECK_HEADERS(sys/acl.h acl/libacl.h) +AC_CHECK_FUNCS(_acl __acl _facl __facl) +################################################# +# check for ACL support @@ -1481,137 +1322,995 @@ TODO items: AC_CONFIG_FILES([Makefile lib/dummy zlib/dummy popt/dummy shconfig]) AC_OUTPUT ---- old/flist.c -+++ new/flist.c -@@ -44,6 +44,7 @@ extern int filesfrom_fd; - extern int one_file_system; - extern int copy_dirlinks; - extern int keep_dirlinks; -+extern int preserve_acls; - extern int preserve_links; - extern int preserve_hard_links; - extern int preserve_devices; -@@ -965,6 +966,11 @@ static struct file_struct *send_file_nam - if (chmod_modes && !S_ISLNK(file->mode)) - file->mode = tweak_mode(file->mode, chmod_modes); +--- old/flist.c ++++ new/flist.c +@@ -41,6 +41,7 @@ extern int filesfrom_fd; + extern int one_file_system; + extern int copy_dirlinks; + extern int keep_dirlinks; ++extern int preserve_acls; + extern int preserve_links; + extern int preserve_hard_links; + extern int preserve_devices; +@@ -147,6 +148,8 @@ static void list_file_entry(struct file_ + permstring(permbuf, f->mode); + len = F_LENGTH(f); + ++ /* TODO: indicate '+' if the entry has an ACL. */ ++ + #ifdef SUPPORT_LINKS + if (preserve_links && S_ISLNK(f->mode)) { + rprintf(FINFO, "%s %11.0f %s %s -> %s\n", +@@ -662,6 +665,12 @@ static struct file_struct *recv_file_ent + } + #endif + ++#ifdef SUPPORT_ACLS ++ /* We need one or two index int32s when we're preserving ACLs. */ ++ if (preserve_acls) ++ extra_len += (S_ISDIR(mode) ? 2 : 1) * EXTRA_LEN; ++#endif ++ + if (always_checksum && S_ISREG(mode)) + extra_len += SUM_EXTRA_CNT * EXTRA_LEN; + +@@ -799,6 +808,11 @@ static struct file_struct *recv_file_ent + read_buf(f, bp, checksum_len); + } + ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ receive_acl(file, f); ++#endif ++ + return file; + } + +@@ -1068,6 +1082,9 @@ static struct file_struct *send_file_nam + int flags) + { + struct file_struct *file; ++#ifdef SUPPORT_ACLS ++ statx sx; ++#endif + + file = make_file(fname, flist, stp, flags, + f == -2 ? SERVER_FILTERS : ALL_FILTERS); +@@ -1077,12 +1094,26 @@ static struct file_struct *send_file_nam + if (chmod_modes && !S_ISLNK(file->mode)) + file->mode = tweak_mode(file->mode, chmod_modes); + ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && f >= 0) { ++ sx.st.st_mode = file->mode; ++ sx.acc_acl = sx.def_acl = NULL; ++ if (get_acl(fname, &sx) < 0) ++ return NULL; ++ } ++#endif ++ + maybe_emit_filelist_progress(flist->count + flist_count_offset); + + flist_expand(flist); + flist->files[flist->count++] = file; +- if (f >= 0) ++ if (f >= 0) { + send_file_entry(file, f, flist->count - 1); ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ send_acl(&sx, f); ++#endif ++ } + return file; + } + +--- old/generator.c ++++ new/generator.c +@@ -35,6 +35,7 @@ extern int do_progress; + extern int relative_paths; + extern int implied_dirs; + extern int keep_dirlinks; ++extern int preserve_acls; + extern int preserve_links; + extern int preserve_devices; + extern int preserve_specials; +@@ -87,6 +88,7 @@ extern int force_delete; + extern int one_file_system; + extern struct stats stats; + extern dev_t filesystem_dev; ++extern mode_t orig_umask; + extern char *backup_dir; + extern char *backup_suffix; + extern int backup_suffix_len; +@@ -518,21 +520,26 @@ static void do_delete_pass(struct file_l + rprintf(FINFO, " \r"); + } + +-int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st) ++int unchanged_attrs(struct file_struct *file, statx *sxp) + { +- if (preserve_perms && !BITS_EQUAL(st->st_mode, file->mode, CHMOD_BITS)) ++ if (preserve_perms && !BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS)) + return 0; + +- if (am_root && preserve_uid && st->st_uid != F_UID(file)) ++ if (am_root && preserve_uid && sxp->st.st_uid != F_UID(file)) + return 0; + +- if (preserve_gid && F_GID(file) != GID_NONE && st->st_gid != F_GID(file)) ++ if (preserve_gid && F_GID(file) != GID_NONE && sxp->st.st_gid != F_GID(file)) + return 0; + ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && set_acl(NULL, file, sxp) == 0) ++ return 0; ++#endif ++ + return 1; + } + +-void itemize(struct file_struct *file, int ndx, int statret, STRUCT_STAT *st, ++void itemize(struct file_struct *file, int ndx, int statret, statx *sxp, + int32 iflags, uchar fnamecmp_type, const char *xname) + { + if (statret >= 0) { /* A from-dest-dir statret can == 1! */ +@@ -540,20 +547,24 @@ void itemize(struct file_struct *file, i + : S_ISDIR(file->mode) ? !omit_dir_times + : !S_ISLNK(file->mode); + +- if (S_ISREG(file->mode) && F_LENGTH(file) != st->st_size) ++ if (S_ISREG(file->mode) && F_LENGTH(file) != sxp->st.st_size) + iflags |= ITEM_REPORT_SIZE; + if ((iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !keep_time + && !(iflags & ITEM_MATCHED) + && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname)) +- || (keep_time && cmp_time(file->modtime, st->st_mtime) != 0)) ++ || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0)) + iflags |= ITEM_REPORT_TIME; +- if (!BITS_EQUAL(st->st_mode, file->mode, CHMOD_BITS)) ++ if (!BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS)) + iflags |= ITEM_REPORT_PERMS; +- if (preserve_uid && am_root && F_UID(file) != st->st_uid) ++ if (preserve_uid && am_root && F_UID(file) != sxp->st.st_uid) + iflags |= ITEM_REPORT_OWNER; + if (preserve_gid && F_GID(file) != GID_NONE +- && st->st_gid != F_GID(file)) ++ && sxp->st.st_gid != F_GID(file)) + iflags |= ITEM_REPORT_GROUP; ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && set_acl(NULL, file, sxp) == 0) ++ iflags |= ITEM_REPORT_ACL; ++#endif + } else + iflags |= ITEM_IS_NEW; + +@@ -808,7 +819,7 @@ void check_for_finished_hlinks(int itemi + * handling the file, -1 if no dest-linking occurred, or a non-negative + * value if we found an alternate basis file. */ + static int try_dests_reg(struct file_struct *file, char *fname, int ndx, +- char *cmpbuf, STRUCT_STAT *stp, int itemizing, ++ char *cmpbuf, statx *sxp, int itemizing, + enum logcode code) + { + int best_match = -1; +@@ -817,7 +828,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: +@@ -825,16 +836,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; +@@ -849,7 +864,7 @@ static int try_dests_reg(struct file_str + if (j != best_match) { + j = best_match; + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); +- if (link_stat(cmpbuf, stp, 0) < 0) ++ if (link_stat(cmpbuf, &sxp->st, 0) < 0) + return -1; + } + +@@ -859,16 +874,25 @@ static int try_dests_reg(struct file_str + if (!hard_link_one(file, fname, cmpbuf, 1)) + goto try_a_copy; + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, stp, itemizing, code, j); ++ finish_hard_link(file, fname, &sxp->st, itemizing, code, j); + if (itemizing && (verbose > 1 || stdout_format_has_i > 1)) { +- itemize(file, ndx, 1, stp, ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && !ACL_READY(*sxp)) ++ get_acl(fname, sxp); ++#endif ++ itemize(file, ndx, 1, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, + 0, ""); + } + } else + #endif +- if (itemizing) +- itemize(file, ndx, 0, stp, 0, 0, NULL); ++ if (itemizing) { ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && !ACL_READY(*sxp)) ++ get_acl(fname, sxp); ++#endif ++ itemize(file, ndx, 0, sxp, 0, 0, NULL); ++ } + if (verbose > 1 && maybe_ATTRS_REPORT) + rprintf(FCLIENT, "%s is uptodate\n", fname); + return -2; +@@ -885,8 +909,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) +@@ -897,7 +926,7 @@ static int try_dests_reg(struct file_str + } + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, stp, itemizing, code, -1); ++ finish_hard_link(file, fname, &sxp->st, itemizing, code, -1); + #endif + return -2; + } +@@ -909,7 +938,7 @@ static int try_dests_reg(struct file_str + * handling the file, or -1 if no dest-linking occurred, or a non-negative + * value if we found an alternate basis file. */ + static int try_dests_non(struct file_struct *file, char *fname, int ndx, +- char *cmpbuf, STRUCT_STAT *stp, int itemizing, ++ char *cmpbuf, statx *sxp, int itemizing, + enum logcode code) + { + char lnk[MAXPATHLEN]; +@@ -942,24 +971,24 @@ static int try_dests_non(struct file_str + + do { + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); +- if (link_stat(cmpbuf, stp, 0) < 0) ++ if (link_stat(cmpbuf, &sxp->st, 0) < 0) + continue; + switch (type) { + case TYPE_DIR: +- if (!S_ISDIR(stp->st_mode)) ++ if (!S_ISDIR(sxp->st.st_mode)) + continue; + break; + case TYPE_SPECIAL: +- if (!IS_SPECIAL(stp->st_mode)) ++ if (!IS_SPECIAL(sxp->st.st_mode)) + continue; + break; + case TYPE_DEVICE: +- if (!IS_DEVICE(stp->st_mode)) ++ if (!IS_DEVICE(sxp->st.st_mode)) + continue; + break; + #ifdef SUPPORT_LINKS + case TYPE_SYMLINK: +- if (!S_ISLNK(stp->st_mode)) ++ if (!S_ISLNK(sxp->st.st_mode)) + continue; + break; + #endif +@@ -974,7 +1003,7 @@ static int try_dests_non(struct file_str + case TYPE_SPECIAL: + case TYPE_DEVICE: + devp = F_RDEV_P(file); +- if (stp->st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp))) ++ if (sxp->st.st_rdev != MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp))) + continue; + break; + #ifdef SUPPORT_LINKS +@@ -991,7 +1020,11 @@ static int try_dests_non(struct file_str + match_level = 2; + best_match = j; + } +- if (unchanged_attrs(file, stp)) { ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ get_acl(cmpbuf, sxp); ++#endif ++ if (unchanged_attrs(file, sxp)) { + match_level = 3; + best_match = j; + break; +@@ -1004,7 +1037,7 @@ static int try_dests_non(struct file_str + if (j != best_match) { + j = best_match; + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); +- if (link_stat(cmpbuf, stp, 0) < 0) ++ if (link_stat(cmpbuf, &sxp->st, 0) < 0) + return -1; + } + +@@ -1035,7 +1068,15 @@ static int try_dests_non(struct file_str + : ITEM_LOCAL_CHANGE + + (match_level == 3 ? ITEM_XNAME_FOLLOWS : 0); + char *lp = match_level == 3 ? "" : NULL; +- itemize(file, ndx, 0, stp, chg + ITEM_MATCHED, 0, lp); ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ get_acl(fname, sxp); ++#endif ++ itemize(file, ndx, 0, sxp, chg + ITEM_MATCHED, 0, lp); ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ free_acl(sxp); ++#endif + } + if (verbose > 1 && maybe_ATTRS_REPORT) { + rprintf(FCLIENT, "%s%s is uptodate\n", +@@ -1048,6 +1089,7 @@ static int try_dests_non(struct file_str + } + + static int phase = 0; ++static int dflt_perms; + + /* 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 +@@ -1068,7 +1110,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; +@@ -1124,6 +1167,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; +@@ -1131,7 +1177,7 @@ static void recv_generator(char *fname, + const char *dn = file->dirname ? file->dirname : "."; + if (parent_dirname != dn && strcmp(parent_dirname, dn) != 0) { + if (relative_paths && !implied_dirs +- && do_stat(dn, &st) < 0 ++ && do_stat(dn, &sx.st) < 0 + && create_directory_path(fname) < 0) { + rsyserr(FERROR, errno, + "recv_generator: mkdir %s failed", +@@ -1143,6 +1189,10 @@ static void recv_generator(char *fname, + } + if (fuzzy_basis) + need_fuzzy_dirlist = 1; ++#ifdef SUPPORT_ACLS ++ if (!preserve_perms) ++ dflt_perms = default_perms_for_dir(dn); ++#endif + } + parent_dirname = dn; + +@@ -1152,7 +1202,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; + } +@@ -1170,8 +1220,9 @@ static void recv_generator(char *fname, + * mode based on the local permissions and some heuristics. */ + if (!preserve_perms) { + int exists = statret == 0 +- && S_ISDIR(st.st_mode) == S_ISDIR(file->mode); +- file->mode = dest_mode(file->mode, st.st_mode, exists); ++ && S_ISDIR(sx.st.st_mode) == S_ISDIR(file->mode); ++ file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, ++ exists); + } + + if (S_ISDIR(file->mode)) { +@@ -1180,8 +1231,8 @@ static void recv_generator(char *fname, + * file of that name and it is *not* a directory, then + * we need to delete it. If it doesn't exist, then + * (perhaps recursively) create it. */ +- if (statret == 0 && !S_ISDIR(st.st_mode)) { +- if (delete_item(fname, st.st_mode, "directory", del_opts) != 0) ++ if (statret == 0 && !S_ISDIR(sx.st.st_mode)) { ++ if (delete_item(fname, sx.st.st_mode, "directory", del_opts) != 0) + return; + statret = -1; + } +@@ -1190,14 +1241,14 @@ static void recv_generator(char *fname, + dry_run++; + } + real_ret = statret; +- real_st = st; ++ real_sx = sx; + if (new_root_dir) { + if (*fname == '.' && fname[1] == '\0') + statret = -1; + new_root_dir = 0; + } + if (statret != 0 && basis_dir[0] != NULL) { +- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st, ++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { + itemizing = 0; +@@ -1206,7 +1257,11 @@ static void recv_generator(char *fname, + statret = 1; + } + if (itemizing && f_out != -1) { +- itemize(file, ndx, statret, &st, ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && statret == 0) ++ get_acl(fname, &sx); ++#endif ++ itemize(file, ndx, statret, &sx, + statret ? ITEM_LOCAL_CHANGE : 0, 0, NULL); + } + if (real_ret != 0 && do_mkdir(fname,file->mode) < 0 && errno != EEXIST) { +@@ -1226,21 +1281,21 @@ static void recv_generator(char *fname, + return; + } + } +- if (set_file_attrs(fname, file, real_ret ? NULL : &real_st, 0) ++ if (set_file_attrs(fname, file, real_ret ? NULL : &real_sx, 0) + && verbose && code != FNONE && f_out != -1) + rprintf(code, "%s/\n", fname); + if (real_ret != 0 && one_file_system) +- real_st.st_dev = filesystem_dev; ++ real_sx.st.st_dev = filesystem_dev; + if (delete_during && f_out != -1 && !phase && dry_run < 2 + && (file->flags & FLAG_XFER_DIR)) +- delete_in_dir(the_file_list, fname, file, &real_st); +- return; ++ delete_in_dir(the_file_list, fname, file, &real_sx.st); ++ goto cleanup; + } + + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_HLINK_NOT_FIRST(file) +- && hard_link_check(file, ndx, fname, statret, &st, itemizing, code)) +- return; ++ && hard_link_check(file, ndx, fname, statret, &sx, itemizing, code)) ++ goto cleanup; + #endif + if (preserve_links && S_ISLNK(file->mode)) { +@@ -1260,17 +1315,17 @@ static void recv_generator(char *fname, + char lnk[MAXPATHLEN]; + int len; + +- if (!S_ISLNK(st.st_mode)) ++ if (!S_ISLNK(sx.st.st_mode)) + statret = -1; + else if ((len = readlink(fname, lnk, MAXPATHLEN-1)) > 0 + && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') { + /* The link is pointing to the right place. */ + if (itemizing) +- itemize(file, ndx, 0, &st, 0, 0, NULL); +- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT); ++ itemize(file, ndx, 0, &sx, 0, 0, NULL); ++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT); + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, &st, itemizing, code, -1); ++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1); + #endif + if (remove_source_files == 1) + goto return_with_success; +@@ -1278,10 +1333,10 @@ static void recv_generator(char *fname, + } + /* Not the right symlink (or not a symlink), so + * delete it. */ +- if (delete_item(fname, st.st_mode, "symlink", del_opts) != 0) ++ if (delete_item(fname, sx.st.st_mode, "symlink", del_opts) != 0) + return; + } else if (basis_dir[0] != NULL) { +- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st, ++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { + #ifndef CAN_HARDLINK_SYMLINK +@@ -1306,7 +1361,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) +@@ -1332,33 +1387,38 @@ static void recv_generator(char *fname, + if (statret == 0) { + char *t; + if (IS_DEVICE(file->mode)) { +- if (!IS_DEVICE(st.st_mode)) ++ if (!IS_DEVICE(sx.st.st_mode)) + statret = -1; + t = "device file"; + } else { +- if (!IS_SPECIAL(st.st_mode)) ++ if (!IS_SPECIAL(sx.st.st_mode)) + statret = -1; + t = "special file"; + } + if (statret == 0 +- && BITS_EQUAL(st.st_mode, file->mode, _S_IFMT) +- && st.st_rdev == rdev) { ++ && BITS_EQUAL(sx.st.st_mode, file->mode, _S_IFMT) ++ && sx.st.st_rdev == rdev) { + /* The device or special file is identical. */ +- if (itemizing) +- itemize(file, ndx, 0, &st, 0, 0, NULL); +- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT); ++ if (itemizing) { +#ifdef SUPPORT_ACLS -+ if (preserve_acls && make_acl(file, fname) < 0) -+ return NULL; ++ if (preserve_acls) ++ get_acl(fname, &sx); +#endif -+ - maybe_emit_filelist_progress(flist->count + flist_count_offset); ++ itemize(file, ndx, 0, &sx, 0, 0, NULL); ++ } ++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT); + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, &st, itemizing, code, -1); ++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1); + #endif + if (remove_source_files == 1) + goto return_with_success; +- return; ++ goto cleanup; + } +- if (delete_item(fname, st.st_mode, t, del_opts) != 0) ++ if (delete_item(fname, sx.st.st_mode, t, del_opts) != 0) + return; + } else if (basis_dir[0] != NULL) { +- int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &st, ++ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, + itemizing, code); + if (j == -2) { + #ifndef CAN_HARDLINK_SPECIAL +@@ -1388,7 +1448,11 @@ static void recv_generator(char *fname, + } else { + set_file_attrs(fname, file, NULL, 0); + if (itemizing) { +- itemize(file, ndx, statret, &st, ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && statret == 0) ++ get_acl(fname, &sx); ++#endif ++ itemize(file, ndx, statret, &sx, + ITEM_LOCAL_CHANGE, 0, NULL); + } + if (code != FNONE && verbose) +@@ -1400,7 +1464,7 @@ static void recv_generator(char *fname, + if (remove_source_files == 1) + goto return_with_success; + } +- return; ++ goto cleanup; + } - flist_expand(flist); -@@ -972,6 +978,16 @@ static struct file_struct *send_file_nam - if (file->basename[0]) { - flist->files[flist->count++] = file; - send_file_entry(file, f); + if (!S_ISREG(file->mode)) { +@@ -1434,7 +1498,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; +@@ -1443,20 +1507,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, "regular file", del_opts) != 0) ++ if (statret == 0 && !S_ISREG(sx.st.st_mode)) { ++ if (delete_item(fname, sx.st.st_mode, "regular file", 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, code); + if (j == -2) { + if (remove_source_files == 1) + goto return_with_success; +- return; ++ goto cleanup; + } + if (j >= 0) { + fnamecmp = fnamecmpbuf; +@@ -1466,7 +1530,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 +@@ -1485,7 +1549,7 @@ static void recv_generator(char *fname, + rprintf(FINFO, "fuzzy basis selected for %s: %s\n", + fname, fnamecmpbuf); + } +- st.st_size = F_LENGTH(fuzzy_file); ++ sx.st.st_size = F_LENGTH(fuzzy_file); + statret = 0; + fnamecmp = fnamecmpbuf; + fnamecmp_type = FNAMECMP_FUZZY; +@@ -1504,36 +1568,41 @@ static void recv_generator(char *fname, + return; + } + +- if (append_mode && st.st_size > F_LENGTH(file)) ++ if (append_mode && sx.st.st_size > F_LENGTH(file)) + 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, statret, &st, 0, 0, NULL); +- set_file_attrs(fname, file, &st, maybe_ATTRS_REPORT); ++ if (itemizing) { +#ifdef SUPPORT_ACLS -+ if (preserve_acls) -+ send_acl(file, f); ++ if (preserve_acls && statret == 0) ++ get_acl(fnamecmp, &sx); +#endif -+ } else { ++ itemize(file, ndx, statret, &sx, 0, 0, NULL); ++ } ++ set_file_attrs(fname, file, &sx, maybe_ATTRS_REPORT); + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, &st, itemizing, code, -1); ++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1); + #endif + if (remove_source_files != 1) +- return; ++ goto cleanup; + return_with_success: + if (!dry_run) + send_msg_int(MSG_SUCCESS, ndx); +- return; ++ goto cleanup; + } + + prepare_to_open: + if (partialptr) { +- st = partial_st; ++ sx.st = partial_st; + fnamecmp = partialptr; + fnamecmp_type = FNAMECMP_PARTIAL_DIR; + statret = 0; +@@ -1558,16 +1627,20 @@ static void recv_generator(char *fname, + /* pretend the file didn't exist */ + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_HLINK_NOT_LAST(file)) +- return; ++ goto cleanup; + #endif + statret = real_ret = -1; +#ifdef SUPPORT_ACLS -+ /* Cleanup unsent ACL(s). */ -+ if (preserve_acls) -+ send_acl(file, -1); ++ if (preserve_acls && ACL_READY(sx)) ++ free_acl(&sx); +#endif + goto notify_others; + } + + if (inplace && make_backups && 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); +@@ -1578,7 +1651,7 @@ static void recv_generator(char *fname, + full_fname(backupptr)); + unmake_file(back_file); + close(fd); +- return; ++ goto cleanup; + } + if ((f_copy = do_open(backupptr, + O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600)) < 0) { +@@ -1586,14 +1659,14 @@ static void recv_generator(char *fname, + full_fname(backupptr)); + unmake_file(back_file); + close(fd); +- return; ++ goto cleanup; + } + fnamecmp_type = FNAMECMP_BACKUP; + } + + if (verbose > 3) { + rprintf(FINFO, "gen mapped %s of size %.0f\n", +- fnamecmp, (double)st.st_size); ++ fnamecmp, (double)sx.st.st_size); } - return file; - } -@@ -1360,6 +1376,11 @@ struct file_list *recv_file_list(int f) - flags |= read_byte(f) << 8; - file = receive_file_entry(flist, flags, f); + if (verbose > 2) +@@ -1615,26 +1688,34 @@ static void recv_generator(char *fname, + iflags |= ITEM_BASIS_TYPE_FOLLOWS; + if (fnamecmp_type == FNAMECMP_FUZZY) + iflags |= ITEM_XNAME_FOLLOWS; +- itemize(file, -1, real_ret, &real_st, iflags, fnamecmp_type, ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && real_ret == 0) ++ get_acl(fnamecmp, &real_sx); ++#endif ++ itemize(file, -1, real_ret, &real_sx, iflags, fnamecmp_type, + fuzzy_file ? fuzzy_file->basename : NULL); +#ifdef SUPPORT_ACLS + if (preserve_acls) -+ receive_acl(file, f); ++ free_acl(&real_sx); +#endif -+ - if (S_ISREG(file->mode) || S_ISLNK(file->mode)) - stats.total_size += file->length; + } + + if (!do_xfers) { + #ifdef SUPPORT_HARD_LINKS + if (preserve_hard_links && F_IS_HLINKED(file)) +- finish_hard_link(file, fname, &st, itemizing, code, -1); ++ finish_hard_link(file, fname, &sx.st, itemizing, code, -1); + #endif +- return; ++ goto cleanup; + } + if (read_batch) +- return; ++ goto cleanup; -@@ -1382,6 +1403,11 @@ struct file_list *recv_file_list(int f) + 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); - clean_flist(flist, relative_paths, 1); + if (f_copy >= 0) { + close(f_copy); +@@ -1647,6 +1728,13 @@ static void recv_generator(char *fname, + } + close(fd); ++ ++ cleanup: +#ifdef SUPPORT_ACLS + if (preserve_acls) -+ sort_file_acl_index_lists(); ++ free_acl(&sx); +#endif -+ - if (f >= 0) { - recv_uid_list(f, flist); ++ return; + } ---- old/generator.c -+++ new/generator.c -@@ -85,6 +85,7 @@ extern long block_size; /* "long" becaus - extern int max_delete; - extern int force_delete; - extern int one_file_system; -+extern mode_t orig_umask; - extern struct stats stats; - extern dev_t filesystem_dev; - extern char *backup_dir; -@@ -319,6 +320,8 @@ static void do_delete_pass(struct file_l + void generate_files(int f_out, struct file_list *flist, char *local_name) +@@ -1713,6 +1801,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; - int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st) - { -+ /* FIXME: Flag ACL changes. */ ++ dflt_perms = (ACCESSPERMS & ~orig_umask); + - if (preserve_perms - && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) - return 0; -@@ -353,6 +356,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; + for (i = 0; i < flist->count; i++) { + struct file_struct *file = flist->files[i]; -@@ -769,6 +773,7 @@ static int try_dests_non(struct file_str +--- old/hlink.c ++++ new/hlink.c +@@ -27,6 +27,7 @@ extern int verbose; + extern int dry_run; + extern int do_xfers; + extern int link_dest; ++extern int preserve_acls; + extern int make_backups; + extern int protocol_version; + extern int remove_source_files; +@@ -268,15 +269,19 @@ void match_hard_links(void) } - static int phase = 0; -+static int dflt_perms; - - /* 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 -@@ -860,6 +865,10 @@ static void recv_generator(char *fname, + static int maybe_hard_link(struct file_struct *file, int ndx, +- const char *fname, int statret, STRUCT_STAT *stp, ++ const char *fname, int statret, statx *sxp, + const char *oldname, STRUCT_STAT *old_stp, + const char *realname, int itemizing, enum logcode code) + { + if (statret == 0) { +- if (stp->st_dev == old_stp->st_dev +- && stp->st_ino == old_stp->st_ino) { ++ if (sxp->st.st_dev == old_stp->st_dev ++ && sxp->st.st_ino == old_stp->st_ino) { + if (itemizing) { +- itemize(file, ndx, statret, stp, ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && !ACL_READY(*sxp)) ++ get_acl(fname, sxp); ++#endif ++ itemize(file, ndx, statret, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, + 0, ""); } - if (fuzzy_basis) - need_fuzzy_dirlist = 1; +@@ -297,7 +302,11 @@ static int maybe_hard_link(struct file_s + + if (hard_link_one(file, fname, oldname, 0)) { + if (itemizing) { +- itemize(file, ndx, statret, stp, +#ifdef SUPPORT_ACLS -+ if (!preserve_perms) -+ dflt_perms = default_perms_for_dir(dn); ++ if (preserve_acls && statret == 0 && !ACL_READY(*sxp)) ++ get_acl(fname, sxp); +#endif ++ itemize(file, ndx, statret, sxp, + ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0, + realname); } - parent_dirname = dn; - -@@ -887,7 +896,8 @@ static void recv_generator(char *fname, - if (!preserve_perms) { - int exists = statret == 0 - && S_ISDIR(st.st_mode) == S_ISDIR(file->mode); -- file->mode = dest_mode(file->mode, st.st_mode, exists); -+ file->mode = dest_mode(file->mode, st.st_mode, dflt_perms, -+ exists); +@@ -311,7 +320,7 @@ static int maybe_hard_link(struct file_s + /* Only called if FLAG_HLINKED is set and FLAG_HLINK_FIRST is not. Returns: + * 0 = process the file, 1 = skip the file, -1 = error occurred. */ + int hard_link_check(struct file_struct *file, int ndx, const char *fname, +- int statret, STRUCT_STAT *stp, int itemizing, ++ int statret, statx *sxp, int itemizing, + enum logcode code) + { + STRUCT_STAT prev_st; +@@ -362,18 +371,20 @@ int hard_link_check(struct file_struct * + if (statret < 0 && basis_dir[0] != NULL) { + /* If we match an alt-dest item, we don't output this as a change. */ + char cmpbuf[MAXPATHLEN]; +- STRUCT_STAT alt_st; ++ statx alt_sx; + int j = 0; ++#ifdef SUPPORT_ACLS ++ alt_sx.acc_acl = alt_sx.def_acl = NULL; ++#endif + do { + pathjoin(cmpbuf, MAXPATHLEN, basis_dir[j], fname); +- if (link_stat(cmpbuf, &alt_st, 0) < 0) ++ if (link_stat(cmpbuf, &alt_sx.st, 0) < 0) + continue; + if (link_dest) { +- if (prev_st.st_dev != alt_st.st_dev +- || prev_st.st_ino != alt_st.st_ino) ++ if (prev_st.st_dev != alt_sx.st.st_dev ++ || prev_st.st_ino != alt_sx.st.st_ino) + continue; + statret = 1; +- *stp = alt_st; + if (verbose < 2 || !stdout_format_has_i) { + itemizing = 0; + code = FNONE; +@@ -382,16 +393,36 @@ int hard_link_check(struct file_struct * + } + break; + } +- if (!unchanged_file(cmpbuf, file, &alt_st)) ++ if (!unchanged_file(cmpbuf, file, &alt_sx.st)) + continue; + statret = 1; +- *stp = alt_st; +- if (unchanged_attrs(file, &alt_st)) ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ get_acl(cmpbuf, &alt_sx); ++#endif ++ if (unchanged_attrs(file, &alt_sx)) + break; + } while (basis_dir[++j] != NULL); ++ if (statret == 1) { ++ sxp->st = alt_sx.st; ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) { ++ if (!ACL_READY(*sxp)) ++ get_acl(cmpbuf, sxp); ++ else { ++ sxp->acc_acl = alt_sx.acc_acl; ++ sxp->def_acl = alt_sx.def_acl; ++ } ++ } ++#endif ++ } ++#ifdef SUPPORT_ACLS ++ else if (preserve_acls) ++ free_acl(&alt_sx); ++#endif } - if (S_ISDIR(file->mode)) { -@@ -1366,6 +1376,8 @@ void generate_files(int f_out, struct fi - * notice that and let us know via the redo pipe (or its closing). */ - ignore_timeout = 1; +- if (maybe_hard_link(file, ndx, fname, statret, stp, prev_name, &prev_st, ++ if (maybe_hard_link(file, ndx, fname, statret, sxp, prev_name, &prev_st, + realname, itemizing, code) < 0) + return -1; -+ dflt_perms = (ACCESSPERMS & ~orig_umask); -+ - for (i = 0; i < flist->count; i++) { - struct file_struct *file = flist->files[i]; +@@ -426,7 +457,8 @@ void finish_hard_link(struct file_struct + STRUCT_STAT *stp, int itemizing, enum logcode code, + int alt_dest) + { +- STRUCT_STAT st, prev_st; ++ statx prev_sx; ++ STRUCT_STAT st; + char alt_name[MAXPATHLEN], *prev_name; + const char *our_name; + int prev_statret, ndx, prev_ndx = F_HL_PREV(file); +@@ -450,14 +482,24 @@ void finish_hard_link(struct file_struct + } else + our_name = fname; ++#ifdef SUPPORT_ACLS ++ prev_sx.acc_acl = prev_sx.def_acl = NULL; ++#endif ++ + while ((ndx = prev_ndx) >= 0) { ++ int val; + file = FPTR(ndx); + file->flags = (file->flags & ~FLAG_HLINK_FIRST) | FLAG_HLINK_DONE; + prev_ndx = F_HL_PREV(file); + prev_name = f_name(file, NULL); +- prev_statret = link_stat(prev_name, &prev_st, 0); +- if (maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_st, +- our_name, stp, fname, itemizing, code) < 0) ++ prev_statret = link_stat(prev_name, &prev_sx.st, 0); ++ val = maybe_hard_link(file, ndx, prev_name, prev_statret, &prev_sx, ++ our_name, stp, fname, itemizing, code); ++#ifdef SUPPORT_ACLS ++ if (preserve_acls) ++ free_acl(&prev_sx); ++#endif ++ if (val < 0) + continue; + if (remove_source_files == 1 && do_xfers) + send_msg_int(MSG_SUCCESS, ndx); --- old/lib/sysacls.c +++ new/lib/sysacls.c -@@ -0,0 +1,3240 @@ +@@ -0,0 +1,3251 @@ +/* + Unix SMB/CIFS implementation. + Samba system utilities for ACL support. @@ -1635,7 +2334,14 @@ TODO items: +#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) @@ -3911,8 +4617,10 @@ TODO items: + 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 */ + @@ -4852,9 +5560,21 @@ TODO items: +#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 ++#endif ++#ifdef HAVE_ACL_LIBACL_H ++#include ++#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) @@ -4883,20 +5603,26 @@ TODO items: +int sys_acl_free_text(char *text); +int sys_acl_free_acl(SMB_ACL_T the_acl); +int sys_acl_free_qualifier(void *qual, SMB_ACL_TAG_T tagtype); ---- old/mkproto.awk -+++ new/mkproto.awk -@@ -58,7 +58,7 @@ BEGIN { - next; - } - --!/^OFF_T|^size_t|^off_t|^pid_t|^unsigned|^mode_t|^DIR|^user|^int|^char|^uint|^uchar|^short|^struct|^BOOL|^void|^time|^const|^RETSIGTYPE/ { -+!/^OFF_T|^size_t|^off_t|^pid_t|^id_t|^unsigned|^mode_t|^DIR|^user|^int|^char|^uint|^uchar|^short|^struct|^BOOL|^void|^time|^const|^RETSIGTYPE/ { - next; - } ++ ++#endif /* SUPPORT_ACLS */ +--- old/log.c ++++ new/log.c +@@ -625,8 +625,10 @@ static void log_formatted(enum logcode c + c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p'; + c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o'; + c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g'; +- c[8] = '.'; +- c[9] = '\0'; ++ c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u'; ++ c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a'; ++ c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x'; ++ c[11] = '\0'; + if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) { + char ch = iflags & ITEM_IS_NEW ? '+' : '?'; --- old/options.c +++ new/options.c -@@ -45,6 +45,7 @@ int copy_dirlinks = 0; +@@ -47,6 +47,7 @@ int copy_dirlinks = 0; int copy_links = 0; int preserve_links = 0; int preserve_hard_links = 0; @@ -4904,7 +5630,7 @@ TODO items: int preserve_perms = 0; int preserve_executability = 0; int preserve_devices = 0; -@@ -192,6 +193,7 @@ static void print_rsync_version(enum log +@@ -198,6 +199,7 @@ static void print_rsync_version(enum log char const *got_socketpair = "no "; char const *have_inplace = "no "; char const *hardlinks = "no "; @@ -4912,7 +5638,7 @@ TODO items: char const *links = "no "; char const *ipv6 = "no "; STRUCT_STAT *dumstat; -@@ -208,6 +210,10 @@ static void print_rsync_version(enum log +@@ -214,6 +216,10 @@ static void print_rsync_version(enum log hardlinks = ""; #endif @@ -4923,29 +5649,37 @@ TODO items: #ifdef SUPPORT_LINKS links = ""; #endif -@@ -221,9 +227,9 @@ static void print_rsync_version(enum log - rprintf(f, "Copyright (C) 1996-2006 by Andrew Tridgell, Wayne Davison, and others.\n"); - rprintf(f, "\n"); - rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, " -- "%shard links, %ssymlinks, batchfiles,\n", -+ "%shard links, %sACLs, %ssymlinks, batchfiles,\n", - (int) (sizeof (OFF_T) * 8), -- got_socketpair, hardlinks, links); -+ got_socketpair, hardlinks, acls, links); +@@ -232,8 +238,8 @@ static void print_rsync_version(enum log + (int)(sizeof (int64) * 8)); + rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n", + got_socketpair, hardlinks, links, ipv6, have_inplace); +- rprintf(f, " %sappend\n", +- have_inplace); ++ rprintf(f, " %sappend, %sACLs\n", ++ have_inplace, acls); - /* Note that this field may not have type ino_t. It depends - * on the complicated interaction between largefile feature -@@ -293,6 +299,9 @@ void usage(enum logcode F) - rprintf(F," -H, --hard-links preserve hard links\n"); + #ifdef MAINTAINER_MODE + rprintf(f, "Panic Action: \"%s\"\n", get_panic_action()); +@@ -279,7 +285,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"); +@@ -301,6 +307,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"); -@@ -408,6 +417,9 @@ static struct poptOption long_options[] + rprintf(F," --devices preserve device files (super-user only)\n"); +@@ -421,6 +430,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 }, @@ -4955,13 +5689,13 @@ TODO items: {"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 }, -@@ -1066,6 +1078,24 @@ int parse_arguments(int *argc, const cha +@@ -1086,6 +1098,24 @@ int parse_arguments(int *argc, const cha usage(FINFO); exit_cleanup(0); + case 'A': +#ifdef SUPPORT_ACLS -+ preserve_acls++; ++ preserve_acls = 1; + preserve_perms = 1; + break; +#else @@ -4980,7 +5714,7 @@ TODO items: default: /* A large opt value means that set_refuse_options() * turned this option off. */ -@@ -1508,6 +1538,10 @@ void server_options(char **args,int *arg +@@ -1528,6 +1558,10 @@ void server_options(char **args,int *arg if (preserve_hard_links) argstr[x++] = 'H'; @@ -4993,32 +5727,32 @@ TODO items: 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; +@@ -347,6 +348,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 -+ char *parent_dirname = ""; ++ const char *parent_dirname = ""; +#endif - int i, recv_ok; + int ndx, recv_ok; if (verbose > 2) -@@ -541,7 +546,16 @@ int recv_files(int f_in, struct file_lis +@@ -545,7 +550,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; - file->mode = dest_mode(file->mode, st.st_mode, exists); +#ifdef SUPPORT_ACLS -+ char *dn = file->dirname ? file->dirname : "."; ++ const char *dn = file->dirname ? file->dirname : "."; + if (parent_dirname != dn + && strcmp(parent_dirname, dn) != 0) { + dflt_perms = default_perms_for_dir(dn); @@ -5029,62 +5763,203 @@ TODO items: + 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) +@@ -48,7 +49,6 @@ extern int preserve_gid; + extern int inplace; + extern int keep_dirlinks; + extern int make_backups; +-extern mode_t orig_umask; + extern struct stats stats; + extern struct file_list *the_file_list; + extern struct chmod_mode_struct *daemon_chmod_modes; +@@ -153,7 +153,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; +@@ -170,56 +171,65 @@ mode_t dest_mode(mode_t flist_mode, mode + new_mode |= (new_mode & 0444) >> 2; + } + } else { +- /* Apply the umask and turn off special permissions. */ +- new_mode = flist_mode & (~CHMOD_BITS | (ACCESSPERMS & ~orig_umask)); ++ /* Apply destination default permissions and turn ++ * off special permissions. */ ++ new_mode = flist_mode & (~CHMOD_BITS | dflt_perms); + } + return new_mode; + } + +-int set_file_attrs(char *fname, struct file_struct *file, STRUCT_STAT *st, ++int set_file_attrs(char *fname, struct file_struct *file, statx *sxp, + int flags) + { + int updated = 0; +- STRUCT_STAT st2; ++ statx sx2; + int change_uid, change_gid; + mode_t new_mode = file->mode; + +- if (!st) { ++ if (!sxp) { + if (dry_run) + return 1; +- if (link_stat(fname, &st2, 0) < 0) { ++ if (link_stat(fname, &sx2.st, 0) < 0) { + rsyserr(FERROR, errno, "stat %s failed", + full_fname(fname)); + return 0; + } +- st = &st2; ++#ifdef SUPPORT_ACLS ++ sx2.acc_acl = sx2.def_acl = NULL; ++#endif + if (!preserve_perms && S_ISDIR(new_mode) +- && st->st_mode & S_ISGID) { ++ && sx2.st.st_mode & S_ISGID) { + /* We just created this directory and its setgid + * bit is on, so make sure it stays on. */ + new_mode |= S_ISGID; + } ++ sxp = &sx2; + } + +- if (!preserve_times || (S_ISDIR(st->st_mode) && omit_dir_times)) ++#ifdef SUPPORT_ACLS ++ if (preserve_acls && !ACL_READY(*sxp)) ++ get_acl(fname, sxp); ++#endif ++ ++ if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && omit_dir_times)) + flags |= ATTRS_SKIP_MTIME; + if (!(flags & ATTRS_SKIP_MTIME) +- && cmp_time(st->st_mtime, file->modtime) != 0) { +- int ret = set_modtime(fname, file->modtime, st->st_mode); ++ && cmp_time(sxp->st.st_mtime, file->modtime) != 0) { ++ int ret = set_modtime(fname, file->modtime, sxp->st.st_mode); + if (ret < 0) { + rsyserr(FERROR, errno, "failed to set times on %s", + full_fname(fname)); +- return 0; ++ goto cleanup; + } + if (ret == 0) /* ret == 1 if symlink could not be set */ + updated = 1; + } + +- change_uid = am_root && preserve_uid && st->st_uid != F_UID(file); ++ change_uid = am_root && preserve_uid && sxp->st.st_uid != F_UID(file); + change_gid = preserve_gid && F_GID(file) != GID_NONE +- && st->st_gid != F_GID(file); ++ && sxp->st.st_gid != F_GID(file); + #if !defined HAVE_LCHOWN && !defined CHOWN_MODIFIES_SYMLINK +- if (S_ISLNK(st->st_mode)) ++ if (S_ISLNK(sxp->st.st_mode)) + ; + else + #endif +@@ -229,45 +239,57 @@ int set_file_attrs(char *fname, struct f + rprintf(FINFO, + "set uid of %s from %ld to %ld\n", + fname, +- (long)st->st_uid, (long)F_UID(file)); ++ (long)sxp->st.st_uid, (long)F_UID(file)); + } + if (change_gid) { + rprintf(FINFO, + "set gid of %s from %ld to %ld\n", + fname, +- (long)st->st_gid, (long)F_GID(file)); ++ (long)sxp->st.st_gid, (long)F_GID(file)); + } + } + if (do_lchown(fname, +- change_uid ? F_UID(file) : st->st_uid, +- change_gid ? F_GID(file) : st->st_gid) != 0) { ++ change_uid ? F_UID(file) : sxp->st.st_uid, ++ change_gid ? F_GID(file) : sxp->st.st_gid) != 0) { + /* shouldn't have attempted to change uid or gid + * unless have the privilege */ + rsyserr(FERROR, errno, "%s %s failed", + change_uid ? "chown" : "chgrp", + full_fname(fname)); +- return 0; ++ goto cleanup; + } + /* a lchown had been done - we have to re-stat if the + * destination had the setuid or setgid bits set due + * to the side effect of the chown call */ +- if (st->st_mode & (S_ISUID | S_ISGID)) { +- link_stat(fname, st, +- keep_dirlinks && S_ISDIR(st->st_mode)); ++ if (sxp->st.st_mode & (S_ISUID | S_ISGID)) { ++ link_stat(fname, &sxp->st, ++ keep_dirlinks && S_ISDIR(sxp->st.st_mode)); } - } else -- cur_mode = flist_mode & ACCESSPERMS & ~orig_umask; -+ cur_mode = flist_mode & ACCESSPERMS & dflt_perms; - if (daemon_chmod_modes && !S_ISLNK(flist_mode)) - cur_mode = tweak_mode(cur_mode, daemon_chmod_modes); - return (flist_mode & ~CHMOD_BITS) | (cur_mode & CHMOD_BITS); -@@ -203,6 +205,17 @@ 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); +- if (!BITS_EQUAL(st->st_mode, new_mode, CHMOD_BITS)) { ++ if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) { + int ret = do_chmod(fname, new_mode); + if (ret < 0) { + rsyserr(FERROR, errno, + "failed to set permissions on %s", + full_fname(fname)); +- return 0; ++ goto cleanup; + } + if (ret == 0) /* ret == 1 if symlink could not be set */ + updated = 1; +@@ -280,6 +302,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 -@@ -660,6 +660,20 @@ struct chmod_mode_struct; - - #define UNUSED(x) x __attribute__((__unused__)) +@@ -532,6 +532,14 @@ struct idev_node { + #define IN_LOOPBACKNET 127 + #endif -+#if HAVE_POSIX_ACLS|HAVE_UNIXWARE_ACLS|HAVE_SOLARIS_ACLS|\ -+ HAVE_HPUX_ACLS|HAVE_IRIX_ACLS|HAVE_AIX_ACLS|HAVE_TRU64_ACLS ++#ifndef HAVE_NO_ACLS +#define SUPPORT_ACLS 1 +#endif + @@ -5092,25 +5967,85 @@ TODO items: +#define ACLS_NEED_MASK 1 +#endif + -+#if defined SUPPORT_ACLS && defined HAVE_SYS_ACL_H -+#include + #define GID_NONE ((gid_t)-1) + + union file_extras { +@@ -551,6 +559,7 @@ struct file_struct { + extern int file_extra_cnt; + extern int preserve_uid; + extern int preserve_gid; ++extern int preserve_acls; + + #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename)) + #define EXTRA_LEN (sizeof (union file_extras)) +@@ -583,10 +592,12 @@ extern int preserve_gid; + /* When the associated option is on, all entries will have these present: */ + #define F_UID(f) REQ_EXTRA(f, preserve_uid)->unum + #define F_GID(f) REQ_EXTRA(f, preserve_gid)->unum ++#define F_ACL(f) REQ_EXTRA(f, preserve_acls)->unum + + /* These items are per-entry optional and mutally exclusive: */ + #define F_HL_GNUM(f) OPT_EXTRA(f, LEN64_BUMP(f))->num + #define F_HL_PREV(f) OPT_EXTRA(f, LEN64_BUMP(f))->num ++#define F_DEF_ACL(f) OPT_EXTRA(f, LEN64_BUMP(f))->unum + + /* This optional item might follow an F_HL_*() item. + * (Note: a device doesn't need to check LEN64_BUMP(f).) */ +@@ -722,6 +733,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" +@@ -740,6 +762,16 @@ struct chmod_mode_struct; + #define NORETURN __attribute__((__noreturn__)) + #endif + ++typedef struct { ++ STRUCT_STAT st; ++#ifdef SUPPORT_ACLS ++ struct rsync_acl *acc_acl; /* access ACL */ ++ struct rsync_acl *def_acl; /* default ACL */ +#endif -+#include "smb_acls.h" ++} statx; ++ ++#define ACL_READY(sx) ((sx).acc_acl != NULL) + #include "proto.h" /* We have replacement versions of these if they're missing. */ --- old/rsync.yo +++ new/rsync.yo -@@ -321,6 +321,7 @@ to the detailed description below for a - -H, --hard-links preserve hard links +@@ -301,7 +301,7 @@ to the detailed description below for a + -q, --quiet suppress non-error messages + --no-motd suppress daemon-mode MOTD (see caveat) + -c, --checksum skip based on checksum, not mod-time & size +- -a, --archive archive mode; same as -rlptgoD (no -H) ++ -a, --archive archive mode; same as -rlptgoD (no -H, -A) + --no-OPTION turn off an implied OPTION (e.g. --no-D) + -r, --recursive recurse into directories + -R, --relative use relative path names +@@ -323,6 +323,7 @@ to the detailed description below for a -p, --perms preserve permissions -E, --executability preserve executability + --chmod=CHMOD affect file and/or directory permissions + -A, --acls preserve ACLs (implies -p) [non-standard] - --chmod=CHMOD change destination permissions -o, --owner preserve owner (super-user only) -g, --group preserve group -@@ -742,7 +743,9 @@ quote(itemize( + --devices preserve device files (super-user only) +@@ -754,7 +755,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 @@ -5121,7 +6056,7 @@ TODO items: 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 +@@ -785,9 +788,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 @@ -5136,7 +6071,7 @@ TODO items: 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,15 @@ quote(itemize( +@@ -805,6 +810,14 @@ quote(itemization( If bf(--perms) is enabled, this option is ignored. @@ -5144,14 +6079,37 @@ TODO items: +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. ++The ACL-sending protocol used by this version was first introduced in ++the patch that was shipped with 2.6.8. Sending ACLs to an older version ++of the ACL patch is not supported. + dit(bf(--chmod)) This option tells rsync to apply one or more comma-separated "chmod" strings to the permission of the files in the transfer. The resulting value is treated as though it was the permissions +@@ -1402,8 +1415,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. +@@ -1452,7 +1465,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,281 @@ @@ -5541,9 +6499,266 @@ TODO items: + +# 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 <"$chkfile" +-cD+++++++ block ++cD+++++++++ block + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 1 failed" + + $RSYNC -ai "$fromdir/block2" "$todir/block" \ + | tee "$outfile" + cat <"$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 <"$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 <"$chkfile" +-.d..t.... ./ +-cD..t.... block +-cD block2 +-cD+++++++ block3 +-hD+++++++ block2.5 => block3 +-cD+++++++ char +-cD+++++++ char2 +-cD+++++++ char3 +-cS+++++++ fifo ++.d..t...... ./ ++cD..t...... block ++cD block2 ++cD+++++++++ block3 ++hD+++++++++ block2.5 => block3 ++cD+++++++++ char ++cD+++++++++ char2 ++cD+++++++++ char3 ++cS+++++++++ fifo + EOT + if test ! -b "$fromdir/block2.5"; then + sed -e '/block2\.5/d' \ +@@ -94,15 +94,15 @@ if test -b "$fromdir/block2.5"; then + $RSYNC -aii --link-dest="$todir" "$fromdir/" "$chkdir/" \ + | tee "$outfile" + cat <"$chkfile" +-cd ./ +-hD block +-hD block2 +-hD block2.5 +-hD block3 +-hD char +-hD char2 +-hD char3 +-hS fifo ++cd ./ ++hD block ++hD block2 ++hD block2.5 ++hD block3 ++hD char ++hD char2 ++hD char3 ++hS fifo + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 4 failed" + fi +--- old/testsuite/itemize.test ++++ new/testsuite/itemize.test +@@ -38,15 +38,15 @@ rm -f "$to2dir" "$to2dir.test" + $RSYNC -iplr "$fromdir/" "$todir/" \ + | tee "$outfile" + cat <"$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" + +@@ -58,10 +58,10 @@ chmod 601 "$fromdir/foo/config2" + $RSYNC -iplrH "$fromdir/" "$todir/" \ + | tee "$outfile" + cat <"$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" + +@@ -78,11 +78,11 @@ chmod 777 "$todir/bar/baz/rsync" + $RSYNC -iplrtc "$fromdir/" "$todir/" \ + | tee "$outfile" + cat <"$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" + +@@ -107,15 +107,15 @@ $RSYNC -ivvplrtH "$fromdir/" "$todir/" \ + | tee "$outfile" + filter_outfile + cat <"$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" + +@@ -134,8 +134,8 @@ touch "$todir/foo/config2" + $RSYNC -iplrtH "$fromdir/" "$todir/" \ + | tee "$outfile" + cat <"$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" + +@@ -143,15 +143,15 @@ $RSYNC -ivvplrtH --copy-dest=../to "$fro + | tee "$outfile" + filter_outfile + cat <"$chkfile" +-cd ./ +-cd bar/ +-cd bar/baz/ +-cf bar/baz/rsync +-cd foo/ +-cf foo/config1 +-cf foo/config2 +-hf foo/extra => foo/config1 +-cL foo/sym -> ../bar/baz/rsync ++cd ./ ++cd bar/ ++cd bar/baz/ ++cf bar/baz/rsync ++cd foo/ ++cf foo/config1 ++cf foo/config2 ++hf foo/extra => foo/config1 ++cL foo/sym -> ../bar/baz/rsync + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 8 failed" + +@@ -159,7 +159,7 @@ rm -rf "$to2dir" + $RSYNC -iplrtH --copy-dest=../to "$fromdir/" "$to2dir/" \ + | tee "$outfile" + cat <"$chkfile" +-hf foo/extra => foo/config1 ++hf foo/extra => foo/config1 + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 9 failed" + +@@ -185,15 +185,15 @@ $RSYNC -ivvplrtH --link-dest="$todir" "$ + | tee "$outfile" + filter_outfile + cat <"$chkfile" +-cd ./ +-cd bar/ +-cd bar/baz/ +-hf bar/baz/rsync +-cd foo/ +-hf foo/config1 +-hf foo/config2 +-hf foo/extra => foo/config1 +-$L foo/sym -> ../bar/baz/rsync ++cd ./ ++cd bar/ ++cd bar/baz/ ++hf bar/baz/rsync ++cd foo/ ++hf foo/config1 ++hf foo/config2 ++hf foo/extra => foo/config1 ++$L foo/sym -> ../bar/baz/rsync + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 11 failed" + +@@ -233,15 +233,15 @@ $RSYNC -ivvplrtH --compare-dest="$todir" + | tee "$outfile" + filter_outfile + cat <"$chkfile" +-cd ./ +-cd bar/ +-cd bar/baz/ +-.f bar/baz/rsync +-cd foo/ +-.f foo/config1 +-.f foo/config2 +-.f foo/extra +-.L foo/sym -> ../bar/baz/rsync ++cd ./ ++cd bar/ ++cd bar/baz/ ++.f bar/baz/rsync ++cd foo/ ++.f foo/config1 ++.f foo/config2 ++.f foo/extra ++.L foo/sym -> ../bar/baz/rsync + EOT + diff $diffopt "$chkfile" "$outfile" || test_fail "test 15 failed" + --- old/uidlist.c +++ new/uidlist.c -@@ -34,6 +34,7 @@ +@@ -35,6 +35,7 @@ extern int verbose; extern int preserve_uid; extern int preserve_gid; @@ -5551,16 +6766,16 @@ TODO items: extern int numeric_ids; extern int am_root; -@@ -274,7 +275,7 @@ void send_uid_list(int f) - if (numeric_ids) - return; +@@ -270,7 +271,7 @@ void send_uid_list(int f) + { + struct idlist *list; - if (preserve_uid) { + if (preserve_uid || preserve_acls) { int len; /* we send sequences of uid/byte-length/name */ for (list = uidlist; list; list = list->next) { -@@ -291,7 +292,7 @@ void send_uid_list(int f) +@@ -287,7 +288,7 @@ void send_uid_list(int f) write_int(f, 0); } @@ -5569,7 +6784,7 @@ TODO items: 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 +@@ -308,7 +309,7 @@ void recv_uid_list(int f, struct file_li int id, i; char *name; @@ -5578,7 +6793,7 @@ TODO items: /* 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 +@@ -320,7 +321,7 @@ void recv_uid_list(int f, struct file_li } } @@ -5587,7 +6802,7 @@ TODO items: /* 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 +@@ -332,6 +333,16 @@ void recv_uid_list(int f, struct file_li } } @@ -5604,3 +6819,37 @@ TODO items: /* Now convert all the uids/gids from sender values to our values. */ if (am_root && preserve_uid && !numeric_ids) { for (i = 0; i < flist->count; i++) +--- old/util.c ++++ new/util.c +@@ -1467,3 +1467,31 @@ int bitbag_next_bit(struct bitbag *bb, i + + return -1; + } ++ ++void *expand_item_list(item_list *lp, size_t item_size, ++ const char *desc, int incr) ++{ ++ /* First time through, 0 <= 0, so list is expanded. */ ++ if (lp->malloced <= lp->count) { ++ void *new_ptr; ++ size_t new_size = lp->malloced; ++ if (incr < 0) ++ new_size -= incr; /* increase slowly */ ++ else if (new_size < (size_t)incr) ++ new_size += incr; ++ else ++ new_size *= 2; ++ new_ptr = realloc_array(lp->items, char, new_size * item_size); ++ if (verbose >= 4) { ++ rprintf(FINFO, "[%s] expand %s to %.0f bytes, did%s move\n", ++ who_am_i(), desc, (double)new_size * item_size, ++ new_ptr == lp->items ? " not" : ""); ++ } ++ if (!new_ptr) ++ out_of_memory("expand_item_list"); ++ ++ lp->items = new_ptr; ++ lp->malloced = new_size; ++ } ++ return (char*)lp->items + (lp->count++ * item_size); ++}