./configure --enable-acl-support
make
-The program currently complains when the --acls (-A) option is used to copy
-from a disk that doesn't support ACLs. This should be changed to silently
-notice that no ACLs are available to copy. Of course, trying to write out
-ACLs to a non-ACL-supporting disk should complain.
+This code does not yet itemize changes in ACL information (see --itemize),
+and it has a bug where some user/group ACL changes might not be propagated
+from the sender to the receiver if the receiver already has a version of
+the file that does not need any other attribute updates.
--- old/Makefile.in
+++ new/Makefile.in
popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
--- old/acls.c
+++ new/acls.c
-@@ -0,0 +1,1202 @@
+@@ -0,0 +1,1242 @@
+/* -*- c-file-style: "linux" -*-
+ Copyright (C) Andrew Tridgell 1996
+ Copyright (C) Paul Mackerras 1996
++ Copyright (C) Matt McCutchen 2006
++ Copyright (C) Wayne Davison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+
+#ifdef SUPPORT_ACLS
+
-+extern int preserve_acls;
+extern int am_root;
+extern int dry_run;
+extern int orig_umask;
+typedef struct {
+ id_t id;
+ uchar access;
-+ SMB_ACL_TAG_T tag_type;
-+} rsync_ace;
++} id_access;
+
+typedef struct {
+ size_t count;
+ size_t malloced;
-+ rsync_ace *races;
++ id_access *idas;
++} ida_list;
++
++#define NO_ENTRY ((uchar)0x80)
++typedef struct {
++ ida_list users;
++ ida_list groups;
++ /* These will be NO_ENTRY if there's no such entry. */
++ uchar user_obj;
++ uchar group_obj;
++ uchar mask;
++ uchar other;
+} rsync_acl;
+
-+static const rsync_acl rsync_acl_initializer = { 0, 0, NULL };
++static const rsync_acl rsync_acl_initializer =
++ { {0, 0, NULL}, {0, 0, NULL}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY};
++
++#define OTHER_TYPE(t) (SMB_ACL_TYPE_ACCESS+SMB_ACL_TYPE_DEFAULT-(t))
++#define BUMP_TYPE(t) ((t = OTHER_TYPE(t)) == SMB_ACL_TYPE_DEFAULT)
++
++/* a few useful calculations */
++
++static int rsync_acl_count_entries(const rsync_acl *racl) {
++ return racl->users.count + racl->groups.count
++ + (racl->user_obj != NO_ENTRY)
++ + (racl->group_obj != NO_ENTRY)
++ + (racl->mask != NO_ENTRY)
++ + (racl->other != NO_ENTRY);
++}
++
++static int rsync_acl_get_perms(const rsync_acl *racl) {
++ /* Note that (NO_ENTRY & 7) is 0. */
++ return ((racl->user_obj & 7) << 6)
++ + (((racl->mask != NO_ENTRY ? racl->mask : racl->group_obj) & 7) << 3)
++ + (racl->other & 7);
++}
+
-+static void expand_rsync_acl(rsync_acl *racl)
++static void rsync_acl_strip_perms(rsync_acl *racl) {
++ racl->user_obj = NO_ENTRY;
++ if (racl->mask == NO_ENTRY)
++ racl->group_obj = NO_ENTRY;
++ else
++ racl->mask = NO_ENTRY;
++ racl->other = NO_ENTRY;
++}
++
++static void expand_ida_list(ida_list *idal)
+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
-+ if (racl->malloced <= racl->count) {
-+ rsync_ace *new_ptr;
-+ size_t new_size = racl->malloced + 10;
-+ new_ptr = realloc_array(racl->races, rsync_ace, new_size);
++ /* 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 racl->races[0],
-+ racl->races ? "" : " not");
++ (double) new_size * sizeof idal->idas[0],
++ idal->idas ? "" : " not");
+ }
+
-+ racl->races = new_ptr;
-+ racl->malloced = new_size;
++ idal->idas = new_ptr;
++ idal->malloced = new_size;
+
-+ if (!racl->races)
-+ out_of_memory("expand_rsync_acl");
++ if (!idal->idas)
++ out_of_memory("expand_ida_list");
+ }
+}
+
++static void ida_list_free(ida_list *idal)
++{
++ free(idal->idas);
++ idal->idas = NULL;
++ idal->count = 0;
++ idal->malloced = 0;
++}
++
+static void rsync_acl_free(rsync_acl *racl)
+{
-+ free(racl->races);
-+ racl->races = NULL;
-+ racl->count = 0;
-+ racl->malloced = 0;
++ ida_list_free(&racl->users);
++ ida_list_free(&racl->groups);
+}
+
-+static int rsync_ace_sorter(const void *r1, const void *r2)
++static int id_access_sorter(const void *r1, const void *r2)
+{
-+ rsync_ace *race1 = (rsync_ace *)r1;
-+ SMB_ACL_TAG_T rtag1 = race1->tag_type;
-+ id_t rid1 = race1->id;
-+ rsync_ace *race2 = (rsync_ace *)r2;
-+ SMB_ACL_TAG_T rtag2 = race2->tag_type;
-+ id_t rid2 = race2->id;
-+ /* start at the extrema */
-+ if (rtag1 == SMB_ACL_USER_OBJ || rtag2 == SMB_ACL_MASK)
-+ return -1;
-+ if (rtag2 == SMB_ACL_USER_OBJ || rtag1 == SMB_ACL_MASK)
-+ return 1;
-+ /* work inwards */
-+ if (rtag1 == SMB_ACL_OTHER)
-+ return 1;
-+ if (rtag2 == SMB_ACL_OTHER)
-+ return -1;
-+ /* only SMB_ACL_USERs and SMB_ACL_GROUP*s left */
-+ if (rtag1 == SMB_ACL_USER) {
-+ switch (rtag2) {
-+ case SMB_ACL_GROUP:
-+ case SMB_ACL_GROUP_OBJ:
-+ case SMB_ACL_OTHER:
-+ return -1;
-+ }
-+ /* both USER */
-+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
-+ }
-+ if (rtag2 == SMB_ACL_USER)
-+ return 1;
-+ /* only SMB_ACL_GROUP*s to worry about; kick out GROUP_OBJs first */
-+ if (rtag1 == SMB_ACL_GROUP_OBJ)
-+ return -1;
-+ if (rtag2 == SMB_ACL_GROUP_OBJ)
-+ return 1;
-+ /* only SMB_ACL_GROUPs left */
++ id_access *ida1 = (id_access *)r1;
++ id_access *ida2 = (id_access *)r2;
++ id_t rid1 = ida1->id, rid2 = ida2->id;
+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1;
+}
+
-+static void sort_rsync_acl(rsync_acl *racl)
++static void sort_ida_list(ida_list *idal)
+{
-+ if (!racl->count)
++ if (!idal->count)
+ return;
-+ qsort((void **)racl->races, racl->count, sizeof racl->races[0],
-+ &rsync_ace_sorter);
++ qsort((void **)idal->idas, idal->count, sizeof idal->idas[0],
++ &id_access_sorter);
+}
+
+static BOOL unpack_smb_acl(rsync_acl *racl, SMB_ACL_T sacl)
+{
+ SMB_ACL_ENTRY_T entry;
-+ int rc;
+ const char *errfun;
++ int rc;
++
+ *racl = rsync_acl_initializer;
+ errfun = "sys_acl_get_entry";
+ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
+ rc == 1;
+ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
+ SMB_ACL_PERMSET_T permset;
++ uchar access;
+ void *qualifier;
-+ rsync_ace *race;
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ if ((rc = sys_acl_get_tag_type(entry, &race->tag_type))) {
++ id_access *ida;
++ ida_list *idal;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
+ errfun = "sys_acl_get_tag_type";
+ break;
+ }
-+ race->access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
-+ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
-+ switch (race->tag_type) {
++ access = (sys_acl_get_perm(permset, SMB_ACL_READ) ? 4 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? 2 : 0)
++ | (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? 1 : 0);
++ /* continue == done with entry; break == store in given idal */
++ switch (tag_type) {
++ case SMB_ACL_USER_OBJ:
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate USER_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_USER:
++ idal = &racl->users;
++ break;
++ case SMB_ACL_GROUP_OBJ:
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate GROUP_OBJ entry ignored\n");
++ continue;
+ case SMB_ACL_GROUP:
++ idal = &racl->groups;
+ break;
++ case SMB_ACL_MASK:
++ if (racl->mask == NO_ENTRY)
++ racl->mask = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate MASK entry ignored\n");
++ continue;
++ case SMB_ACL_OTHER:
++ if (racl->other == NO_ENTRY)
++ racl->other = access;
++ else
++ rprintf(FINFO, "unpack_smb_acl: warning: duplicate OTHER entry ignored\n");
++ continue;
+ default:
++ rprintf(FINFO, "unpack_smb_acl: warning: entry with unrecognized tag type ignored\n");
+ continue;
+ }
+ if (!(qualifier = sys_acl_get_qualifier(entry))) {
+ rc = EINVAL;
+ break;
+ }
-+ race->id = *((id_t *)qualifier);
-+ sys_acl_free_qualifier(qualifier, race->tag_type);
++ expand_ida_list(idal);
++ ida = &idal->idas[idal->count++];
++ ida->id = *((id_t *)qualifier);
++ ida->access = access;
++ sys_acl_free_qualifier(qualifier, tag_type);
+ }
+ if (rc) {
+ rprintf(FERROR, "unpack_smb_acl: %s(): %s\n",
+ rsync_acl_free(racl);
+ return False;
+ }
-+ sort_rsync_acl(racl);
++
++ sort_ida_list(&racl->users);
++ sort_ida_list(&racl->groups);
++
+ return True;
+}
+
-+static BOOL rsync_acls_equal(const rsync_acl *racl1, const rsync_acl *racl2)
++static BOOL ida_lists_equal(const ida_list *ial1, const ida_list *ial2)
+{
-+ rsync_ace *race1, *race2;
-+ size_t count = racl1->count;
-+ if (count != racl2->count)
++ id_access *ida1, *ida2;
++ size_t count = ial1->count;
++ if (count != ial2->count)
+ return False;
-+ race1 = racl1->races;
-+ race2 = racl2->races;
-+ for (; count--; race1++, race2++) {
-+ if (race1->tag_type != race2->tag_type
-+ || race1->access != race2->access
-+ || ((race1->tag_type == SMB_ACL_USER
-+ || race1->tag_type == SMB_ACL_GROUP)
-+ && race1->id != race2->id))
++ ida1 = ial1->idas;
++ ida2 = ial2->idas;
++ for (; count--; ida1++, ida2++) {
++ if (ida1->access != ida2->access || ida1->id != ida2->id)
+ return False;
+ }
+ return True;
+}
+
++static BOOL rsync_acls_equal(const rsync_acl *racl1, const rsync_acl *racl2)
++{
++ return (racl1->user_obj == racl2->user_obj
++ && racl1->group_obj == racl2->group_obj
++ && racl1->mask == racl2->mask
++ && racl1->other == racl2->other
++ && ida_lists_equal(&racl1->users, &racl2->users)
++ && ida_lists_equal(&racl1->groups, &racl2->groups));
++}
++
++static BOOL rsync_acl_extended_parts_equal(const rsync_acl *racl1, const rsync_acl *racl2)
++{
++ /* We ignore any differences that chmod() can take care of. */
++ if ((racl1->mask ^ racl2->mask) & NO_ENTRY)
++ return False;
++ if (racl1->mask != NO_ENTRY && racl1->group_obj != racl2->group_obj)
++ return False;
++ return ida_lists_equal(&racl1->users, &racl2->users)
++ && ida_lists_equal(&racl1->groups, &racl2->groups);
++}
++
+typedef struct {
+ size_t count;
+ size_t malloced;
+
+static void expand_rsync_acl_list(rsync_acl_list *racl_list)
+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
++ /* 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 0
-+static void free_rsync_acl_list(rsync_acl_list *racl_list)
-+{
-+ /* Run this in reverse, so references are freed before referents,
-+ * although not currently necessary. */
-+ while (racl_list->count--) {
-+ rsync_acl *racl = &racl_list->racls[racl_list->count];
-+ if (racl)
-+ rsync_acl_free(racl);
-+ }
-+ free(racl_list->racls);
-+ racl_list->racls = NULL;
-+ racl_list->malloced = 0;
-+}
-+#endif
-+
+static int find_matching_rsync_acl(SMB_ACL_TYPE_T type,
+ const rsync_acl_list *racl_list,
+ const rsync_acl *racl)
+ * lowercase in this instance means there's no ACL following, so the
+ * ACL is a repeat, so the receiver should reuse the last of the same
+ * type ACL. */
++static void send_ida_list(int f, const ida_list *idal, char tag_char)
++{
++ id_access *ida;
++ size_t count = idal->count;
++ for (ida = idal->idas; count--; ida++) {
++ write_byte(f, tag_char);
++ write_byte(f, ida->access);
++ write_int(f, ida->id);
++ /* FIXME: sorta wasteful: we should maybe buffer as
++ * many ids as max(ACL_USER + ACL_GROUP) objects to
++ * keep from making so many calls. */
++ if (tag_char == 'U')
++ add_uid(ida->id);
++ else
++ add_gid(ida->id);
++ }
++}
+
+static void send_rsync_acl(int f, const rsync_acl *racl)
+{
-+ rsync_ace *race;
-+ size_t count = racl->count;
++ size_t count = rsync_acl_count_entries(racl);
+ write_int(f, count);
-+ for (race = racl->races; count--; race++) {
-+ char ch;
-+ switch (race->tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ ch = 'u';
-+ break;
-+ case SMB_ACL_USER:
-+ ch = 'U';
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ ch = 'g';
-+ break;
-+ case SMB_ACL_GROUP:
-+ ch = 'G';
-+ break;
-+ case SMB_ACL_OTHER:
-+ ch = 'o';
-+ break;
-+ case SMB_ACL_MASK:
-+ ch = 'm';
-+ break;
-+ default:
-+ rprintf(FERROR,
-+ "send_rsync_acl: unknown tag_type (%0x) on ACE; disregarding\n",
-+ race->tag_type);
-+ continue;
-+ }
-+ write_byte(f, ch);
-+ write_byte(f, race->access);
-+ if (isupper((int)ch)) {
-+ write_int(f, race->id);
-+ /* FIXME: sorta wasteful: we should maybe buffer as
-+ * many ids as max(ACL_USER + ACL_GROUP) objects to
-+ * keep from making so many calls. */
-+ if (ch == 'U')
-+ add_uid(race->id);
-+ else
-+ add_gid(race->id);
-+ }
++ if (racl->user_obj != NO_ENTRY) {
++ write_byte(f, 'u');
++ write_byte(f, racl->user_obj);
++ }
++ send_ida_list(f, &racl->users, 'U');
++ if (racl->group_obj != NO_ENTRY) {
++ write_byte(f, 'g');
++ write_byte(f, racl->group_obj);
++ }
++ send_ida_list(f, &racl->groups, 'G');
++ if (racl->mask != NO_ENTRY) {
++ write_byte(f, 'm');
++ write_byte(f, racl->mask);
++ }
++ if (racl->other != NO_ENTRY) {
++ write_byte(f, 'o');
++ write_byte(f, racl->other);
+ }
+}
+
+static rsync_acl _curr_rsync_acls[2];
+
-+
+static const char *str_acl_type(SMB_ACL_TYPE_T type)
+{
+ return type == SMB_ACL_TYPE_ACCESS ? "SMB_ACL_TYPE_ACCESS" :
+
+/* Generate the ACL(s) for this flist entry;
+ * ACL(s) are either sent or cleaned-up by send_acl() below. */
-+
+int make_acl(const struct file_struct *file, const char *fname)
+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
++ SMB_ACL_TYPE_T type;
+ rsync_acl *curr_racl;
-+ if (!preserve_acls || S_ISLNK(file->mode))
++
++ if (S_ISLNK(file->mode))
+ return 1;
-+ for (type = &types[0], curr_racl = &_curr_rsync_acls[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++, curr_racl++) {
++
++ curr_racl = &_curr_rsync_acls[0];
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
+ SMB_ACL_T sacl;
+ BOOL ok;
-+ *curr_racl = rsync_acl_initializer;
-+ if (!(sacl = sys_acl_get_file(fname, *type))) {
++ if ((sacl = sys_acl_get_file(fname, type)) != 0) {
++ ok = unpack_smb_acl(curr_racl, sacl);
++ sys_acl_free_acl(sacl);
++ if (!ok)
++ return -1;
++ /* Strip access ACLs of permission-bit entries. */
++ if (type == SMB_ACL_TYPE_ACCESS)
++ rsync_acl_strip_perms(curr_racl);
++ } else if (errno == ENOTSUP) {
++ /* ACLs are not supported. Leave list empty. */
++ *curr_racl = rsync_acl_initializer;
++ } else {
+ rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n",
-+ fname, str_acl_type(*type), strerror(errno));
++ fname, str_acl_type(type), strerror(errno));
+ return -1;
+ }
-+ ok = unpack_smb_acl(curr_racl, sacl);
-+ sys_acl_free_acl(sacl);
-+ if (!ok)
-+ return -1;
-+ }
++ curr_racl++;
++ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
++
+ return 0;
+}
+
+/* Send the make_acl()-generated ACLs for this flist entry,
+ * or clean up after an flist entry that's not being sent (f == -1). */
-+
+void send_acl(const struct file_struct *file, int f)
+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
++ SMB_ACL_TYPE_T type;
+ rsync_acl *curr_racl;
-+ if (!preserve_acls || S_ISLNK(file->mode))
++
++ if (S_ISLNK(file->mode))
+ return;
-+ for (type = &types[0], curr_racl = &_curr_rsync_acls[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++, curr_racl++) {
++
++ curr_racl = &_curr_rsync_acls[0];
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
+ int index;
-+ rsync_acl_list *racl_list = rsync_acl_lists(*type);
++ 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))
++ if ((index = find_matching_rsync_acl(type, racl_list, curr_racl))
+ != -1) {
-+ write_byte(f, *type == SMB_ACL_TYPE_ACCESS ? 'a' : 'd');
++ 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');
++ 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: */
+
+static void expand_file_acl_index_list(file_acl_index_list *fileaclidx_list)
+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
++ /* First time through, 0 <= 0, so list is expanded. */
+ if (fileaclidx_list->malloced <= fileaclidx_list->count) {
+ file_acl_index *new_ptr;
+ size_t new_size;
+ }
+}
+
-+#if 0
-+static void free_file_acl_index_list(file_acl_index_list *fileaclidx_list)
-+{
-+ free(fileaclidx_list->fileaclidxs);
-+ fileaclidx_list->fileaclidxs = NULL;
-+ fileaclidx_list->malloced = 0;
-+}
-+#endif
-+
+/* lists to hold the SMB_ACL_Ts corresponding to the rsync_acl_list entries */
+
+typedef struct {
+
+static void expand_smb_acl_list(smb_acl_list *sacl_list)
+{
-+ /* First time through, 0 <= 0, so list is expanded.
-+ * (Diabolical, rsync guys!) */
++ /* 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 0
-+static void free_smb_acl_list(SMB_ACL_TYPE_T type)
++#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)
+{
-+ smb_acl_list *sacl_list = smb_acl_lists(type);
-+ SMB_ACL_T *sacl = sacl_list->sacls;
-+ while (sacl_list->count--) {
-+ if (*sacl)
-+ sys_acl_free_acl(*sacl++);
-+ }
-+ free(sacl_list->sacls);
-+ sacl_list->sacls = NULL;
-+ sacl_list->malloced = 0;
++ const char *errfun = NULL;
++ SMB_ACL_PERMSET_T permset;
++
++ COE( sys_acl_get_permset,(entry, &permset) );
++ COE( sys_acl_clear_perms,(permset) );
++ if (access & 4)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_READ) );
++ if (access & 2)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_WRITE) );
++ if (access & 1)
++ COE( sys_acl_add_perm,(permset, SMB_ACL_EXECUTE) );
++ COE( sys_acl_set_permset,(entry, permset) );
++
++ return 0;
++
++ error_exit:
++ rprintf(FERROR, "store_access_in_entry %s(): %s\n", errfun,
++ strerror(errno));
++ return -1;
+}
-+#endif
+
+/* build an SMB_ACL_T corresponding to an rsync_acl */
+static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl)
+{
-+ size_t count = racl->count;
-+ rsync_ace *race = racl->races;
++ size_t count;
++ id_access *ida;
+ const char *errfun = NULL;
-+ *smb_acl = sys_acl_init(count);
-+ if (!*smb_acl) {
-+ rprintf(FERROR, "pack_smb_acl: sys_acl_int(): %s\n",
++ SMB_ACL_ENTRY_T entry;
++
++ if (!(*smb_acl = sys_acl_init(rsync_acl_count_entries(racl)))) {
++ rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n",
+ strerror(errno));
+ return False;
+ }
-+ for (; count--; race++) {
-+ SMB_ACL_ENTRY_T entry;
-+ SMB_ACL_PERMSET_T permset;
-+ if (sys_acl_create_entry(smb_acl, &entry)) {
-+ errfun = "sys_acl_create)";
-+ break;
-+ }
-+ if (sys_acl_set_tag_type(entry, race->tag_type)) {
-+ errfun = "sys_acl_set_tag";
-+ break;
-+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP)
-+ if (sys_acl_set_qualifier(entry, (void*)&race->id)) {
-+ errfun = "sys_acl_set_qualfier";
-+ break;
-+ }
-+ if (sys_acl_get_permset(entry, &permset)) {
-+ errfun = "sys_acl_get_permset";
-+ break;
-+ }
-+ if (sys_acl_clear_perms(permset)) {
-+ errfun = "sys_acl_clear_perms";
-+ break;
-+ }
-+ if (race->access & 4)
-+ if (sys_acl_add_perm(permset, SMB_ACL_READ)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (race->access & 2)
-+ if (sys_acl_add_perm(permset, SMB_ACL_WRITE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (race->access & 1)
-+ if (sys_acl_add_perm(permset, SMB_ACL_EXECUTE)) {
-+ errfun = "sys_acl_add_perm";
-+ break;
-+ }
-+ if (sys_acl_set_permset(entry, permset)) {
-+ errfun = "sys_acl_set_permset";
-+ break;
-+ }
++
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_USER_OBJ) );
++ COE2( store_access_in_entry,(racl->user_obj, entry) );
++
++ 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, 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) );
++ }
++ 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) );
+ }
++
++ COE( sys_acl_create_entry,(smb_acl, &entry) );
++ COE( sys_acl_set_tag_type,(entry, SMB_ACL_OTHER) );
++ COE2( store_access_in_entry,(racl->other, entry) );
++
++#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) {
-+ sys_acl_free_acl(*smb_acl);
+ rprintf(FERROR, "pack_smb_acl %s(): %s\n", errfun,
+ strerror(errno));
-+ return False;
+ }
-+ return True;
++ sys_acl_free_acl(*smb_acl);
++ return False;
++}
++
++static mode_t change_sacl_perms(SMB_ACL_T sacl, uchar mask, mode_t old_mode, mode_t mode)
++{
++ SMB_ACL_ENTRY_T entry;
++ int group_id = mask != NO_ENTRY ? SMB_ACL_MASK : SMB_ACL_GROUP_OBJ;
++ const char *errfun;
++ int rc;
++
++ if (S_ISDIR(mode)) {
++ /* If the sticky bit is going on, it's not safe to allow all
++ * the new ACLs to go into effect before it gets set. */
++ if (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;
++ }
++
++ errfun = "sys_acl_get_entry";
++ for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry);
++ rc == 1;
++ rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) {
++ SMB_ACL_TAG_T tag_type;
++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) {
++ errfun = "sys_acl_get_tag_type";
++ break;
++ }
++ if (tag_type == SMB_ACL_USER_OBJ)
++ COE2( store_access_in_entry,((mode >> 6) & 7, entry) );
++ else if (tag_type == group_id)
++ COE2( store_access_in_entry,((mode >> 3) & 7, entry) );
++ else if (tag_type == SMB_ACL_OTHER)
++ COE2( store_access_in_entry,(mode & 7, entry) );
++ }
++ if (rc) {
++ error_exit:
++ if (errfun) {
++ rprintf(FERROR, "change_sacl_perms: %s(): %s\n",
++ errfun, strerror(errno));
++ }
++ return ~0u;
++ }
++
++ /* 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)
+{
-+#if ACLS_NEED_MASK
-+ uchar required_mask_perm = 0;
-+#endif
-+ BOOL saw_mask = False;
-+ BOOL saw_user_obj = False, saw_group_obj = False,
-+ saw_other = False;
-+ size_t count = read_int(f);
-+ rsync_ace *race;
-+ if (!count)
++ uchar computed_mask_bits = 0;
++ ida_list *idal = NULL;
++ id_access *ida;
++ size_t count;
++
++ *racl = rsync_acl_initializer;
++
++ if (!(count = read_int(f)))
+ return;
++
+ while (count--) {
-+ uchar tag = read_byte(f);
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
++ char tag = read_byte(f);
++ uchar access = read_byte(f);
++ if (access & ~ (4 | 2 | 1)) {
++ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
++ access);
++ exit_cleanup(RERR_STREAMIO);
++ }
+ switch (tag) {
+ case 'u':
-+ race->tag_type = SMB_ACL_USER_OBJ;
-+ saw_user_obj = True;
-+ break;
++ if (racl->user_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate USER_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->user_obj = access;
++ continue;
+ case 'U':
-+ race->tag_type = SMB_ACL_USER;
++ idal = &racl->users;
+ break;
+ case 'g':
-+ race->tag_type = SMB_ACL_GROUP_OBJ;
-+ saw_group_obj = True;
-+ break;
++ if (racl->group_obj != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate GROUP_OBJ entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->group_obj = access;
++ continue;
+ case 'G':
-+ race->tag_type = SMB_ACL_GROUP;
-+ break;
-+ case 'o':
-+ race->tag_type = SMB_ACL_OTHER;
-+ saw_other = True;
++ idal = &racl->groups;
+ break;
+ case 'm':
-+ race->tag_type = SMB_ACL_MASK;
-+ saw_mask = True;
-+ break;
++ if (racl->mask != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate MASK entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->mask = access;
++ continue;
++ case 'o':
++ if (racl->other != NO_ENTRY) {
++ rprintf(FERROR, "receive_rsync_acl: error: duplicate OTHER entry\n");
++ exit_cleanup(RERR_STREAMIO);
++ }
++ racl->other = access;
++ continue;
+ default:
+ rprintf(FERROR, "receive_rsync_acl: unknown tag %c\n",
+ tag);
+ exit_cleanup(RERR_STREAMIO);
+ }
-+ race->access = read_byte(f);
-+ if (race->access & ~ (4 | 2 | 1)) {
-+ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n",
-+ race->access);
-+ exit_cleanup(RERR_STREAMIO);
-+ }
-+ if (race->tag_type == SMB_ACL_USER ||
-+ race->tag_type == SMB_ACL_GROUP) {
-+ race->id = read_int(f);
-+#if ACLS_NEED_MASK
-+ required_mask_perm |= race->access;
-+#endif
-+ }
-+#if ACLS_NEED_MASK
-+ else if (race->tag_type == SMB_ACL_GROUP_OBJ)
-+ required_mask_perm |= race->access;
-+#endif
-+
-+ }
-+ if (!saw_user_obj) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_USER_OBJ;
-+ race->access = 7;
-+ }
-+ if (!saw_group_obj) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_GROUP_OBJ;
-+ race->access = 0;
-+ }
-+ if (!saw_other) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_OTHER;
-+ race->access = 0;
-+ }
-+#if ACLS_NEED_MASK
-+ if (!saw_mask) {
-+ expand_rsync_acl(racl);
-+ race = &racl->races[racl->count++];
-+ race->tag_type = SMB_ACL_MASK;
-+ race->access = required_mask_perm;
-+ }
-+#else
-+ /* If we, a system without ACLS_NEED_MASK, received data from a
-+ * system that has masks, throw away the extraneous CLASS_OBJs. */
-+ if (saw_mask && racl->count == 4) {
-+ rsync_ace *group_obj_race = NULL, *mask_race = NULL;
-+ rsync_ace *p;
-+ size_t i;
-+ for (i = 0, p = racl->races; i < racl->count; i++, p++) {
-+ if (p->tag_type == SMB_ACL_MASK)
-+ mask_race = p;
-+ else if (p->tag_type == SMB_ACL_GROUP_OBJ)
-+ group_obj_race = p;
-+ }
-+ if (mask_race == NULL || group_obj_race == NULL) {
-+ rprintf(FERROR, "receive_rsync_acl: have four ACES "
-+ "and one's ACL_MASK but missing "
-+ "either it or ACL_GROUP_OBJ, "
-+ "when pruning ACL\n");
-+ } else {
++ expand_ida_list(idal);
++ ida = &idal->idas[idal->count++];
++ ida->access = access;
++ ida->id = read_int(f);
++ computed_mask_bits |= access;
++ }
++
++ /* Ensure that these are never unset. */
++ if (racl->user_obj == NO_ENTRY)
++ racl->user_obj = 7;
++ if (racl->group_obj == NO_ENTRY)
++ racl->group_obj = 0;
++ if (racl->other == NO_ENTRY)
++ racl->other = 0;
++#ifndef ACLS_NEED_MASK
++ if (!racl->users.count && !racl->groups.count) {
++ /* If we, a system without ACLS_NEED_MASK, received a
++ * superfluous mask, throw it away. */
++ if (racl->mask != NO_ENTRY) {
+ /* mask off group perms with it first */
-+ group_obj_race->access &= mask_race->access;
-+ /* dump mask_race; re-slot any followers-on */
-+ racl->count--;
-+ if (mask_race != &racl->races[racl->count]) {
-+ *mask_race = racl->races[racl->count];
-+ saw_user_obj = False; /* force re-sort */
-+ }
++ racl->group_obj &= racl->mask;
++ racl->mask = NO_ENTRY;
+ }
-+ }
-+#endif
-+#if ACLS_NEED_MASK
-+ if (!(saw_user_obj && saw_group_obj && saw_other && saw_mask))
-+#else
-+ if (!(saw_user_obj && saw_group_obj && saw_other))
++ } else
+#endif
-+ sort_rsync_acl(racl);
++ if (racl->mask == NO_ENTRY)
++ racl->mask = computed_mask_bits | racl->group_obj;
+}
+
+/* receive and build the rsync_acl_lists */
-+
+void receive_acl(struct file_struct *file, int f)
+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
++ SMB_ACL_TYPE_T type;
+ char *fname;
-+ if (!preserve_acls || S_ISLNK(file->mode))
++
++ if (S_ISLNK(file->mode))
+ return;
++
+ fname = f_name(file, NULL);
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++) {
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(*type);
-+ uchar tag;
++ file_acl_index_lists(type);
++ char tag;
+ expand_file_acl_index_list(fileaclidx_list);
+
+ tag = read_byte(f);
+ if (tag == 'A' || tag == 'a') {
-+ if (*type != SMB_ACL_TYPE_ACCESS) {
++ if (type != SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: duplicate access ACL\n",
+ fname);
+ exit_cleanup(RERR_STREAMIO);
+ }
+ } else if (tag == 'D' || tag == 'd') {
-+ if (*type == SMB_ACL_TYPE_ACCESS) {
++ if (type == SMB_ACL_TYPE_ACCESS) {
+ rprintf(FERROR, "receive_acl %s: expecting access ACL; got default\n",
+ fname);
+ exit_cleanup(RERR_STREAMIO);
+ exit_cleanup(RERR_STREAMIO);
+ }
+ if (tag == 'A' || tag == 'D') {
-+ rsync_acl racl = rsync_acl_initializer;
-+ rsync_acl_list *racl_list = rsync_acl_lists(*type);
-+ smb_acl_list *sacl_list = smb_acl_lists(*type);
++ rsync_acl racl;
++ rsync_acl_list *racl_list = rsync_acl_lists(type);
++ smb_acl_list *sacl_list = smb_acl_lists(type);
+ fileaclidx_list->fileaclidxs[fileaclidx_list->count].
+ aclidx = racl_list->count;
+ fileaclidx_list->fileaclidxs[fileaclidx_list->count++].
+ sacl_list->sacls[sacl_list->count++] = NULL;
+ } else {
+ int index = read_int(f);
-+ rsync_acl_list *racl_list = rsync_acl_lists(*type);
++ rsync_acl_list *racl_list = rsync_acl_lists(type);
+ if ((size_t) index >= racl_list->count) {
+ rprintf(FERROR, "receive_acl %s: %s ACL index %d out of range\n",
+ fname,
-+ str_acl_type(*type),
++ str_acl_type(type),
+ index);
+ exit_cleanup(RERR_STREAMIO);
+ }
+ fileaclidx_list->fileaclidxs[fileaclidx_list->count++].
+ file = file;
+ }
-+ }
++ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
+}
+
+static int file_acl_index_list_sorter(const void *f1, const void *f2)
+
+void sort_file_acl_index_lists()
+{
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ if (!preserve_acls)
-+ return;
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++)
-+ {
++ SMB_ACL_TYPE_T type;
++
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
+ file_acl_index_list *fileaclidx_list =
-+ file_acl_index_lists(*type);
++ file_acl_index_lists(type);
+ if (!fileaclidx_list->count)
+ continue;
+ qsort(fileaclidx_list->fileaclidxs, fileaclidx_list->count,
+ sizeof fileaclidx_list->fileaclidxs[0],
+ &file_acl_index_list_sorter);
-+ }
++ } while (BUMP_TYPE(type));
+}
+
+static int find_file_acl_index(const file_acl_index_list *fileaclidx_list,
+}
+
+/* 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,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
++ SMB_ACL_TYPE_T type;
+ int ret = 0;
-+ if (!preserve_acls)
-+ return 1;
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(mode));
-+ type++) {
++
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
+ SMB_ACL_T sacl_orig, sacl_bak;
+ rsync_acl racl_orig, racl_bak;
-+ if (!(sacl_orig = sys_acl_get_file(orig, *type))) {
++ 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));
++ orig, str_acl_type(type), strerror(errno));
+ ret = -1;
+ continue;
+ }
-+ if (!(sacl_bak = sys_acl_get_file(orig, *type))) {
++ 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));
++ bak, str_acl_type(type), strerror(errno));
+ ret = -1;
+ /* try to forge on through */
+ }
+ } else {
+ ; /* presume they're unequal */
+ }
-+ if (*type == SMB_ACL_TYPE_DEFAULT && !racl_orig.count) {
-+ if (-1 == sys_acl_delete_def_file(bak)) {
++ if (type == SMB_ACL_TYPE_DEFAULT
++ && rsync_acl_count_entries(&racl_orig) == 0) {
++ if (sys_acl_delete_def_file(bak) < 0) {
+ rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n",
+ bak, strerror(errno));
+ ret = -1;
+ }
-+ } else if (-1 == sys_acl_set_file(bak, *type, sacl_bak)) {
++ } 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));
++ bak, str_acl_type(type), strerror(errno));
+ ret = -1;
+ }
+ out_with_all:
+ /* out_with_one_sacl: */
+ if (sacl_orig)
+ sys_acl_free_acl(sacl_orig);
-+ }
++ } while (BUMP_TYPE(type) && S_ISDIR(mode));
++
+ return ret;
+}
+
+void push_keep_backup_acl(const struct file_struct *file,
+ const char *orig, const char *dest)
+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ backup_orig_file = file;
-+ backup_orig_fname = orig;
-+ backup_dest_fname = dest;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode))
-+ *sacl = NULL;
-+ else {
-+ if (!(*sacl = sys_acl_get_file(orig, *type))) {
-+ rprintf(FERROR, "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ orig, str_acl_type(*type),
-+ strerror(errno));
-+ }
-+ }
++ SMB_ACL_TYPE_T type;
++ SMB_ACL_T *sacl;
++
++ backup_orig_file = file;
++ backup_orig_fname = orig;
++ backup_dest_fname = dest;
++
++ sacl = &_backup_sacl[0];
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
++ if (type == SMB_ACL_TYPE_DEFAULT && !S_ISDIR(file->mode)) {
++ *sacl = NULL;
++ break;
+ }
-+ }
++ if (!(*sacl = sys_acl_get_file(orig, type))) {
++ rprintf(FERROR,
++ "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
++ orig, str_acl_type(type),
++ strerror(errno));
++ }
++ } while (BUMP_TYPE(type));
+}
+
+static int set_keep_backup_acl()
+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ int ret = 0;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*sacl) {
-+ if (-1 == sys_acl_set_file(backup_dest_fname,
-+ *type, *sacl))
-+ {
-+ rprintf(FERROR, "push_keep_backup_acl: sys_acl_get_file(%s, %s): %s\n",
-+ backup_dest_fname,
-+ str_acl_type(*type),
-+ strerror(errno));
-+ ret = -1;
-+ }
-+ }
++ 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;
+ }
-+ return ret;
-+ }
-+ return 1;
++ } while (BUMP_TYPE(type));
++
++ return ret;
+}
+
+void cleanup_keep_backup_acl()
+{
-+ if (preserve_acls) {
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ SMB_ACL_T *sacl;
-+ backup_orig_file = NULL;
-+ backup_orig_fname = null_string;
-+ backup_dest_fname = null_string;
-+ for (type = &types[0], sacl = &_backup_sacl[0];
-+ type < &types[0] + sizeof types / sizeof types[0];
-+ type++) {
-+ if (*sacl)
-+ sys_acl_free_acl(*sacl);
++ SMB_ACL_TYPE_T type;
++ SMB_ACL_T *sacl;
++
++ backup_orig_file = NULL;
++ backup_orig_fname = null_string;
++ backup_dest_fname = null_string;
++
++ sacl = &_backup_sacl[0];
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
++ if (*sacl) {
++ sys_acl_free_acl(*sacl);
+ *sacl = NULL;
+ }
-+ }
++ } while (BUMP_TYPE(type));
+}
+
-+/* set ACL on rsync-ed or keep_backup-ed file */
-+
-+int set_acl(const char *fname, const struct file_struct *file)
++/* set ACL on rsync-ed or keep_backup-ed file
++ *
++ * This sets extended access ACL entries and default ACLs. If convenient,
++ * it sets permission bits along with the access ACLs and signals having
++ * done so by modifying p_mode, which should point into the stat buffer.
++ *
++ * returns: 1 for unchanged, 0 for changed, -1 for failed
++ * Pass NULL for p_mode to get the return code without changing anything. */
++int set_acl(const char *fname, const struct file_struct *file, mode_t *p_mode)
+{
-+ int updated = 0;
-+ SMB_ACL_TYPE_T *type,
-+ types[] = {SMB_ACL_TYPE_ACCESS, SMB_ACL_TYPE_DEFAULT};
-+ if (dry_run || !preserve_acls || S_ISLNK(file->mode))
++ int unchanged = 1;
++ SMB_ACL_TYPE_T type;
++
++ if (S_ISLNK(file->mode))
+ return 1;
++
+ if (file == backup_orig_file) {
+ if (!strcmp(fname, backup_dest_fname))
+ return set_keep_backup_acl();
+ }
-+ for (type = &types[0];
-+ type < &types[0] + sizeof types / sizeof types[0]
-+ && (*type == SMB_ACL_TYPE_ACCESS || S_ISDIR(file->mode));
-+ type++) {
++ type = SMB_ACL_TYPE_ACCESS;
++ do {
++ BOOL ok;
+ SMB_ACL_T sacl_orig, *sacl_new;
+ rsync_acl racl_orig, *racl_new;
-+ int aclidx = find_file_acl_index(file_acl_index_lists(*type),
-+ file);
-+ BOOL ok;
-+ racl_new = &(rsync_acl_lists(*type)->racls[aclidx]);
-+ sacl_new = &(smb_acl_lists(*type)->sacls[aclidx]);
-+ sacl_orig = sys_acl_get_file(fname, *type);
++ 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));
-+ updated = -1;
++ 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) {
-+ updated = -1;
++ unchanged = -1;
+ continue;
+ }
-+ ok = rsync_acls_equal(&racl_orig, racl_new);
++ if (type == SMB_ACL_TYPE_ACCESS)
++ ok = rsync_acl_extended_parts_equal(&racl_orig, racl_new);
++ else
++ ok = rsync_acls_equal(&racl_orig, racl_new);
+ rsync_acl_free(&racl_orig);
+ if (ok)
+ continue;
-+ if (*type == SMB_ACL_TYPE_DEFAULT && !racl_new->count) {
-+ if (-1 == sys_acl_delete_def_file(fname)) {
-+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
-+ fname, strerror(errno));
-+ updated = -1;
-+ continue;
-+ }
-+ } else {
-+ if (!*sacl_new)
-+ if (!pack_smb_acl(sacl_new, racl_new)) {
-+ updated = -1;
++ if (!dry_run && p_mode) {
++ if (type == SMB_ACL_TYPE_DEFAULT
++ && rsync_acl_count_entries(racl_new) == 0) {
++ if (sys_acl_delete_def_file(fname) < 0) {
++ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n",
++ fname, strerror(errno));
++ unchanged = -1;
++ continue;
++ }
++ } else {
++ mode_t cur_mode = *p_mode;
++ if (!*sacl_new
++ && !pack_smb_acl(sacl_new, racl_new)) {
++ unchanged = -1;
+ continue;
+ }
-+ if (-1 == sys_acl_set_file(fname, *type, *sacl_new)) {
-+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
-+ fname, str_acl_type(*type),
-+ strerror(errno));
-+ updated = -1;
-+ continue;
++ if (type == SMB_ACL_TYPE_ACCESS) {
++ cur_mode = change_sacl_perms(*sacl_new, racl_new->mask,
++ cur_mode, file->mode);
++ if (cur_mode == ~0u)
++ continue;
++ }
++ if (sys_acl_set_file(fname, type, *sacl_new) < 0) {
++ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n",
++ fname, str_acl_type(type),
++ strerror(errno));
++ unchanged = -1;
++ continue;
++ }
++ if (type == SMB_ACL_TYPE_ACCESS)
++ *p_mode = cur_mode;
+ }
+ }
-+ if (!updated)
-+ updated = 1;
-+ }
-+ return updated;
++ if (unchanged == 1)
++ unchanged = 0;
++ } while (BUMP_TYPE(type) && S_ISDIR(file->mode));
++
++ return unchanged;
+}
+
+/* Enumeration functions for uid mapping: */
+
+static rsync_acl_list **enum_racl_list = &_enum_racl_lists[0];
+static size_t enum_racl_index = 0;
-+static size_t enum_race_index = 0;
++static size_t enum_ida_index = 0;
+
+/* This returns the next tag_type id from the given acl for the next entry,
+ * or it returns 0 if there are no more tag_type ids in the acl. */
-+
-+static id_t next_ace_id(SMB_ACL_TAG_T tag_type, const rsync_acl *racl)
++static id_t *next_ace_id(SMB_ACL_TAG_T tag_type, const rsync_acl *racl)
+{
-+ for (; enum_race_index < racl->count; enum_race_index++) {
-+ rsync_ace *race = &racl->races[enum_race_index];
-+ if (race->tag_type == tag_type)
-+ return race->id;
++ const ida_list *idal = (tag_type == SMB_ACL_USER ?
++ &racl->users : &racl->groups);
++ if (enum_ida_index < idal->count) {
++ id_access *ida = &idal->idas[enum_ida_index++];
++ return &ida->id;
+ }
-+ enum_race_index = 0;
-+ return 0;
++ enum_ida_index = 0;
++ return NULL;
+}
+
-+static id_t next_acl_id(SMB_ACL_TAG_T tag_type, const rsync_acl_list *racl_list)
++static id_t *next_acl_id(SMB_ACL_TAG_T tag_type, const rsync_acl_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);
++ id_t *id = next_ace_id(tag_type, racl);
+ if (id)
+ return id;
+ }
+ enum_racl_index = 0;
-+ return 0;
++ return NULL;
+}
+
-+static id_t next_acl_list_id(SMB_ACL_TAG_T tag_type)
++static id_t *next_acl_list_id(SMB_ACL_TAG_T tag_type)
+{
+ for (; *enum_racl_list; enum_racl_list++) {
-+ id_t id = next_acl_id(tag_type, *enum_racl_list);
++ id_t *id = next_acl_id(tag_type, *enum_racl_list);
+ if (id)
+ return id;
+ }
+ enum_racl_list = &_enum_racl_lists[0];
-+ return 0;
++ return NULL;
+}
+
-+id_t next_acl_uid()
++id_t *next_acl_uid()
+{
+ return next_acl_list_id(SMB_ACL_USER);
+}
+
-+id_t next_acl_gid()
++id_t *next_acl_gid()
+{
+ return next_acl_list_id(SMB_ACL_GROUP);
+}
+
-+/* referring to the global context enum_entry, sets the entry's id */
-+static void set_acl_id(id_t id)
-+{
-+ (*enum_racl_list)->racls[enum_racl_index].races[enum_race_index++].id = id;
-+}
-+
-+void acl_uid_map(id_t uid)
-+{
-+ set_acl_id(uid);
-+}
-+
-+void acl_gid_map(id_t gid)
-+{
-+ set_acl_id(gid);
-+}
-+
-+#define PERMS_SPLICE(perms,newbits,where) (((perms) & ~(7 << (where))) | ((newbits) << (where)))
-+
+int default_perms_for_dir(const char *dir)
+{
+ rsync_acl racl;
+ SMB_ACL_T sacl;
-+ BOOL ok, saw_mask = False;
-+ size_t i;
++ BOOL ok;
+ int perms;
+
+ if (dir == NULL)
+ return perms;
+ }
+
-+ /* Look at each default ACL entry and possibly modify three bits of `perms' accordingly.
-+ * If there's "no" default ACL, there will be zero entries and the umask-based perms is unchanged. */
-+ for (i = 0; i < racl.count; i++) {
-+ switch (racl.races[i].tag_type) {
-+ case SMB_ACL_USER_OBJ:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 6);
-+ break;
-+ case SMB_ACL_GROUP_OBJ:
-+ if (!saw_mask)
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_MASK:
-+ saw_mask = True;
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 3);
-+ break;
-+ case SMB_ACL_OTHER:
-+ perms = PERMS_SPLICE(perms, racl.races[i].access, 0);
-+ break;
-+ default:
-+ break;
-+ }
++ /* Apply the permission-bit entries of the default ACL, if any. */
++ if (rsync_acl_count_entries(&racl) > 0) {
++ perms = rsync_acl_get_perms(&racl);
++ if (verbose > 2)
++ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ }
++
+ rsync_acl_free(&racl);
-+ if (verbose > 2)
-+ rprintf(FINFO, "got ACL-based default perms %o for directory %s\n", perms, dir);
+ return perms;
+}
+
+#endif /* SUPPORT_ACLS */
--- old/backup.c
+++ new/backup.c
-@@ -134,6 +134,7 @@ static int make_bak_dir(char *fullpath)
+@@ -28,6 +28,7 @@ extern char *backup_suffix;
+ extern char *backup_dir;
+
+ extern int am_root;
++extern int preserve_acls;
+ extern int preserve_devices;
+ extern int preserve_specials;
+ extern int preserve_links;
+@@ -132,6 +133,10 @@ static int make_bak_dir(char *fullpath)
} else {
do_lchown(fullpath, st.st_uid, st.st_gid);
do_chmod(fullpath, st.st_mode);
-+ (void)DUP_ACL(end, fullpath, st.st_mode);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ dup_acl(end, fullpath, st.st_mode);
++#endif
}
}
*p = '/';
-@@ -187,6 +188,8 @@ static int keep_backup(char *fname)
+@@ -185,6 +190,11 @@ static int keep_backup(char *fname)
if (!(buf = get_backup_name(fname)))
return 0;
-+ PUSH_KEEP_BACKUP_ACL(file, fname, buf);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ push_keep_backup_acl(file, fname, buf);
++#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))) {
-@@ -262,6 +265,7 @@ static int keep_backup(char *fname)
+@@ -260,6 +270,10 @@ static int keep_backup(char *fname)
}
}
set_file_attrs(buf, file, NULL, 0);
-+ CLEANUP_KEEP_BACKUP_ACL();
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ cleanup_keep_backup_acl();
++#endif
free(file);
if (verbose > 1) {
--- old/flist.c
+++ new/flist.c
-@@ -967,6 +967,8 @@ static struct file_struct *send_file_nam
- f == -2 ? SERVER_FILTERS : ALL_FILTERS);
- if (!file)
- return NULL;
-+ if (MAKE_ACL(file, fname) < 0)
-+ return NULL;
-
+@@ -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;
+@@ -970,6 +971,11 @@ static struct file_struct *send_file_nam
if (chmod_modes && !S_ISLNK(file->mode))
file->mode = tweak_mode(file->mode, chmod_modes);
-@@ -978,6 +980,10 @@ static struct file_struct *send_file_nam
+
++#ifdef SUPPORT_ACLS
++ if (preserve_acls && make_acl(file, fname) < 0)
++ return NULL;
++#endif
++
+ maybe_emit_filelist_progress(flist->count + flist_count_offset);
+
+ flist_expand(flist);
+@@ -977,6 +983,16 @@ static struct file_struct *send_file_nam
if (file->basename[0]) {
flist->files[flist->count++] = file;
send_file_entry(file, f);
-+ SEND_ACL(file, f);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ send_acl(file, f);
++#endif
+ } else {
++#ifdef SUPPORT_ACLS
+ /* Cleanup unsent ACL(s). */
-+ SEND_ACL(file, -1);
++ if (preserve_acls)
++ send_acl(file, -1);
++#endif
}
return file;
}
-@@ -1366,6 +1372,8 @@ struct file_list *recv_file_list(int f)
+@@ -1365,6 +1381,11 @@ struct file_list *recv_file_list(int f)
flags |= read_byte(f) << 8;
file = receive_file_entry(flist, flags, f);
-+ RECEIVE_ACL(file, f);
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ receive_acl(file, f);
++#endif
+
if (S_ISREG(file->mode) || S_ISLNK(file->mode))
stats.total_size += file->length;
-@@ -1388,6 +1396,8 @@ struct file_list *recv_file_list(int f)
+@@ -1387,6 +1408,11 @@ struct file_list *recv_file_list(int f)
clean_flist(flist, relative_paths, 1);
-+ SORT_FILE_ACL_INDEX_LISTS();
++#ifdef SUPPORT_ACLS
++ if (preserve_acls)
++ sort_file_acl_index_lists();
++#endif
+
if (f >= 0) {
recv_uid_list(f, flist);
--- old/generator.c
+++ new/generator.c
-@@ -86,6 +86,7 @@ extern long block_size; /* "long" becaus
+@@ -85,6 +85,7 @@ extern long block_size; /* "long" becaus
extern int max_delete;
extern int force_delete;
extern int one_file_system;
extern struct stats stats;
extern dev_t filesystem_dev;
extern char *backup_dir;
-@@ -754,6 +755,7 @@ static int try_dests_non(struct file_str
+@@ -321,6 +322,8 @@ static void do_delete_pass(struct file_l
+
+ int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st)
+ {
++ /* FIXME: Flag ACL changes. */
++
+ if (preserve_perms
+ && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS))
+ return 0;
+@@ -355,6 +358,7 @@ void itemize(struct file_struct *file, i
+ if (preserve_gid && file->gid != GID_NONE
+ && st->st_gid != file->gid)
+ iflags |= ITEM_REPORT_GROUP;
++ /* FIXME: Itemize ACL changes. ITEM_REPORT_XATTR? */
+ } else
+ iflags |= ITEM_IS_NEW;
+
+@@ -753,6 +757,7 @@ static int try_dests_non(struct file_str
}
static int phase = 0;
/* Acts on the_file_list->file's ndx'th item, whose name is fname. If a dir,
* make sure it exists, and has the right permissions/timestamp info. For
-@@ -844,6 +846,10 @@ static void recv_generator(char *fname,
+@@ -844,6 +849,10 @@ static void recv_generator(char *fname,
}
if (fuzzy_basis)
need_fuzzy_dirlist = 1;
}
parent_dirname = dn;
-@@ -871,7 +877,8 @@ static void recv_generator(char *fname,
+@@ -871,7 +880,8 @@ static void recv_generator(char *fname,
if (!preserve_perms) {
int exists = statret == 0
&& S_ISDIR(st.st_mode) == S_ISDIR(file->mode);
}
if (S_ISDIR(file->mode)) {
-@@ -905,6 +912,10 @@ static void recv_generator(char *fname,
- if (set_file_attrs(fname, file, statret ? NULL : &st, 0)
- && verbose && code && f_out != -1)
- rprintf(code, "%s/\n", fname);
-+#ifdef SUPPORT_ACLS
-+ if (f_out == -1)
-+ SET_ACL(fname, file);
-+#endif
- if (delete_during && f_out != -1 && !phase && dry_run < 2
- && (file->flags & FLAG_DEL_HERE))
- delete_in_dir(the_file_list, fname, file, &st);
-@@ -1342,6 +1353,8 @@ void generate_files(int f_out, struct fi
+@@ -1343,6 +1353,8 @@ void generate_files(int f_out, struct fi
* notice that and let us know via the redo pipe (or its closing). */
ignore_timeout = 1;
--- old/options.c
+++ new/options.c
-@@ -44,6 +44,7 @@ int keep_dirlinks = 0;
+@@ -45,6 +45,7 @@ int copy_dirlinks = 0;
int copy_links = 0;
int preserve_links = 0;
int preserve_hard_links = 0;
int preserve_perms = 0;
int preserve_executability = 0;
int preserve_devices = 0;
-@@ -193,6 +194,7 @@ static void print_rsync_version(enum log
+@@ -194,6 +195,7 @@ static void print_rsync_version(enum log
char const *got_socketpair = "no ";
char const *have_inplace = "no ";
char const *hardlinks = "no ";
char const *links = "no ";
char const *ipv6 = "no ";
STRUCT_STAT *dumstat;
-@@ -209,6 +211,10 @@ static void print_rsync_version(enum log
+@@ -210,6 +212,10 @@ static void print_rsync_version(enum log
hardlinks = "";
#endif
#ifdef SUPPORT_LINKS
links = "";
#endif
-@@ -222,9 +228,9 @@ static void print_rsync_version(enum log
+@@ -223,9 +229,9 @@ static void print_rsync_version(enum log
rprintf(f, "Copyright (C) 1996-2006 by Andrew Tridgell, Wayne Davison, and others.\n");
rprintf(f, "<http://rsync.samba.org/>\n");
rprintf(f, "Capabilities: %d-bit files, %ssocketpairs, "
/* 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," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
+@@ -295,6 +301,9 @@ void usage(enum logcode F)
+ rprintf(F," -H, --hard-links preserve hard links\n");
rprintf(F," -p, --perms preserve permissions\n");
rprintf(F," -E, --executability preserve the file's executability\n");
+#ifdef SUPPORT_ACLS
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[]
+@@ -410,6 +419,9 @@ static struct poptOption long_options[]
{"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
{"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
{"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
{"times", 't', POPT_ARG_VAL, &preserve_times, 1, 0, 0 },
{"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
{"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
-@@ -1062,6 +1074,23 @@ int parse_arguments(int *argc, const cha
+@@ -1068,6 +1080,23 @@ int parse_arguments(int *argc, const cha
usage(FINFO);
exit_cleanup(0);
default:
/* A large opt value means that set_refuse_options()
* turned this option off. */
-@@ -1502,6 +1531,10 @@ void server_options(char **args,int *arg
+@@ -1511,6 +1540,10 @@ void server_options(char **args,int *arg
if (preserve_hard_links)
argstr[x++] = 'H';
if (preserve_gid)
--- old/receiver.c
+++ new/receiver.c
-@@ -50,6 +50,7 @@ extern int keep_partial;
+@@ -46,6 +46,7 @@ extern int keep_partial;
extern int checksum_seed;
extern int inplace;
extern int delay_updates;
extern struct stats stats;
extern char *log_format;
extern char *tmpdir;
-@@ -348,6 +349,10 @@ int recv_files(int f_in, struct file_lis
+@@ -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;
int max_phase = protocol_version >= 29 ? 2 : 1;
int i, recv_ok;
if (verbose > 2)
-@@ -545,7 +550,16 @@ int recv_files(int f_in, struct file_lis
+@@ -541,7 +546,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;
/* We now check to see if we are writing file "inplace" */
--- old/rsync.c
+++ new/rsync.c
-@@ -101,7 +101,8 @@ void free_sums(struct sum_struct *s)
+@@ -33,6 +33,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)
/* 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. */
{
/* If the file already exists, we'll return the local permissions,
* possibly tweaked by the --executability option. */
-@@ -116,7 +117,7 @@ mode_t dest_mode(mode_t flist_mode, mode
+@@ -116,7 +118,7 @@ mode_t dest_mode(mode_t flist_mode, mode
cur_mode |= (cur_mode & 0444) >> 2;
}
} else
- cur_mode = flist_mode & ACCESSPERMS & ~orig_umask;
-+ cur_mode = (flist_mode & ACCESSPERMS & dflt_perms) | S_IWUSR;
++ 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);
-@@ -217,6 +218,14 @@ int set_file_attrs(char *fname, struct f
+@@ -203,9 +205,21 @@ int set_file_attrs(char *fname, struct f
+ updated = 1;
}
- #endif
-+ /* If this is a directory, SET_ACL() will be called on the cleanup
-+ * receive_generator() pass (if we called it here, we might clobber
-+ * writability on the directory). Everything else is OK to do now. */
-+ if (!S_ISDIR(st->st_mode)) {
-+ if (SET_ACL(fname, file) == 0)
-+ updated = 1;
-+ }
++#ifdef SUPPORT_ACLS
++ /* It's OK to call set_acl() now, even for a dir, as the generator
++ * will enable owner-writability using chmod, if necessary.
++ *
++ * If set_acl changes permission bits in the process of setting
++ * an access ACL, it changes st->st_mode so we know whether we
++ * need to chmod. */
++ if (preserve_acls && set_acl(fname, file, &st->st_mode) == 0)
++ updated = 1;
++#endif
+
- if (verbose > 1 && flags & ATTRS_REPORT) {
- enum logcode code = daemon_log_format_has_i || dry_run
- ? FCLIENT : FINFO;
+ #ifdef HAVE_CHMOD
+ if ((st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) {
+- int ret = do_chmod(fname, file->mode);
++ mode_t mode = file->mode;
++ int ret = do_chmod(fname, mode);
+ if (ret < 0) {
+ rsyserr(FERROR, errno,
+ "failed to set permissions on %s",
--- old/rsync.h
+++ new/rsync.h
-@@ -657,6 +657,44 @@ struct chmod_mode_struct;
+@@ -658,6 +658,20 @@ struct chmod_mode_struct;
#define UNUSED(x) x __attribute__((__unused__))
+#define ACLS_NEED_MASK 1
+#endif
+
-+#ifdef SUPPORT_ACLS
-+#ifdef HAVE_SYS_ACL_H
++#if defined SUPPORT_ACLS && defined HAVE_SYS_ACL_H
+#include <sys/acl.h>
+#endif
-+#define MAKE_ACL(file, fname) make_acl(file, fname)
-+#define SEND_ACL(file, f) send_acl(file, f)
-+#define RECEIVE_ACL(file, f) receive_acl(file, f)
-+#define SORT_FILE_ACL_INDEX_LISTS() sort_file_acl_index_lists()
-+#define SET_ACL(fname, file) set_acl(fname, file)
-+#define NEXT_ACL_UID() next_acl_uid()
-+#define ACL_UID_MAP(uid) acl_uid_map(uid)
-+#define PUSH_KEEP_BACKUP_ACL(file, orig, dest) \
-+ push_keep_backup_acl(file, orig, dest)
-+#define CLEANUP_KEEP_BACKUP_ACL() cleanup_keep_backup_acl()
-+#define DUP_ACL(orig, dest, mode) dup_acl(orig, dest, mode)
-+#else /* SUPPORT_ACLS */
-+#define MAKE_ACL(file, fname) 1 /* checked return value */
-+#define SEND_ACL(file, f)
-+#define RECEIVE_ACL(file, f)
-+#define SORT_FILE_ACL_INDEX_LISTS()
-+#define SET_ACL(fname, file) 1 /* checked return value */
-+#define NEXT_ACL_UID()
-+#define ACL_UID_MAP(uid)
-+#define PUSH_KEEP_BACKUP_ACL(file, orig, dest)
-+#define CLEANUP_KEEP_BACKUP_ACL()
-+#define DUP_ACL(src, orig, mode) 1 /* checked return value */
-+#endif /* SUPPORT_ACLS */
+#include "smb_acls.h"
+
#include "proto.h"
/* We have replacement versions of these if they're missing. */
--- old/rsync.yo
+++ new/rsync.yo
-@@ -319,6 +319,7 @@ to the detailed description below for a
- -K, --keep-dirlinks treat symlinked dir on receiver as dir
+@@ -321,6 +321,7 @@ to the detailed description below for a
+ -H, --hard-links preserve hard links
-p, --perms preserve permissions
-E, --executability preserve executability
+ -A, --acls preserve ACLs (implies -p) [non-standard]
--chmod=CHMOD change destination permissions
-o, --owner preserve owner (super-user only)
-g, --group preserve group
-@@ -705,7 +706,9 @@ quote(itemize(
+@@ -742,7 +743,9 @@ quote(itemize(
permissions, though the bf(--executability) option might change just
the execute permission for the file.
it() New files get their "normal" permission bits set to the source
their special permission bits disabled except in the case where a new
directory inherits a setgid bit from its parent directory.
))
-@@ -736,9 +739,11 @@ The preservation of the destination's se
+@@ -773,9 +776,11 @@ The preservation of the destination's se
directories when bf(--perms) is off was added in rsync 2.6.7. Older rsync
versions erroneously preserved the three special permission bits for
newly-created files when bf(--perms) was off, while overriding the
dit(bf(-E, --executability)) This option causes rsync to preserve the
executability (or non-executability) of regular files when bf(--perms) is
-@@ -756,6 +761,10 @@ quote(itemize(
+@@ -793,6 +798,10 @@ quote(itemize(
If bf(--perms) is enabled, this option is ignored.
+#ifndef _SMB_ACLS_H
+#define _SMB_ACLS_H
+
-+#if defined(HAVE_POSIX_ACLS)
++#if defined HAVE_POSIX_ACLS
+
+/* This is an identity mapping (just remove the SMB_). */
+
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_TRU64_ACLS)
++#elif defined HAVE_TRU64_ACLS
+
+/* This is for DEC/Compaq Tru64 UNIX */
+
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_UNIXWARE_ACLS) || defined(HAVE_SOLARIS_ACLS)
++#elif defined HAVE_UNIXWARE_ACLS || defined HAVE_SOLARIS_ACLS
+/*
+ * Donated by Michael Davidson <md@sco.COM> for UnixWare / OpenUNIX.
+ * Modified by Toomas Soome <tsoome@ut.ee> for Solaris.
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
-+#elif defined(HAVE_HPUX_ACLS)
++#elif defined HAVE_HPUX_ACLS
+
+/*
+ * Based on the Solaris & UnixWare code.
+#define SMB_ACL_TYPE_ACCESS 0
+#define SMB_ACL_TYPE_DEFAULT 1
+
-+#elif defined(HAVE_IRIX_ACLS)
++#elif defined HAVE_IRIX_ACLS
+
+#define SMB_ACL_TAG_T acl_tag_t
+#define SMB_ACL_TYPE_T acl_type_t
+#define SMB_ACL_TYPE_ACCESS ACL_TYPE_ACCESS
+#define SMB_ACL_TYPE_DEFAULT ACL_TYPE_DEFAULT
+
-+#elif defined(HAVE_AIX_ACLS)
++#elif defined HAVE_AIX_ACLS
+
+/* Donated by Medha Date, mdate@austin.ibm.com, for IBM */
+
+ $RSYNC -rvv "$scratchdir/file" "$todir/to/anotherfile"
+ check_perms "$todir/to/anotherfile" $3 "Target $1"
+ # Make sure we obey default ACLs when not transferring a regular file
-+ $RSYNC -rvv "$scratchdir/dir" "$todir/to/anotherdir"
++ $RSYNC -rvv "$scratchdir/dir/" "$todir/to/anotherdir/"
+ check_perms "$todir/to/anotherdir" $4 "Target $1"
+}
+
/* read the gid list */
while ((id = read_int(f)) != 0) {
int len = read_byte(f);
-@@ -336,6 +337,18 @@ void recv_uid_list(int f, struct file_li
+@@ -336,6 +337,16 @@ void recv_uid_list(int f, struct file_li
}
}
+#ifdef SUPPORT_ACLS
+ if (preserve_acls && !numeric_ids) {
-+ id_t id;
-+ /* The enumerations don't return 0 except to flag the last
-+ * entry, since uidlist doesn't munge 0 anyway. */
-+ while ((id = next_acl_uid(flist)) != 0)
-+ acl_uid_map(match_uid(id));
-+ while ((id = next_acl_gid(flist)) != 0)
-+ acl_gid_map(match_gid(id));
++ id_t *id;
++ while ((id = next_acl_uid(flist)) != NULL)
++ *id = match_uid(*id);
++ while ((id = next_acl_gid(flist)) != NULL)
++ *id = match_gid(*id);
+ }
-+#endif /* SUPPORT_ACLS */
++#endif
+
/* Now convert all the uids/gids from sender values to our values. */
if (am_root && preserve_uid && !numeric_ids) {