[patch] NFSv4/ZFS ACLs

Richard P richardp345 at gmail.com
Sat Jun 9 15:33:15 MDT 2012


>
> This time with a non-stripped attachment (hopefully)
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.samba.org/pipermail/rsync/attachments/20120609/a97e270a/attachment.html>
-------------- next part --------------
diff -uNr rsync-3.0.9.orig/acls.c rsync-3.0.9/acls.c
--- rsync-3.0.9.orig/acls.c	2011-05-31 00:39:07.000000000 +0900
+++ rsync-3.0.9/acls.c	2012-06-05 11:05:42.000000000 +0900
@@ -33,6 +33,7 @@
 extern int inc_recurse;
 extern int preserve_devices;
 extern int preserve_specials;
+extern int protocol_version;
 
 /* Flags used to indicate what items are being transmitted for an entry. */
 #define XMIT_USER_OBJ (1<<0)
@@ -52,6 +53,9 @@
 #define XFLAG_NAME_FOLLOWS 0x0001u
 #define XFLAG_NAME_IS_USER 0x0002u
 
+ /* ordinary ACLs have this code; NFSv4 ACLs have a platform-specific code */
+#define SMB_ACL_TYPE 1
+
 /* === ACL structures === */
 
 typedef struct {
@@ -76,6 +80,10 @@
 	uchar group_obj;
 	uchar mask_obj;
 	uchar other_obj;
+#ifdef SUPPORT_NFS4_ACLS
+	size_t nfs4_acl_len;
+	char *nfs4_acl_data;
+#endif
 } rsync_acl;
 
 typedef struct {
@@ -84,7 +92,11 @@
 } acl_duo;
 
 static const rsync_acl empty_rsync_acl = {
+#ifdef SUPPORT_NFS4_ACLS
+	{NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY, 0, NULL
+#else
 	{NULL, 0}, NO_ENTRY, NO_ENTRY, NO_ENTRY, NO_ENTRY
+#endif
 };
 
 static item_list access_acl_list = EMPTY_ITEM_LIST;
@@ -192,6 +204,11 @@
 
 static BOOL rsync_acl_equal(const rsync_acl *racl1, const rsync_acl *racl2)
 {
+#ifdef SUPPORT_NFS4_ACLS
+	if (racl1->nfs4_acl_len || racl2->nfs4_acl_len)
+		return racl1->nfs4_acl_len == racl2->nfs4_acl_len
+		  && !memcmp(racl1->nfs4_acl_data, racl2->nfs4_acl_data, racl1->nfs4_acl_len);
+#endif
 	return racl1->user_obj == racl2->user_obj
 	    && racl1->group_obj == racl2->group_obj
 	    && racl1->mask_obj == racl2->mask_obj
@@ -207,6 +224,12 @@
 static BOOL rsync_acl_equal_enough(const rsync_acl *racl1,
 				   const rsync_acl *racl2, mode_t m)
 {
+
+#ifdef SUPPORT_NFS4_ACLS
+	if (racl1->nfs4_acl_len || racl2->nfs4_acl_len)
+		return racl1->nfs4_acl_len == racl2->nfs4_acl_len
+		    && !memcmp(racl1->nfs4_acl_data, racl2->nfs4_acl_data, racl1->nfs4_acl_len);
+#endif
 	if ((racl1->mask_obj ^ racl2->mask_obj) & NO_ENTRY)
 		return False; /* One has a mask and the other doesn't */
 
@@ -229,6 +252,10 @@
 {
 	if (racl->names.idas)
 		free(racl->names.idas);
+#ifdef SUPPORT_NFS4_ACLS
+	if (racl->nfs4_acl_data)
+		free(racl->nfs4_acl_data);
+#endif
 	*racl = empty_rsync_acl;
 }
 
@@ -541,6 +568,60 @@
 	return 0;
 }
 
+#ifdef SUPPORT_NFS4_ACLS
+#ifdef HAVE_LINUX_XATTRS
+#define NFS4_ACL_TYPE 2
+#define NFS4_ACL_ATTR "system.nfs4_acl"
+#include "lib/sysxattrs.h"
+static int sys_nfs4acl_get_file(const char* fname, rsync_acl *racl)
+{
+	size_t len = 0; /* no extra data alloc needed from get_xattr_data() */
+	if ((racl->nfs4_acl_data = get_xattr_data(fname, NFS4_ACL_ATTR, &len, 1)) != NULL) {
+		racl->nfs4_acl_len = len;
+		return 0;
+	}
+	return errno == ENOTSUP ? 1 : -1;
+}
+
+static int sys_nfs4acl_set_file(const char* fname, const rsync_acl *racl)
+{
+	return sys_lsetxattr(fname, NFS4_ACL_ATTR, racl->nfs4_acl_data, racl->nfs4_acl_len);
+}
+
+#elif defined(HAVE_SOLARIS_ACLS)
+#define NFS4_ACL_TYPE 3
+static int sys_nfs4acl_get_file(const char* fname, rsync_acl *racl)
+{
+	acl_t *aclp;
+	char *acltextp;
+
+	if (acl(fname, ACE_GETACLCNT, 0, NULL) == -1)
+		return errno = EINVAL ? 1 : -1;
+	if (acl_get(fname, 0, &aclp))
+		return errno == ENOSYS ? 1 : -1;
+	acltextp = acl_totext(aclp, ACL_COMPACT_FMT);
+	racl->nfs4_acl_data = acltextp;
+	racl->nfs4_acl_len = strlen(acltextp) + 1;
+	acl_free(aclp);
+	return 0;
+}
+
+static int sys_nfs4acl_set_file(const char* fname, const rsync_acl *racl)
+{
+	acl_t *aclp;
+	int rc;
+
+	if (acl_fromtext(racl->nfs4_acl_data, &aclp)) {
+		errno = EINVAL;
+		return -1;
+	}
+	rc = acl_set(fname, aclp);
+	acl_free(aclp);
+	return rc;
+}
+#endif /* HAVE_LINUX_XATTRS */
+#endif /* SUPPORT_NFS4_ACLS */
+
 /* Return the Access Control List for the given filename. */
 int get_acl(const char *fname, stat_x *sxp)
 {
@@ -562,6 +643,25 @@
 			return 0;
 	}
 
+#ifdef SUPPORT_NFS4_ACLS
+#ifdef SUPPORT_XATTRS
+	/* --fake-super support: load ACLs from an xattr. */
+	if (am_root < 0) {
+		sxp->acc_acl->nfs4_acl_data = get_xattr_nfs4acl(fname, &sxp->acc_acl->nfs4_acl_len);
+		return 0;
+	}
+#endif
+	switch (sys_nfs4acl_get_file(fname, sxp->acc_acl)) {
+	case -1:
+		rsyserr(FERROR_XFER, errno, "get_acl: sys_nfs4acl_get_file(%s)",
+			fname);
+		free_acl(sxp);
+		return -1;
+	case 0:
+		return 0;
+	/* default: 1 - NFSv4 ACLs not supported so fall through to standard ACLs */
+	}
+#endif
 	if (get_rsync_acl(fname, sxp->acc_acl, SMB_ACL_TYPE_ACCESS,
 			  sxp->st.st_mode) < 0) {
 		free_acl(sxp);
@@ -609,7 +709,7 @@
 	}
 }
 
-static void send_rsync_acl(int f, rsync_acl *racl, SMB_ACL_TYPE_T type,
+static void send_rsync_acl(int f, int acl_type, rsync_acl *racl, SMB_ACL_TYPE_T type,
 			   item_list *racl_list)
 {
 	int ndx = find_matching_rsync_acl(racl, type, racl_list);
@@ -621,30 +721,36 @@
 		rsync_acl *new_racl = EXPAND_ITEM_LIST(racl_list, rsync_acl, 1000);
 		uchar flags = 0;
 
-		if (racl->user_obj != NO_ENTRY)
-			flags |= XMIT_USER_OBJ;
-		if (racl->group_obj != NO_ENTRY)
-			flags |= XMIT_GROUP_OBJ;
-		if (racl->mask_obj != NO_ENTRY)
-			flags |= XMIT_MASK_OBJ;
-		if (racl->other_obj != NO_ENTRY)
-			flags |= XMIT_OTHER_OBJ;
-		if (racl->names.count)
-			flags |= XMIT_NAME_LIST;
-
-		write_byte(f, flags);
-
-		if (flags & XMIT_USER_OBJ)
-			write_varint(f, racl->user_obj);
-		if (flags & XMIT_GROUP_OBJ)
-			write_varint(f, racl->group_obj);
-		if (flags & XMIT_MASK_OBJ)
-			write_varint(f, racl->mask_obj);
-		if (flags & XMIT_OTHER_OBJ)
-			write_varint(f, racl->other_obj);
-		if (flags & XMIT_NAME_LIST)
-			send_ida_entries(f, &racl->names);
-
+		if (acl_type == SMB_ACL_TYPE) {
+			if (racl->user_obj != NO_ENTRY)
+				flags |= XMIT_USER_OBJ;
+			if (racl->group_obj != NO_ENTRY)
+				flags |= XMIT_GROUP_OBJ;
+			if (racl->mask_obj != NO_ENTRY)
+				flags |= XMIT_MASK_OBJ;
+			if (racl->other_obj != NO_ENTRY)
+				flags |= XMIT_OTHER_OBJ;
+			if (racl->names.count)
+				flags |= XMIT_NAME_LIST;
+
+			write_byte(f, flags);
+
+			if (flags & XMIT_USER_OBJ)
+				write_varint(f, racl->user_obj);
+			if (flags & XMIT_GROUP_OBJ)
+				write_varint(f, racl->group_obj);
+			if (flags & XMIT_MASK_OBJ)
+				write_varint(f, racl->mask_obj);
+			if (flags & XMIT_OTHER_OBJ)
+				write_varint(f, racl->other_obj);
+			if (flags & XMIT_NAME_LIST)
+				send_ida_entries(f, &racl->names);
+#ifdef SUPPORT_NFS4_ACLS
+		} else {
+			write_varint(f, racl->nfs4_acl_len);
+			write_buf(f, racl->nfs4_acl_data, racl->nfs4_acl_len);
+#endif
+		}
 		/* Give the allocated data to the new list object. */
 		*new_racl = *racl;
 		*racl = empty_rsync_acl;
@@ -658,17 +764,30 @@
 	if (!sxp->acc_acl) {
 		sxp->acc_acl = create_racl();
 		rsync_acl_fake_perms(sxp->acc_acl, sxp->st.st_mode);
+#ifdef SUPPORT_NFS4_ACLS
+	} else if (sxp->acc_acl->nfs4_acl_data) {
+		if (protocol_version >= 31) {
+			write_byte(f, NFS4_ACL_TYPE);
+			send_rsync_acl(f, NFS4_ACL_TYPE, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list);
+		} else {
+			rprintf(FERROR, "send_acl: nfs4 acls require protocol XX or higher (negotiated %d).\n", protocol_version);
+			exit_cleanup(RERR_PROTOCOL);
+		}
+		return;
+#endif
 	}
 	/* Avoid sending values that can be inferred from other data. */
 	rsync_acl_strip_perms(sxp);
 
-	send_rsync_acl(f, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list);
+	if (protocol_version >= 31)
+		write_byte(f, SMB_ACL_TYPE);
+	send_rsync_acl(f, SMB_ACL_TYPE, sxp->acc_acl, SMB_ACL_TYPE_ACCESS, &access_acl_list);
 
 	if (S_ISDIR(sxp->st.st_mode)) {
 		if (!sxp->def_acl)
 			sxp->def_acl = create_racl();
 
-		send_rsync_acl(f, sxp->def_acl, SMB_ACL_TYPE_DEFAULT, &default_acl_list);
+		send_rsync_acl(f, SMB_ACL_TYPE, sxp->def_acl, SMB_ACL_TYPE_DEFAULT, &default_acl_list);
 	}
 }
 
@@ -738,7 +857,7 @@
 	return computed_mask_bits & ~NO_ENTRY;
 }
 
-static int recv_rsync_acl(int f, item_list *racl_list, SMB_ACL_TYPE_T type, mode_t mode)
+static int recv_rsync_acl(int f, int acl_type, item_list *racl_list, SMB_ACL_TYPE_T type, mode_t mode)
 {
 	uchar computed_mask_bits = 0;
 	acl_duo *duo_item;
@@ -758,33 +877,42 @@
 	duo_item = EXPAND_ITEM_LIST(racl_list, acl_duo, 1000);
 	duo_item->racl = empty_rsync_acl;
 
-	flags = read_byte(f);
+	if (acl_type == SMB_ACL_TYPE) {
+		flags = read_byte(f);
 
-	if (flags & XMIT_USER_OBJ)
-		duo_item->racl.user_obj = recv_acl_access(f, NULL);
-	if (flags & XMIT_GROUP_OBJ)
-		duo_item->racl.group_obj = recv_acl_access(f, NULL);
-	if (flags & XMIT_MASK_OBJ)
-		duo_item->racl.mask_obj = recv_acl_access(f, NULL);
-	if (flags & XMIT_OTHER_OBJ)
-		duo_item->racl.other_obj = recv_acl_access(f, NULL);
-	if (flags & XMIT_NAME_LIST)
-		computed_mask_bits |= recv_ida_entries(f, &duo_item->racl.names);
+		if (flags & XMIT_USER_OBJ)
+			duo_item->racl.user_obj = recv_acl_access(f, NULL);
+		if (flags & XMIT_GROUP_OBJ)
+			duo_item->racl.group_obj = recv_acl_access(f, NULL);
+		if (flags & XMIT_MASK_OBJ)
+			duo_item->racl.mask_obj = recv_acl_access(f, NULL);
+		if (flags & XMIT_OTHER_OBJ)
+			duo_item->racl.other_obj = recv_acl_access(f, NULL);
+		if (flags & XMIT_NAME_LIST)
+			computed_mask_bits |= recv_ida_entries(f, &duo_item->racl.names);
 
 #ifdef HAVE_OSX_ACLS
-	/* If we received a superfluous mask, throw it away. */
-	duo_item->racl.mask_obj = NO_ENTRY;
+		/* If we received a superfluous mask, throw it away. */
+		duo_item->racl.mask_obj = NO_ENTRY;
 #else
-	if (duo_item->racl.names.count && duo_item->racl.mask_obj == NO_ENTRY) {
-		/* Mask must be non-empty with lists. */
-		if (type == SMB_ACL_TYPE_ACCESS)
-			computed_mask_bits = (mode >> 3) & 7;
-		else
-			computed_mask_bits |= duo_item->racl.group_obj & ~NO_ENTRY;
-		duo_item->racl.mask_obj = computed_mask_bits;
-	}
+		if (duo_item->racl.names.count && duo_item->racl.mask_obj == NO_ENTRY) {
+			/* Mask must be non-empty with lists. */
+			if (type == SMB_ACL_TYPE_ACCESS)
+				computed_mask_bits = (mode >> 3) & 7;
+			else
+				computed_mask_bits |= duo_item->racl.group_obj & ~NO_ENTRY;
+			duo_item->racl.mask_obj = computed_mask_bits;
+		}
 #endif
-
+#ifdef SUPPORT_NFS4_ACLS
+	} else if (acl_type == NFS4_ACL_TYPE) {
+		duo_item->racl.nfs4_acl_len = read_varint(f);
+		duo_item->racl.nfs4_acl_data = new_array(char, duo_item->racl.nfs4_acl_len);
+		if (!duo_item->racl.nfs4_acl_data)
+			out_of_memory("recv_rsync_acl");
+		read_buf(f, duo_item->racl.nfs4_acl_data, duo_item->racl.nfs4_acl_len);
+#endif
+	}
 	duo_item->sacl = NULL;
 
 	return ndx;
@@ -793,10 +921,20 @@
 /* Receive the ACL info the sender has included for this file-list entry. */
 void receive_acl(int f, struct file_struct *file)
 {
-	F_ACL(file) = recv_rsync_acl(f, &access_acl_list, SMB_ACL_TYPE_ACCESS, file->mode);
+	int acl_type = protocol_version >= 31 ? read_byte(f) : SMB_ACL_TYPE;
 
-	if (S_ISDIR(file->mode))
-		F_DIR_DEFACL(file) = recv_rsync_acl(f, &default_acl_list, SMB_ACL_TYPE_DEFAULT, 0);
+	if (acl_type == SMB_ACL_TYPE) {
+		F_ACL(file) = recv_rsync_acl(f, acl_type, &access_acl_list, SMB_ACL_TYPE_ACCESS, file->mode);
+		if (S_ISDIR(file->mode))
+			F_DIR_DEFACL(file) = recv_rsync_acl(f, acl_type, &default_acl_list, SMB_ACL_TYPE_DEFAULT, 0);
+#ifdef SUPPORT_NFS4_ACLS
+	} else if (acl_type == NFS4_ACL_TYPE) {
+		F_ACL(file) = recv_rsync_acl(f, acl_type, &access_acl_list, SMB_ACL_TYPE_ACCESS, file->mode);
+#endif
+	} else {
+		rprintf(FERROR, "receive_acl: unsupported ACL type (%u)\n", acl_type);
+		exit_cleanup(RERR_UNSUPPORTED);
+	}
 }
 
 static int cache_rsync_acl(rsync_acl *racl, SMB_ACL_TYPE_T type, item_list *racl_list)
@@ -943,18 +1081,59 @@
 }
 #endif
 
+#ifdef SUPPORT_NFS4_ACLS
+#define NFS4_ACL_NOTREADY(racl) (!(racl) || !(racl)->nfs4_acl_data)
+#else
+#define NFS4_ACL_NOTREADY(racl) (1)
+#endif
+
 static int set_rsync_acl(const char *fname, acl_duo *duo_item,
 			 SMB_ACL_TYPE_T type, stat_x *sxp, mode_t mode)
 {
+#ifdef SUPPORT_NFS4_ACLS
+	if (duo_item->racl.nfs4_acl_data) {
+#ifdef SUPPORT_XATTRS
+		if (am_root < 0)
+			/* --fake-super support: store ACL in an xattr. */
+			return set_xattr_nfs4acl(fname, duo_item->racl.nfs4_acl_data,
+						 duo_item->racl.nfs4_acl_len);
+#endif
+#ifdef HAVE_CHMOD
+		/* prefer chmod to equalize the ACLs - don't create redundant security descriptors
+		   - unfortunately ACLs can still compare unequal on Linux simply due to padding
+		 */
+		if (!BITS_EQUAL(sxp->st.st_mode, mode, CHMOD_BITS)) {
+			static rsync_acl temp_racl;
+
+			if (!do_chmod(fname, mode)) {
+				sxp->st.st_mode = mode;
+				if (!sys_nfs4acl_get_file(fname, &temp_racl)) {
+					if (rsync_acl_equal(&temp_racl, &duo_item->racl)) {
+						free(temp_racl.nfs4_acl_data);
+						return 0;
+					}
+					free(temp_racl.nfs4_acl_data);
+				}
+			}
+		}
+#endif
+		if (sys_nfs4acl_set_file(fname, &duo_item->racl)) {
+			rsyserr(FERROR_XFER, errno, "set_acl: sys_nfs4acl_set_file(%s)", fname);
+			return -1;
+		}
+		return 0;
+	}
+#endif
 	if (type == SMB_ACL_TYPE_DEFAULT
 	 && duo_item->racl.user_obj == NO_ENTRY) {
-		int rc;
+		int rc = 0;
 #ifdef SUPPORT_XATTRS
 		/* --fake-super support: delete default ACL from xattrs. */
 		if (am_root < 0)
 			rc = del_def_xattr_acl(fname);
 		else
 #endif
+		if (NFS4_ACL_NOTREADY(sxp->acc_acl))
 			rc = sys_acl_delete_def_file(fname);
 		if (rc < 0) {
 			rsyserr(FERROR_XFER, errno, "set_acl: sys_acl_delete_def_file(%s)",
@@ -986,7 +1165,7 @@
 		free(buf);
 		return rc;
 #endif
-	} else {
+	} else if (NFS4_ACL_NOTREADY(sxp->acc_acl)) {
 		mode_t cur_mode = sxp->st.st_mode;
 		if (!duo_item->sacl
 		 && !pack_smb_acl(&duo_item->sacl, &duo_item->racl))
diff -uNr rsync-3.0.9.orig/rsync.h rsync-3.0.9/rsync.h
--- rsync-3.0.9.orig/rsync.h	2011-02-22 04:32:51.000000000 +0900
+++ rsync-3.0.9/rsync.h	2012-05-14 13:50:38.000000000 +0900
@@ -96,7 +96,8 @@
 			     == ((unsigned)(b2) & (unsigned)(mask)))
 
 /* update this if you make incompatible changes */
-#define PROTOCOL_VERSION 30
+#define PROTOCOL_VERSION 31
+#define SUPPORT_NFS4_ACLS 1
 
 /* This is used when working on a new protocol version in CVS, and should
  * be a new non-zero value for each CVS change that affects the protocol.
diff -uNr rsync-3.0.9.orig/xattrs.c rsync-3.0.9/xattrs.c
--- rsync-3.0.9.orig/xattrs.c	2011-09-23 01:02:21.000000000 +0900
+++ rsync-3.0.9/xattrs.c	2012-05-14 13:50:38.000000000 +0900
@@ -71,6 +71,10 @@
 #define XACC_ACL_ATTR RSYNC_PREFIX "%" XACC_ACL_SUFFIX
 #define XDEF_ACL_SUFFIX "dacl"
 #define XDEF_ACL_ATTR RSYNC_PREFIX "%" XDEF_ACL_SUFFIX
+#ifdef SUPPORT_NFS4_ACLS
+#define XNFS4ACL_SUFFIX "nfs4acl"
+#define XNFS4ACL_ATTR RSYNC_PREFIX "%" XNFS4ACL_SUFFIX
+#endif
 
 typedef struct {
 	char *datum, *name;
@@ -166,8 +170,8 @@
 /* On entry, the *len_ptr parameter contains the size of the extra space we
  * should allocate when we create a buffer for the data.  On exit, it contains
  * the length of the datum. */
-static char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr,
-			    int no_missing_error)
+char *get_xattr_data(const char *fname, const char *name, size_t *len_ptr,
+		     int no_missing_error)
 {
 	size_t datum_len = sys_lgetxattr(fname, name, NULL, 0);
 	size_t extra_len = *len_ptr;
@@ -938,6 +942,14 @@
 	return get_xattr_data(fname, name, len_p, 1);
 }
 
+#ifdef SUPPORT_NFS4_ACLS
+char *get_xattr_nfs4acl(const char *fname, size_t *len_p)
+{
+	*len_p = 0; /* no extra data alloc needed from get_xattr_data() */
+	return get_xattr_data(fname, XNFS4ACL_ATTR, len_p, 1);
+}
+#endif
+
 int set_xattr_acl(const char *fname, int is_access_acl, const char *buf, size_t buf_len)
 {
 	const char *name = is_access_acl ? XACC_ACL_ATTR : XDEF_ACL_ATTR;
@@ -950,6 +962,19 @@
 	return 0;
 }
 
+#ifdef SUPPORT_NFS4_ACLS
+int set_xattr_nfs4acl(const char *fname, const char *buf, size_t buf_len)
+{
+	if (sys_lsetxattr(fname, XNFS4ACL_ATTR, buf, buf_len) < 0) {
+		rsyserr(FERROR_XFER, errno,
+			"set_xattr_nfs4acl: lsetxattr(\"%s\",\"%s\") failed",
+			full_fname(fname), XNFS4ACL_ATTR);
+		return -1;
+	}
+	return 0;
+}
+#endif
+
 int del_def_xattr_acl(const char *fname)
 {
 	return sys_lremovexattr(fname, XDEF_ACL_ATTR);


More information about the rsync mailing list