X-Git-Url: https://mattmccutchen.net/rsync/rsync-patches.git/blobdiff_plain/cf51033aaac1500965f0aa48ce535e9fde26f681..b06103cc7d4f1827bb8f8a83ca07b56a48930e63:/acls.diff diff --git a/acls.diff b/acls.diff index d064859..8373667 100644 --- a/acls.diff +++ b/acls.diff @@ -4,6 +4,11 @@ After applying this patch, run these commands for a successful build: ./configure --enable-acl-support make +This code does not yet itemize changes in ACL information (see --itemize), +and it has a bug where some user/group ACL changes might not be propagated +from the sender to the receiver if the receiver already has a version of +the file that does not need any other attribute updates. + --- old/Makefile.in +++ new/Makefile.in @@ -25,15 +25,15 @@ VERSION=@VERSION@ @@ -27,10 +32,12 @@ After applying this patch, run these commands for a successful build: popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \ --- old/acls.c +++ new/acls.c -@@ -0,0 +1,1201 @@ +@@ -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 @@ -61,113 +68,126 @@ After applying this patch, run these commands for a successful build: +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) + -+static void expand_rsync_acl(rsync_acl *racl) ++/* a few useful calculations */ ++ ++static int rsync_acl_count_entries(const rsync_acl *racl) { ++ return racl->users.count + racl->groups.count ++ + (racl->user_obj != NO_ENTRY) ++ + (racl->group_obj != NO_ENTRY) ++ + (racl->mask != NO_ENTRY) ++ + (racl->other != NO_ENTRY); ++} ++ ++static int rsync_acl_get_perms(const rsync_acl *racl) { ++ /* Note that (NO_ENTRY & 7) is 0. */ ++ return ((racl->user_obj & 7) << 6) ++ + (((racl->mask != NO_ENTRY ? racl->mask : racl->group_obj) & 7) << 3) ++ + (racl->other & 7); ++} ++ ++static void rsync_acl_strip_perms(rsync_acl *racl) { ++ racl->user_obj = NO_ENTRY; ++ if (racl->mask == NO_ENTRY) ++ racl->group_obj = NO_ENTRY; ++ else ++ racl->mask = NO_ENTRY; ++ racl->other = NO_ENTRY; ++} ++ ++static void expand_ida_list(ida_list *idal) +{ + /* First time through, 0 <= 0, so list is expanded. */ -+ if (racl->malloced <= racl->count) { -+ rsync_ace *new_ptr; -+ size_t new_size = racl->malloced + 10; -+ new_ptr = realloc_array(racl->races, rsync_ace, new_size); ++ if (idal->malloced <= idal->count) { ++ id_access *new_ptr; ++ size_t new_size = idal->malloced + 10; ++ new_ptr = realloc_array(idal->idas, id_access, new_size); + if (verbose >= 4) { + rprintf(FINFO, "expand rsync_acl to %.0f bytes, did%s move\n", -+ (double) new_size * sizeof racl->races[0], -+ racl->races ? "" : " not"); ++ (double) new_size * sizeof idal->idas[0], ++ idal->idas ? "" : " not"); + } + -+ racl->races = new_ptr; -+ racl->malloced = new_size; ++ idal->idas = new_ptr; ++ idal->malloced = new_size; + -+ if (!racl->races) -+ out_of_memory("expand_rsync_acl"); ++ if (!idal->idas) ++ out_of_memory("expand_ida_list"); + } +} + ++static void ida_list_free(ida_list *idal) ++{ ++ free(idal->idas); ++ idal->idas = NULL; ++ idal->count = 0; ++ idal->malloced = 0; ++} ++ +static void rsync_acl_free(rsync_acl *racl) +{ -+ free(racl->races); -+ racl->races = NULL; -+ racl->count = 0; -+ racl->malloced = 0; ++ ida_list_free(&racl->users); ++ ida_list_free(&racl->groups); +} + -+static int rsync_ace_sorter(const void *r1, const void *r2) ++static int id_access_sorter(const void *r1, const void *r2) +{ -+ rsync_ace *race1 = (rsync_ace *)r1; -+ SMB_ACL_TAG_T rtag1 = race1->tag_type; -+ id_t rid1 = race1->id; -+ rsync_ace *race2 = (rsync_ace *)r2; -+ SMB_ACL_TAG_T rtag2 = race2->tag_type; -+ id_t rid2 = race2->id; -+ /* start at the extrema */ -+ if (rtag1 == SMB_ACL_USER_OBJ || rtag2 == SMB_ACL_MASK) -+ return -1; -+ if (rtag2 == SMB_ACL_USER_OBJ || rtag1 == SMB_ACL_MASK) -+ return 1; -+ /* work inwards */ -+ if (rtag1 == SMB_ACL_OTHER) -+ return 1; -+ if (rtag2 == SMB_ACL_OTHER) -+ return -1; -+ /* only SMB_ACL_USERs and SMB_ACL_GROUP*s left */ -+ if (rtag1 == SMB_ACL_USER) { -+ switch (rtag2) { -+ case SMB_ACL_GROUP: -+ case SMB_ACL_GROUP_OBJ: -+ case SMB_ACL_OTHER: -+ return -1; -+ } -+ /* both USER */ -+ return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1; -+ } -+ if (rtag2 == SMB_ACL_USER) -+ return 1; -+ /* only SMB_ACL_GROUP*s to worry about; kick out GROUP_OBJs first */ -+ if (rtag1 == SMB_ACL_GROUP_OBJ) -+ return -1; -+ if (rtag2 == SMB_ACL_GROUP_OBJ) -+ return 1; -+ /* only SMB_ACL_GROUPs left */ ++ id_access *ida1 = (id_access *)r1; ++ id_access *ida2 = (id_access *)r2; ++ id_t rid1 = ida1->id, rid2 = ida2->id; + return rid1 == rid2 ? 0 : rid1 < rid2 ? -1 : 1; +} + -+static void sort_rsync_acl(rsync_acl *racl) ++static void sort_ida_list(ida_list *idal) +{ -+ if (!racl->count) ++ if (!idal->count) + return; -+ qsort((void **)racl->races, racl->count, sizeof racl->races[0], -+ &rsync_ace_sorter); ++ qsort((void **)idal->idas, idal->count, sizeof idal->idas[0], ++ &id_access_sorter); +} + +static BOOL unpack_smb_acl(rsync_acl *racl, SMB_ACL_T sacl) +{ + SMB_ACL_ENTRY_T entry; -+ int rc; + const char *errfun; ++ int rc; ++ + *racl = rsync_acl_initializer; + errfun = "sys_acl_get_entry"; + for (rc = sys_acl_get_entry(sacl, SMB_ACL_FIRST_ENTRY, &entry); + rc == 1; + rc = sys_acl_get_entry(sacl, SMB_ACL_NEXT_ENTRY, &entry)) { ++ SMB_ACL_TAG_T tag_type; + SMB_ACL_PERMSET_T permset; ++ uchar access; + void *qualifier; -+ rsync_ace *race; -+ expand_rsync_acl(racl); -+ race = &racl->races[racl->count++]; -+ if ((rc = sys_acl_get_tag_type(entry, &race->tag_type))) { ++ id_access *ida; ++ ida_list *idal; ++ if ((rc = sys_acl_get_tag_type(entry, &tag_type))) { + errfun = "sys_acl_get_tag_type"; + break; + } @@ -175,14 +195,43 @@ After applying this patch, run these commands for a successful build: + 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))) { @@ -190,8 +239,11 @@ After applying this patch, run these commands for a successful build: + 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", @@ -199,29 +251,49 @@ After applying this patch, run these commands for a successful build: + 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; @@ -264,22 +336,6 @@ After applying this patch, run these commands for a successful build: + } +} + -+#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) @@ -309,57 +365,50 @@ After applying this patch, run these commands for a successful build: + * 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++) { -+ int ch; -+ switch (race->tag_type) { -+ case SMB_ACL_USER_OBJ: -+ ch = 'u'; -+ break; -+ case SMB_ACL_USER: -+ ch = 'U'; -+ break; -+ case SMB_ACL_GROUP_OBJ: -+ ch = 'g'; -+ break; -+ case SMB_ACL_GROUP: -+ ch = 'G'; -+ break; -+ case SMB_ACL_OTHER: -+ ch = 'o'; -+ break; -+ case SMB_ACL_MASK: -+ ch = 'm'; -+ break; -+ default: -+ rprintf(FERROR, -+ "send_rsync_acl: unknown tag_type (%02x) on ACE; disregarding\n", -+ race->tag_type); -+ continue; -+ } -+ write_byte(f, ch); -+ write_byte(f, race->access); -+ if (isupper(ch)) { -+ write_int(f, race->id); -+ /* FIXME: sorta wasteful: we should maybe buffer as -+ * many ids as max(ACL_USER + ACL_GROUP) objects to -+ * keep from making so many calls. */ -+ if (ch == 'U') -+ add_uid(race->id); -+ else -+ add_gid(race->id); -+ } ++ if (racl->user_obj != NO_ENTRY) { ++ write_byte(f, 'u'); ++ write_byte(f, racl->user_obj); ++ } ++ send_ida_list(f, &racl->users, 'U'); ++ if (racl->group_obj != NO_ENTRY) { ++ write_byte(f, 'g'); ++ write_byte(f, racl->group_obj); ++ } ++ send_ida_list(f, &racl->groups, 'G'); ++ if (racl->mask != NO_ENTRY) { ++ write_byte(f, 'm'); ++ write_byte(f, racl->mask); ++ } ++ if (racl->other != NO_ENTRY) { ++ write_byte(f, 'o'); ++ write_byte(f, racl->other); + } +} + +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" : @@ -367,26 +416,8 @@ After applying this patch, run these commands for a successful build: + "unknown SMB_ACL_TYPE_T"; +} + -+/* -+ * Overwrite racl with a new three-entry ACL from the given permissions. -+ */ -+static void perms_to_acl(int perms, rsync_acl *racl) -+{ -+ racl->count = 0; -+ expand_rsync_acl(racl); -+ racl->races[racl->count].tag_type = SMB_ACL_USER_OBJ; -+ racl->races[racl->count++].access = (perms >> 6) & 7; -+ expand_rsync_acl(racl); -+ racl->races[racl->count].tag_type = SMB_ACL_GROUP_OBJ; -+ racl->races[racl->count++].access = (perms >> 3) & 7; -+ expand_rsync_acl(racl); -+ racl->races[racl->count].tag_type = SMB_ACL_OTHER; -+ racl->races[racl->count++].access = (perms >> 0) & 7; -+} -+ +/* Generate the ACL(s) for this flist entry; + * ACL(s) are either sent or cleaned-up by send_acl() below. */ -+ +int make_acl(const struct file_struct *file, const char *fname) +{ + SMB_ACL_TYPE_T type; @@ -405,12 +436,12 @@ After applying this patch, run these commands for a successful build: + sys_acl_free_acl(sacl); + if (!ok) + return -1; ++ /* Strip access ACLs of permission-bit entries. */ ++ if (type == SMB_ACL_TYPE_ACCESS) ++ rsync_acl_strip_perms(curr_racl); + } else if (errno == ENOTSUP) { -+ /* ACLs are not supported. Invent an access ACL from -+ * permissions; let the default ACL default to empty. */ ++ /* ACLs are not supported. Leave list empty. */ + *curr_racl = rsync_acl_initializer; -+ if (type == SMB_ACL_TYPE_ACCESS) -+ perms_to_acl(file->mode & ACCESSPERMS, curr_racl); + } else { + rprintf(FERROR, "send_acl: sys_acl_get_file(%s, %s): %s\n", + fname, str_acl_type(type), strerror(errno)); @@ -424,7 +455,6 @@ After applying this patch, run these commands for a successful build: + +/* 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; @@ -509,15 +539,6 @@ After applying this patch, run these commands for a successful build: + } +} + -+#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 { @@ -562,210 +583,242 @@ After applying this patch, run these commands for a successful build: + } +} + -+#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, int perms) ++static BOOL pack_smb_acl(SMB_ACL_T *smb_acl, const rsync_acl *racl) +{ -+ size_t count = racl->count; -+ rsync_ace *race = racl->races; ++ size_t count; ++ id_access *ida; + const char *errfun = NULL; -+ int bits; ++ SMB_ACL_ENTRY_T entry; + -+ *smb_acl = sys_acl_init(count); -+ if (!*smb_acl) { ++ if (!(*smb_acl = sys_acl_init(rsync_acl_count_entries(racl)))) { + rprintf(FERROR, "pack_smb_acl: sys_acl_init(): %s\n", + strerror(errno)); + return False; + } + -+ for (; count--; race++) { -+ SMB_ACL_ENTRY_T entry; -+ SMB_ACL_PERMSET_T permset; -+ if (sys_acl_create_entry(smb_acl, &entry)) { -+ errfun = "sys_acl_create)"; -+ break; -+ } -+ if (sys_acl_set_tag_type(entry, race->tag_type)) { -+ errfun = "sys_acl_set_tag"; -+ break; -+ } -+ if (race->tag_type == SMB_ACL_USER || -+ race->tag_type == SMB_ACL_GROUP) { -+ if (sys_acl_set_qualifier(entry, (void*)&race->id)) { -+ errfun = "sys_acl_set_qualfier"; -+ break; -+ } -+ } -+ if (sys_acl_get_permset(entry, &permset)) { -+ errfun = "sys_acl_get_permset"; -+ break; -+ } -+ if (sys_acl_clear_perms(permset)) { -+ errfun = "sys_acl_clear_perms"; -+ break; -+ } -+ switch (perms >= 0 ? race->tag_type : SMB_ACL_USER_OBJ) { -+ case SMB_ACL_GROUP_OBJ: -+ bits = racl->count > 3 ? race->access : (perms >> 3) & 7; -+ break; -+ case SMB_ACL_MASK: -+ bits = (perms >> 3) & 7; -+ break; -+ case SMB_ACL_OTHER: -+ bits = perms & 7; -+ break; -+ default: -+ bits = race->access; -+ break; -+ } -+ if (bits & 4) { -+ if (sys_acl_add_perm(permset, SMB_ACL_READ)) { -+ errfun = "sys_acl_add_perm"; -+ break; -+ } -+ } -+ if (bits & 2) { -+ if (sys_acl_add_perm(permset, SMB_ACL_WRITE)) { -+ errfun = "sys_acl_add_perm"; -+ break; -+ } -+ } -+ if (bits & 1) { -+ if (sys_acl_add_perm(permset, SMB_ACL_EXECUTE)) { -+ errfun = "sys_acl_add_perm"; -+ break; -+ } -+ } -+ if (sys_acl_set_permset(entry, permset)) { -+ errfun = "sys_acl_set_permset"; -+ 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, mode_t mode, int f) ++static void receive_rsync_acl(rsync_acl *racl, int f) +{ -+ rsync_ace *user = NULL, *group = NULL, *other = NULL, *mask = NULL; -+ rsync_ace *race; -+ BOOL need_sort = 0; ++ uchar computed_mask_bits = 0; ++ ida_list *idal = NULL; ++ id_access *ida; + size_t count; + ++ *racl = rsync_acl_initializer; ++ + if (!(count = read_int(f))) + return; + + while (count--) { -+ uchar tag = read_byte(f); -+ 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; -+ user = race; -+ break; ++ if (racl->user_obj != NO_ENTRY) { ++ rprintf(FERROR, "receive_rsync_acl: error: duplicate USER_OBJ entry\n"); ++ exit_cleanup(RERR_STREAMIO); ++ } ++ racl->user_obj = access; ++ continue; + case 'U': -+ race->tag_type = SMB_ACL_USER; ++ idal = &racl->users; + break; + case 'g': -+ race->tag_type = SMB_ACL_GROUP_OBJ; -+ group = race; -+ break; ++ if (racl->group_obj != NO_ENTRY) { ++ rprintf(FERROR, "receive_rsync_acl: error: duplicate GROUP_OBJ entry\n"); ++ exit_cleanup(RERR_STREAMIO); ++ } ++ racl->group_obj = access; ++ continue; + case 'G': -+ race->tag_type = SMB_ACL_GROUP; -+ break; -+ case 'o': -+ race->tag_type = SMB_ACL_OTHER; -+ other = race; ++ idal = &racl->groups; + break; + case 'm': -+ race->tag_type = SMB_ACL_MASK; -+ mask = race; -+ break; ++ if (racl->mask != NO_ENTRY) { ++ rprintf(FERROR, "receive_rsync_acl: error: duplicate MASK entry\n"); ++ exit_cleanup(RERR_STREAMIO); ++ } ++ racl->mask = access; ++ continue; ++ case 'o': ++ if (racl->other != NO_ENTRY) { ++ rprintf(FERROR, "receive_rsync_acl: error: duplicate OTHER entry\n"); ++ exit_cleanup(RERR_STREAMIO); ++ } ++ racl->other = access; ++ continue; + default: + rprintf(FERROR, "receive_rsync_acl: unknown tag %c\n", + tag); + exit_cleanup(RERR_STREAMIO); + } -+ race->access = read_byte(f); -+ if (race->access & ~ (4 | 2 | 1)) { -+ rprintf(FERROR, "receive_rsync_acl: bogus permset %o\n", -+ race->access); -+ exit_cleanup(RERR_STREAMIO); ++ expand_ida_list(idal); ++ ida = &idal->idas[idal->count++]; ++ ida->access = access; ++ ida->id = read_int(f); ++ 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 */ ++ racl->group_obj &= racl->mask; ++ racl->mask = NO_ENTRY; + } -+ if (race->tag_type == SMB_ACL_USER || -+ race->tag_type == SMB_ACL_GROUP) { -+ race->id = read_int(f); -+ } -+ } -+ if (!user) { -+ expand_rsync_acl(racl); -+ user = &racl->races[racl->count++]; -+ user->tag_type = SMB_ACL_USER_OBJ; -+ user->access = (mode >> 6) & 7; -+ need_sort = True; -+ } -+ if (!group) { -+ expand_rsync_acl(racl); -+ group = &racl->races[racl->count++]; -+ group->tag_type = SMB_ACL_GROUP_OBJ; -+ group->access = (mode >> 3) & 7; -+ need_sort = True; -+ } -+ if (!other) { -+ expand_rsync_acl(racl); -+ other = &racl->races[racl->count++]; -+ other->tag_type = SMB_ACL_OTHER; -+ other->access = mode & 7; -+ need_sort = True; -+ } -+#if ACLS_NEED_MASK -+ if (!mask) { -+ expand_rsync_acl(racl); -+ mask = &racl->races[racl->count++]; -+ mask->tag_type = SMB_ACL_MASK; -+ mask->access = (mode >> 3) & 7; -+ need_sort = True; -+ } -+#else -+ /* If we, a system without ACLS_NEED_MASK, received data from a -+ * system that has masks, throw away the extraneous CLASS_OBJs. */ -+ if (mask && racl->count == 4) { -+ /* mask off group perms with it first */ -+ group->access &= mask->access; -+ /* dump mask; re-slot any followers-on */ -+ racl->count--; -+ if (mask != &racl->races[racl->count]) { -+ *mask = racl->races[racl->count]; -+ need_sort = True; -+ } -+ } ++ } else +#endif -+ if (need_sort) -+ 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; @@ -779,7 +832,7 @@ After applying this patch, run these commands for a successful build: + do { + file_acl_index_list *fileaclidx_list = + file_acl_index_lists(type); -+ uchar tag; ++ char tag; + expand_file_acl_index_list(fileaclidx_list); + + tag = read_byte(f); @@ -801,14 +854,14 @@ After applying this patch, run these commands for a successful build: + exit_cleanup(RERR_STREAMIO); + } + if (tag == 'A' || tag == 'D') { -+ rsync_acl racl = rsync_acl_initializer; ++ rsync_acl racl; + rsync_acl_list *racl_list = rsync_acl_lists(type); + smb_acl_list *sacl_list = smb_acl_lists(type); + fileaclidx_list->fileaclidxs[fileaclidx_list->count]. + aclidx = racl_list->count; + fileaclidx_list->fileaclidxs[fileaclidx_list->count++]. + file = file; -+ receive_rsync_acl(&racl, file->mode, f); ++ receive_rsync_acl(&racl, f); + expand_rsync_acl_list(racl_list); + racl_list->racls[racl_list->count++] = racl; + expand_smb_acl_list(sacl_list); @@ -883,7 +936,6 @@ After applying this patch, run these commands for a successful build: +} + +/* 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; @@ -919,7 +971,8 @@ After applying this patch, run these commands for a successful build: + } else { + ; /* presume they're unequal */ + } -+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_orig.count) { ++ if (type == SMB_ACL_TYPE_DEFAULT ++ && rsync_acl_count_entries(&racl_orig) == 0) { + if (sys_acl_delete_def_file(bak) < 0) { + rprintf(FERROR, "dup_acl: sys_acl_delete_def_file(%s): %s\n", + bak, strerror(errno)); @@ -1022,15 +1075,21 @@ After applying this patch, run these commands for a successful build: + } 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, mode_t old_mode) ++/* set ACL on rsync-ed or keep_backup-ed file ++ * ++ * This sets extended access ACL entries and default ACLs. If convenient, ++ * it sets permission bits along with the access ACLs and signals having ++ * done so by modifying p_mode, which should point into the stat buffer. ++ * ++ * returns: 1 for unchanged, 0 for changed, -1 for failed ++ * Pass NULL for p_mode to get the return code without changing anything. */ ++int set_acl(const char *fname, const struct file_struct *file, mode_t *p_mode) +{ + int unchanged = 1; + SMB_ACL_TYPE_T type; + -+ if (dry_run || S_ISLNK(file->mode)) -+ return 1; /* FIXME: --dry-run needs to compute this value */ ++ if (S_ISLNK(file->mode)) ++ return 1; + + if (file == backup_orig_file) { + if (!strcmp(fname, backup_dest_fname)) @@ -1038,11 +1097,11 @@ After applying this patch, run these commands for a successful build: + } + type = SMB_ACL_TYPE_ACCESS; + do { ++ BOOL ok; + SMB_ACL_T sacl_orig, *sacl_new; + rsync_acl racl_orig, *racl_new; -+ int aclidx = find_file_acl_index(file_acl_index_lists(type), -+ file); -+ BOOL ok; ++ int aclidx = find_file_acl_index(file_acl_index_lists(type), file); ++ + racl_new = &(rsync_acl_lists(type)->racls[aclidx]); + sacl_new = &(smb_acl_lists(type)->sacls[aclidx]); + sacl_orig = sys_acl_get_file(fname, type); @@ -1058,37 +1117,44 @@ After applying this patch, run these commands for a successful build: + unchanged = -1; + continue; + } -+ ok = rsync_acls_equal(&racl_orig, racl_new); ++ if (type == SMB_ACL_TYPE_ACCESS) ++ ok = rsync_acl_extended_parts_equal(&racl_orig, racl_new); ++ else ++ ok = rsync_acls_equal(&racl_orig, racl_new); + rsync_acl_free(&racl_orig); + if (ok) + continue; -+ if (type == SMB_ACL_TYPE_DEFAULT && !racl_new->count) { -+ if (sys_acl_delete_def_file(fname) < 0) { -+ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n", -+ fname, strerror(errno)); -+ unchanged = -1; -+ continue; -+ } -+ } else { -+ /* We set the mask (if it exists) to the file's group -+ * permissions (as they currently exist on the disk). -+ * This avoids a permission race, particularly for new -+ * files (where the current group permissions == 0). -+ * If this is not the right value, the upcoming chmod() -+ * call will change it. */ -+ int perms = type == SMB_ACL_TYPE_ACCESS -+ ? (int)(old_mode & CHMOD_BITS) : -1; -+ if (!*sacl_new -+ && !pack_smb_acl(sacl_new, racl_new, perms)) { -+ unchanged = -1; -+ continue; -+ } -+ if (sys_acl_set_file(fname, type, *sacl_new) < 0) { -+ rprintf(FERROR, "set_acl: sys_acl_set_file(%s, %s): %s\n", -+ fname, str_acl_type(type), -+ strerror(errno)); -+ unchanged = -1; -+ continue; ++ if (!dry_run && p_mode) { ++ if (type == SMB_ACL_TYPE_DEFAULT ++ && rsync_acl_count_entries(racl_new) == 0) { ++ if (sys_acl_delete_def_file(fname) < 0) { ++ rprintf(FERROR, "set_acl: sys_acl_delete_def_file(%s): %s\n", ++ fname, strerror(errno)); ++ unchanged = -1; ++ continue; ++ } ++ } else { ++ mode_t cur_mode = *p_mode; ++ if (!*sacl_new ++ && !pack_smb_acl(sacl_new, racl_new)) { ++ unchanged = -1; ++ continue; ++ } ++ if (type == SMB_ACL_TYPE_ACCESS) { ++ cur_mode = change_sacl_perms(*sacl_new, racl_new->mask, ++ cur_mode, file->mode); ++ if (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 (unchanged == 1) @@ -1108,19 +1174,19 @@ After applying this patch, run these commands for a successful build: + +static rsync_acl_list **enum_racl_list = &_enum_racl_lists[0]; +static size_t enum_racl_index = 0; -+static size_t enum_race_index = 0; ++static size_t enum_ida_index = 0; + +/* This returns the next tag_type id from the given acl for the next entry, + * or it returns 0 if there are no more tag_type ids in the acl. */ -+ +static id_t *next_ace_id(SMB_ACL_TAG_T tag_type, const rsync_acl *racl) +{ -+ for (; enum_race_index < racl->count; enum_race_index++) { -+ rsync_ace *race = &racl->races[enum_race_index++]; -+ if (race->tag_type == tag_type) -+ return &race->id; ++ const ida_list *idal = (tag_type == SMB_ACL_USER ? ++ &racl->users : &racl->groups); ++ if (enum_ida_index < idal->count) { ++ id_access *ida = &idal->idas[enum_ida_index++]; ++ return &ida->id; + } -+ enum_race_index = 0; ++ enum_ida_index = 0; + return NULL; +} + @@ -1157,14 +1223,11 @@ After applying this patch, run these commands for a successful build: + return next_acl_list_id(SMB_ACL_GROUP); +} + -+#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) @@ -1200,31 +1263,14 @@ After applying this patch, run these commands for a successful build: + 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; +} + @@ -1438,7 +1484,24 @@ After applying this patch, run these commands for a successful build: extern struct stats stats; extern dev_t filesystem_dev; extern char *backup_dir; -@@ -753,6 +754,7 @@ static int try_dests_non(struct file_str +@@ -321,6 +322,8 @@ static void do_delete_pass(struct file_l + + int unchanged_attrs(struct file_struct *file, STRUCT_STAT *st) + { ++ /* FIXME: Flag ACL changes. */ ++ + if (preserve_perms + && (st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) + return 0; +@@ -355,6 +358,7 @@ void itemize(struct file_struct *file, i + if (preserve_gid && file->gid != GID_NONE + && st->st_gid != file->gid) + iflags |= ITEM_REPORT_GROUP; ++ /* FIXME: Itemize ACL changes. ITEM_REPORT_XATTR? */ + } else + iflags |= ITEM_IS_NEW; + +@@ -753,6 +757,7 @@ static int try_dests_non(struct file_str } static int phase = 0; @@ -1446,7 +1509,7 @@ After applying this patch, run these commands for a successful build: /* 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; @@ -1457,7 +1520,7 @@ After applying this patch, run these commands for a successful build: } 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); @@ -1467,7 +1530,7 @@ After applying this patch, run these commands for a successful build: } if (S_ISDIR(file->mode)) { -@@ -1343,6 +1350,8 @@ void generate_files(int f_out, struct fi +@@ -1343,6 +1353,8 @@ void generate_files(int f_out, struct fi * notice that and let us know via the redo pipe (or its closing). */ ignore_timeout = 1; @@ -4927,20 +4990,29 @@ After applying this patch, run these commands for a successful build: 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,13 @@ int set_file_attrs(char *fname, struct f +@@ -203,9 +205,21 @@ int set_file_attrs(char *fname, struct f updated = 1; } +#ifdef SUPPORT_ACLS + /* It's OK to call set_acl() now, even for a dir, as the generator -+ * will enable owner-writability using chmod, if necessary. */ -+ if (preserve_acls && set_acl(fname, file, st->st_mode) == 0) ++ * will enable owner-writability using chmod, if necessary. ++ * ++ * If set_acl changes permission bits in the process of setting ++ * an access ACL, it changes st->st_mode so we know whether we ++ * need to chmod. */ ++ if (preserve_acls && set_acl(fname, file, &st->st_mode) == 0) + updated = 1; +#endif + #ifdef HAVE_CHMOD if ((st->st_mode & CHMOD_BITS) != (file->mode & CHMOD_BITS)) { - int ret = do_chmod(fname, file->mode); +- 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 @@ -658,6 +658,20 @@ struct chmod_mode_struct;