All of lore.kernel.org
 help / color / mirror / Atom feed
From: Anna Schumaker <Anna.Schumaker-HgOvQuBEEgTQT0dZR+AlfA@public.gmane.org>
To: Andreas Gruenbacher
	<andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Alexander Viro
	<viro-RmSDqhL/yNMiFSDQTTA3OLVCufUGDwFn@public.gmane.org>,
	Theodore Ts'o <tytso-3s7WtUTddSA@public.gmane.org>,
	Andreas Dilger
	<adilger.kernel-m1MBpc4rdrD3fQ9qLvQP4Q@public.gmane.org>,
	"J. Bruce Fields"
	<bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>,
	Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>,
	Trond Myklebust
	<trond.myklebust-7I+n7zu2hftEKMMhf/gKZA@public.gmane.org>,
	Anna Schumaker
	<anna.schumaker-HgOvQuBEEgTQT0dZR+AlfA@public.gmane.org>,
	Dave Chinner <david-FqsqvQoI3Ljby3iVrkZq2A@public.gmane.org>,
	<linux-ext4-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	<xfs-VZNHf3L845pBDgjK7y7TUQ@public.gmane.org>,
	<linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	<linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	<linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	<linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>,
	<linux-api-u79uwXL29TY76Z2rM5mHXA@public.gmane.org>
Cc: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Subject: Re: [PATCH v10 44/46] nfs: Add richacl support
Date: Mon, 12 Oct 2015 10:39:26 -0400	[thread overview]
Message-ID: <561BC61E.6010704@Netapp.com> (raw)
In-Reply-To: <1444604337-17651-45-git-send-email-andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

Hi Andreas,

On 10/11/2015 06:58 PM, Andreas Gruenbacher wrote:
> From: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> 
> Add support for the "system.richacl" xattr in nfs.  The existing
> "system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
> and gids; the "system.richacl" xattr does, and only keeps the
> on-the-wire names when there is no mapping.  This allows to copy
> permissions across different file systems.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/nfs/inode.c            |   3 -
>  fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
>  fs/nfs/nfs4xdr.c          | 179 ++++++++++--
>  fs/nfs/super.c            |   4 +-
>  include/linux/nfs_fs.h    |   1 -
>  include/linux/nfs_fs_sb.h |   2 +
>  include/linux/nfs_xdr.h   |   9 +-
>  7 files changed, 673 insertions(+), 223 deletions(-)
> 
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 326d9e1..843d15d 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -1852,9 +1852,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
>  		return NULL;
>  	nfsi->flags = 0UL;
>  	nfsi->cache_validity = 0UL;
> -#if IS_ENABLED(CONFIG_NFS_V4)
> -	nfsi->nfs4_acl = NULL;
> -#endif /* CONFIG_NFS_V4 */
>  	return &nfsi->vfs_inode;
>  }
>  EXPORT_SYMBOL_GPL(nfs_alloc_inode);
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index eec5c4c..a686251 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -55,6 +55,9 @@
>  #include <linux/xattr.h>
>  #include <linux/utsname.h>
>  #include <linux/freezer.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "delegation.h"
> @@ -2982,15 +2985,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
>  			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
>  		}
>  		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
> -		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
> -				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
> +		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
> +				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
>  				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
>  				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
>  				NFS_CAP_CTIME|NFS_CAP_MTIME|
>  				NFS_CAP_SECURITY_LABEL);
> -		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
> -				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> -			server->caps |= NFS_CAP_ACLS;
> +		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
> +			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> +				server->caps |= NFS_CAP_ALLOW_ACLS;
> +			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
> +				server->caps |= NFS_CAP_DENY_ACLS;
> +		}
>  		if (res.has_links != 0)
>  			server->caps |= NFS_CAP_HARDLINKS;
>  		if (res.has_symlinks != 0)
> @@ -4518,45 +4524,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
>  	return 0;
>  }
>  
> -static inline int nfs4_server_supports_acls(struct nfs_server *server)
> -{
> -	return server->caps & NFS_CAP_ACLS;
> -}
> -
> -/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
> - * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
> - * the stack.
> +/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages on the stack when encoding or decoding acls.
>   */
> -#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
> -
> -static int buf_to_pages_noslab(const void *buf, size_t buflen,
> -		struct page **pages)
> -{
> -	struct page *newpage, **spages;
> -	int rc = 0;
> -	size_t len;
> -	spages = pages;
> -
> -	do {
> -		len = min_t(size_t, PAGE_SIZE, buflen);
> -		newpage = alloc_page(GFP_KERNEL);
> -
> -		if (newpage == NULL)
> -			goto unwind;
> -		memcpy(page_address(newpage), buf, len);
> -                buf += len;
> -                buflen -= len;
> -		*pages++ = newpage;
> -		rc++;
> -	} while (buflen != 0);
> -
> -	return rc;
> -
> -unwind:
> -	for(; rc > 0; rc--)
> -		__free_page(spages[rc-1]);
> -	return -ENOMEM;
> -}
> +#define NFS4ACL_SIZE_MAX 65536
>  
>  struct nfs4_cached_acl {
>  	int cached;
> @@ -4564,66 +4536,9 @@ struct nfs4_cached_acl {
>  	char data[0];
>  };
>  
> -static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -
> -	spin_lock(&inode->i_lock);
> -	kfree(nfsi->nfs4_acl);
> -	nfsi->nfs4_acl = acl;
> -	spin_unlock(&inode->i_lock);
> -}
> -
>  static void nfs4_zap_acl_attr(struct inode *inode)
>  {
> -	nfs4_set_cached_acl(inode, NULL);
> -}
> -
> -static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -	struct nfs4_cached_acl *acl;
> -	int ret = -ENOENT;
> -
> -	spin_lock(&inode->i_lock);
> -	acl = nfsi->nfs4_acl;
> -	if (acl == NULL)
> -		goto out;
> -	if (buf == NULL) /* user is just asking for length */
> -		goto out_len;
> -	if (acl->cached == 0)
> -		goto out;
> -	ret = -ERANGE; /* see getxattr(2) man page */
> -	if (acl->len > buflen)
> -		goto out;
> -	memcpy(buf, acl->data, acl->len);
> -out_len:
> -	ret = acl->len;
> -out:
> -	spin_unlock(&inode->i_lock);
> -	return ret;
> -}
> -
> -static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
> -{
> -	struct nfs4_cached_acl *acl;
> -	size_t buflen = sizeof(*acl) + acl_len;
> -
> -	if (buflen <= PAGE_SIZE) {
> -		acl = kmalloc(buflen, GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 1;
> -		_copy_from_pages(acl->data, pages, pgbase, acl_len);
> -	} else {
> -		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 0;
> -	}
> -	acl->len = acl_len;
> -out:
> -	nfs4_set_cached_acl(inode, acl);
> +	forget_cached_richacl(inode);
>  }
>  
>  /*
> @@ -4636,121 +4551,269 @@ out:
>   * length. The next getxattr call will then produce another round trip to
>   * the server, this time with the input buf of the required size.
>   */
> -static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
>  {
> -	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
>  	struct nfs_getaclargs args = {
>  		.fh = NFS_FH(inode),
>  		.acl_pages = pages,
> -		.acl_len = buflen,
> +		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
>  	};
>  	struct nfs_getaclres res = {
> -		.acl_len = buflen,
> +		.server = server,
>  	};
>  	struct rpc_message msg = {
>  		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
>  		.rpc_argp = &args,
>  		.rpc_resp = &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
> -	int ret = -ENOMEM, i;
> +	int err, i;
>  
> -	/* As long as we're doing a round trip to the server anyway,
> -	 * let's be prepared for a page of acl data. */
> -	if (npages == 0)
> -		npages = 1;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -
> -	for (i = 0; i < npages; i++) {
> -		pages[i] = alloc_page(GFP_KERNEL);
> -		if (!pages[i])
> +	if (ARRAY_SIZE(pages) > 1) {
> +		/* for decoding across pages */
> +		res.acl_scratch = alloc_page(GFP_KERNEL);
> +		err = -ENOMEM;
> +		if (!res.acl_scratch)
>  			goto out_free;
>  	}
>  
> -	/* for decoding across pages */
> -	res.acl_scratch = alloc_page(GFP_KERNEL);
> -	if (!res.acl_scratch)
> -		goto out_free;
> -
> -	args.acl_len = npages * PAGE_SIZE;
> -
> -	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
> -		__func__, buf, buflen, npages, args.acl_len);
> -	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
> +	dprintk("%s  args.acl_len %zu\n",
> +		__func__, args.acl_len);
> +	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
>  			     &msg, &args.seq_args, &res.seq_res, 0);
> -	if (ret)
> +	if (err)
>  		goto out_free;
>  
> -	/* Handle the case where the passed-in buffer is too short */
> -	if (res.acl_flags & NFS4_ACL_TRUNC) {
> -		/* Did the user only issue a request for the acl length? */
> -		if (buf == NULL)
> -			goto out_ok;
> -		ret = -ERANGE;
> -		goto out_free;
> -	}
> -	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
> -	if (buf) {
> -		if (res.acl_len > buflen) {
> -			ret = -ERANGE;
> -			goto out_free;
> -		}
> -		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
> -	}
> -out_ok:
> -	ret = res.acl_len;
> +	richacl_compute_max_masks(res.acl);
> +	/* FIXME: Set inode->i_mode from res->mode?  */
> +	set_cached_richacl(inode, res.acl);
> +	err = 0;
> +
>  out_free:
> -	for (i = 0; i < npages; i++)
> -		if (pages[i])
> -			__free_page(pages[i]);
> +	if (err) {
> +		richacl_put(res.acl);
> +		res.acl = ERR_PTR(err);
> +	}
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		__free_page(pages[i]);
>  	if (res.acl_scratch)
>  		__free_page(res.acl_scratch);
> -	return ret;
> +	return res.acl;
>  }
>  
> -static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
>  {
>  	struct nfs4_exception exception = { };
> -	ssize_t ret;
> +	struct richacl *acl;
>  	do {
> -		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
> -		trace_nfs4_get_acl(inode, ret);
> -		if (ret >= 0)
> +		acl = __nfs4_get_acl_uncached(inode);
> +		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
> +		if (!IS_ERR(acl))
>  			break;
> -		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
> +		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
> +			      PTR_ERR(acl), &exception));
>  	} while (exception.retry);
> -	return ret;
> +	return acl;
>  }
>  
> -static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_proc_get_acl(struct inode *inode)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> +	struct richacl *acl;
>  	int ret;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return ERR_PTR(-EOPNOTSUPP);
>  	ret = nfs_revalidate_inode(server, inode);
>  	if (ret < 0)
> -		return ret;
> +		return ERR_PTR(ret);
>  	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
>  		nfs_zap_acl_cache(inode);
> -	ret = nfs4_read_cached_acl(inode, buf, buflen);
> -	if (ret != -ENOENT)
> -		/* -ENOENT is returned if there is no ACL or if there is an ACL
> -		 * but no cached acl data, just the acl length */
> -		return ret;
> -	return nfs4_get_acl_uncached(inode, buf, buflen);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	return nfs4_get_acl_uncached(inode);
> +}
> +
> +static int
> +richacl_supported(struct nfs_server *server, struct richacl *acl)
> +{
> +	struct richace *ace;
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return -EOPNOTSUPP;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_allow(ace)) {
> +			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
> +				return -EINVAL;
> +		} else if (richace_is_deny(ace)) {
> +			if (!(server->caps & NFS_CAP_DENY_ACLS))
> +				return -EINVAL;
> +		} else
> +			return -EINVAL;
> +	}
> +	return 0;
>  }
>  
> -static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int
> +nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
> +		 kuid_t uid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve uid %d to string\n",
> +				from_kuid(&init_user_ns, uid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static int
> +nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
> +		  kgid_t gid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve gid %d to string\n",
> +				from_kgid(&init_user_ns, gid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static unsigned int
> +nfs4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static int
> +nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
> +		    struct richace *ace, struct richacl *acl)
> +{
> +	const char *who;
> +	__be32 *p;
> +
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return -EIO;
> +		}
> +		p = xdr_reserve_space(xdr, 4 + len);
> +		if (!p)
> +			return -EIO;
> +		xdr_encode_opaque(p, who, len);
> +		return 0;
> +	} else {
> +		who = richace_unmapped_identifier(ace, acl);
> +		if (who) {
> +			unsigned int len = strlen(who);
> +
> +			p = xdr_reserve_space(xdr, 4 + len);
> +			if (!p)
> +				return -EIO;
> +			xdr_encode_opaque(p, who, len);
> +			return 0;
> +		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +			return nfs4_encode_group(xdr, server, ace->e_id.gid);
> +		else
> +			return nfs4_encode_user(xdr, server, ace->e_id.uid);
> +	}
> +}
> +
> +static int
> +nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
> +		const struct nfs_server *server)
> +{
> +	int minorversion = server->nfs_client->cl_minorversion;
> +	unsigned int ace_mask = nfs4_ace_mask(minorversion);
> +	struct xdr_stream xdr;
> +	struct xdr_buf buf;
> +	__be32 *p;
> +	struct richace *ace;
> +
> +	/* Reject acls not understood by the server */
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
> +	} else {
> +		if (acl->a_flags)
> +			return -EINVAL;
> +		richacl_for_each_entry(ace, acl) {
> +			if (ace->e_flags & RICHACE_INHERITED_ACE)
> +				return -EINVAL;
> +		}
> +	}
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_mask & ~ace_mask)
> +			return -EINVAL;
> +	}
> +
> +	xdr_init_encode_pages(&xdr, &buf, pages, len);
> +
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(&xdr, 4);
> +		if (!p)
> +			goto fail;
> +		*p = cpu_to_be32(acl ? acl->a_flags : 0);
> +	}
> +
> +	p = xdr_reserve_space(&xdr, 4);
> +	if (!p)
> +		goto fail;
> +	if (!acl) {
> +		*p++ = cpu_to_be32(0);
> +		return buf.len;
> +	}
> +	*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		p = xdr_reserve_space(&xdr, 4*3);
> +		if (!p)
> +			goto fail;
> +		*p++ = cpu_to_be32(ace->e_type);
> +		*p++ = cpu_to_be32(ace->e_flags &
> +			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
> +		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
> +			goto fail;
> +	}
> +
> +	return buf.len;
> +
> +fail:
> +	return -ENOMEM;
> +}
> +
> +static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> -	struct page *pages[NFS4ACL_MAXPAGES];
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
>  	struct nfs_setaclargs arg = {
> +		.server		= server,
>  		.fh		= NFS_FH(inode),
>  		.acl_pages	= pages,
> -		.acl_len	= buflen,
>  	};
>  	struct nfs_setaclres res;
>  	struct rpc_message msg = {
> @@ -4758,16 +4821,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  		.rpc_argp	= &arg,
>  		.rpc_resp	= &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
>  	int ret, i;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
> -	if (i < 0)
> -		return i;
> +	ret = richacl_supported(server, acl);
> +	if (ret)
> +		return ret;
> +
> +	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
> +	if (ret < 0) {
> +		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +			put_page(pages[i]);
> +		return ret;
> +	}
> +	arg.acl_len = ret;
> +
>  	nfs4_inode_return_delegation(inode);
>  	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
>  
> @@ -4775,8 +4842,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	 * Free each page after tx, so the only ref left is
>  	 * held by the network stack
>  	 */
> -	for (; i > 0; i--)
> -		put_page(pages[i-1]);
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		put_page(pages[i]);
>  
>  	/*
>  	 * Acl update can result in inode attribute update.
> @@ -4790,12 +4857,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	return ret;
>  }
>  
> -static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs4_exception exception = { };
>  	int err;
>  	do {
> -		err = __nfs4_proc_set_acl(inode, buf, buflen);
> +		err = __nfs4_proc_set_acl(inode, acl);
>  		trace_nfs4_set_acl(inode, err);
>  		err = nfs4_handle_exception(NFS_SERVER(inode), err,
>  				&exception);
> @@ -6257,34 +6324,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
>  	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
>  }
>  
> +static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
> +				  const void *buf, size_t buflen,
> +				  int flags, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	if (buf) {
> +		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +		error = richacl_apply_masks(&acl, inode->i_uid);
> +	} else {
> +		/*
> +		 * "Remove the acl"; only permissions granted by the mode
> +		 * remain.  We are using the cached mode here which could be
> +		 * outdated; should we do a GETATTR first to narrow down the
> +		 * race window?
> +		 */
> +		acl = richacl_from_mode(inode->i_mode);
> +		error = 0;
> +	}
> +
> +	if (!error)
> +		error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
> +				  void *buf, size_t buflen, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +	umode_t mode = inode->i_mode & S_IFMT;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = -ENODATA;
> +	if (richacl_equiv_mode(acl, &mode) == 0 &&
> +	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
> +		goto out;
> +	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
> +out:
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
> +				      size_t list_len, const char *name,
> +				      size_t name_len, int handler_flags)
> +{
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
> +	size_t len = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return 0;
> +
> +	if (list && len <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, len);
> +	return len;
> +}
> +
>  #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
>  
> +static int richacl_to_nfs4_acl(struct nfs_server *server,
> +			       const struct richacl *acl,
> +			       void *buf, size_t buflen)
> +{
> +	const struct richace *ace;
> +	__be32 *p = buf;
> +	size_t size = 0;
> +
> +	size += sizeof(*p);
> +	if (buflen >= size)
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		char who_buf[IDMAP_NAMESZ];
> +		const char *who = who_buf;
> +		int who_len;
> +
> +		size += 3 * sizeof(*p);
> +		if (buflen >= size) {
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags &
> +					   ~(RICHACE_INHERITED_ACE |
> +					     RICHACE_UNMAPPED_WHO |
> +					     RICHACE_SPECIAL_WHO));
> +			*p++ = cpu_to_be32(ace->e_mask);
> +		}
> +
> +		if (richace_is_unix_user(ace)) {
> +			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
> +						      who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (richace_is_unix_group(ace)) {
> +			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
> +						       who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +			if (!nfs4acl_special_id_to_who(ace->e_id.special,
> +						       &who, &who_len))
> +				return -EIO;
> +		} else {
> +			who = richace_unmapped_identifier(ace, acl);
> +			if (who)
> +				who_len = strlen(who);
> +			else
> +				return -EIO;
> +		}
> +
> +		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
> +		if (buflen >= size) {
> +			unsigned int padding = -who_len & (sizeof(*p) - 1);
> +
> +			*p++ = cpu_to_be32(who_len);
> +			memcpy(p, who, who_len);
> +			memset((char *)p + who_len, 0, padding);
> +			p += DIV_ROUND_UP(who_len, sizeof(*p));
> +		}
> +	}
> +	if (buflen && buflen < size)
> +		return -ERANGE;
> +	return size;
> +}
> +
> +static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
> +					     const void *buf, size_t buflen)
> +{
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	const __be32 *p = buf;
> +	int count, err;
> +
> +	if (buflen < sizeof(*p))
> +		return ERR_PTR(-EINVAL);
> +	count = be32_to_cpu(*p++);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EINVAL);
> +	buflen -= sizeof(*p);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		u32 who_len, size;
> +		int special_id;
> +		char *who;
> +
> +		err = -EINVAL;
> +		if (buflen < 4 * sizeof(*p))
> +			goto out;
> +		ace->e_type = be32_to_cpu(*p++);
> +		ace->e_flags = be32_to_cpu(*p++);
> +		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpu(*p++);
> +		who_len = be32_to_cpu(*p++);
> +		buflen -= 4 * sizeof(*p);
> +		size = ALIGN(who_len, 4);
> +		if (buflen < size || size == 0)
> +			goto out;
> +		who = (char *)p;
> +		special_id = nfs4acl_who_to_special_id(who, who_len);
> +		if (special_id >= 0) {
> +			ace->e_flags |= RICHACE_SPECIAL_WHO;
> +			ace->e_id.special = special_id;
> +		} else {
> +			bool unmappable;
> +
> +			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +				err = nfs_map_group_to_gid(server, who, who_len,
> +							   &ace->e_id.gid);
> +				if (err) {
> +					dprintk("%s: nfs_map_group_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  group nobody and unmappable groups! */
> +				unmappable = gid_eq(ace->e_id.gid,
> +					make_kgid(&init_user_ns, 99));
> +			} else {
> +				err = nfs_map_name_to_uid(server, who, who_len,
> +							  &ace->e_id.uid);
> +				if (err) {
> +					dprintk("%s: nfs_map_name_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  user nobody and unmappable users! */
> +				unmappable = uid_eq(ace->e_id.uid,
> +					make_kuid(&init_user_ns, 99));
> +			}
> +			if (unmappable) {
> +				err = -ENOMEM;
> +				if (richacl_add_unmapped_identifier(&acl, &ace,
> +					who, who_len, GFP_NOFS))
> +					goto out;
> +			}
> +		}
> +		p += size / sizeof(*p);
> +		buflen -= size;
> +	}
> +	err = -EINVAL;
> +	if (buflen != 0)
> +		goto out;
> +	err = 0;
> +
> +out:
> +	if (err) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(err);
> +	}
> +	return acl;
> +}

I'm not a fan of the "one giant function" approach.  Is there any way to split richacl_from_nfs4_acl() into several smaller functions?

Thanks,
Anna

> +
>  static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
>  				   const void *buf, size_t buflen,
>  				   int flags, int type)
>  {
> -	if (strcmp(key, "") != 0)
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (!buf || strcmp(key, "") != 0)
>  		return -EINVAL;
>  
> -	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
> +	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
>  				   void *buf, size_t buflen, int type)
>  {
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
>  	if (strcmp(key, "") != 0)
>  		return -EINVAL;
> -
> -	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
>  				       size_t list_len, const char *name,
>  				       size_t name_len, int type)
>  {
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
>  	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
>  
> -	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
>  		return 0;
>  
>  	if (list && len <= list_len)
> @@ -8837,6 +9153,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
>  	.clone_server	= nfs_clone_server,
>  };
>  
> +static const struct xattr_handler nfs4_xattr_richacl_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= nfs4_xattr_list_richacl,
> +	.get	= nfs4_xattr_get_richacl,
> +	.set	= nfs4_xattr_set_richacl,
> +};
> +
>  static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  	.prefix	= XATTR_NAME_NFSV4_ACL,
>  	.list	= nfs4_xattr_list_nfs4_acl,
> @@ -8845,6 +9168,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  };
>  
>  const struct xattr_handler *nfs4_xattr_handlers[] = {
> +	&nfs4_xattr_richacl_handler,
>  	&nfs4_xattr_nfs4_acl_handler,
>  #ifdef CONFIG_NFS_V4_SECURITY_LABEL
>  	&nfs4_xattr_nfs4_label_handler,
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index eefed15..f2507d7 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -52,6 +52,10 @@
>  #include <linux/nfs.h>
>  #include <linux/nfs4.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/nfs_idmap.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "internal.h"
> @@ -1650,16 +1654,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
>  static void
>  encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
>  {
> -	__be32 *p;
> +	int attrlen_offset;
> +	__be32 attrlen, *p;
>  
>  	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
>  	encode_nfs4_stateid(xdr, &zero_stateid);
> +
> +	/* Encode attribute bitmap. */
>  	p = reserve_space(xdr, 2*4);
>  	*p++ = cpu_to_be32(1);
>  	*p = cpu_to_be32(FATTR4_WORD0_ACL);
> -	p = reserve_space(xdr, 4);
> -	*p = cpu_to_be32(arg->acl_len);
> +
> +	attrlen_offset = xdr->buf->len;
> +	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
> +
>  	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
> +
> +	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
> +	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
>  }
>  
>  static void
> @@ -2488,7 +2500,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
>  	encode_sequence(xdr, &args->seq_args, &hdr);
>  	encode_putfh(xdr, args->fh, &hdr);
>  	replen = hdr.replen + op_decode_hdr_maxsz + 1;
> -	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
> +	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
>  
>  	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
>  		args->acl_pages, 0, args->acl_len);
> @@ -5260,24 +5272,135 @@ decode_restorefh(struct xdr_stream *xdr)
>  	return decode_op_hdr(xdr, OP_RESTOREFH);
>  }
>  
> +static int
> +nfs4_decode_ace_who(struct richace *ace,
> +		    const char **unmapped, unsigned int *unmapped_len,
> +		    const struct nfs_server *server,
> +		    struct xdr_stream *xdr)
> +{
> +	char *who;
> +	u32 len;
> +	int special_id;
> +	__be32 *p;
> +	int error;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (!p)
> +		return -ENOMEM;  /* acl truncated */
> +	len = be32_to_cpup(p++);
> +	if (len >= XDR_MAX_NETOBJ) {
> +		dprintk("%s: name too long (%u)!\n",
> +			__func__, len);
> +		return -EIO;
> +	}
> +	who = (char *)xdr_inline_decode(xdr, len);
> +	if (!who)
> +		return -ENOMEM;  /* acl truncated */
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return 0;
> +	}
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
> +		if (error) {
> +			dprintk("%s: nfs_map_group_to_gid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between group nobody and
> +			  unmappable groups! */
> +		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	} else {
> +		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
> +		if (error) {
> +			dprintk("%s: nfs_map_name_to_uid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between user nobody and
> +			  unmappable users! */
> +		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static struct richacl *
> +decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
> +{
> +	struct richacl *acl;
> +	struct richace *ace;
> +	uint32_t count;
> +	__be32 *p;
> +	int status;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (unlikely(!p))
> +		return ERR_PTR(-ENOMEM);  /* acl truncated */
> +	count = be32_to_cpup(p);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EIO);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		const char *unmapped = NULL;
> +		unsigned int unmapped_len;
> +
> +		p = xdr_inline_decode(xdr, 4*3);
> +		status = -ENOMEM;
> +		if (unlikely(!p))
> +			goto out;  /* acl truncated */
> +		ace->e_type = be32_to_cpup(p++);
> +		ace->e_flags = be32_to_cpup(p++);
> +		status = -EIO;
> +		if (ace->e_flags &
> +		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpup(p++);
> +		status = nfs4_decode_ace_who(ace, &unmapped,
> +					     &unmapped_len, server,
> +					     xdr);
> +		if (status)
> +			goto out;
> +		if (unmapped) {
> +			status = -ENOMEM;
> +			if (richacl_add_unmapped_identifier(&acl, &ace,
> +					unmapped, unmapped_len,
> +					GFP_NOFS))
> +				goto out;
> +		}
> +	}
> +	status = 0;
> +
> +out:
> +	if (status) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(status);
> +	}
> +	return acl;
> +}
> +
>  static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  			 struct nfs_getaclres *res)
>  {
>  	unsigned int savep;
>  	uint32_t attrlen,
>  		 bitmap[3] = {0};
> +	struct richacl *acl = NULL;
>  	int status;
> -	unsigned int pg_offset;
>  
> -	res->acl_len = 0;
>  	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
>  		goto out;
> -
> -	xdr_enter_page(xdr, xdr->buf->page_len);
> -
> -	/* Calculate the offset of the page data */
> -	pg_offset = xdr->buf->head[0].iov_len;
> -
>  	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
>  		goto out;
>  	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
> @@ -5286,24 +5409,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
>  		return -EIO;
>  	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
> -
> -		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
> -		 * are stored with the acl data to handle the problem of
> -		 * variable length bitmaps.*/
> -		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
> -		res->acl_len = attrlen;
> -
> -		/* Check for receive buffer overflow */
> -		if (res->acl_len > (xdr->nwords << 2) ||
> -		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
> -			res->acl_flags |= NFS4_ACL_TRUNC;
> -			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
> -					attrlen, xdr->nwords << 2);
> -		}
> +		acl = decode_acl_entries(xdr, res->server);
> +		status = PTR_ERR(acl);
> +		if (IS_ERR(acl))
> +			goto out;
> +		bitmap[0] &= ~FATTR4_WORD0_ACL;
>  	} else
>  		status = -EOPNOTSUPP;
>  
> +	status = -EIO;
> +	if (unlikely(bitmap[0]))
> +		goto out;
> +
> +	status = decode_attr_mode(xdr, bitmap, &res->mode);
> +	if (status < 0)
> +		goto out;
> +	status = 0;
> +
>  out:
> +	if (status == 0)
> +		res->acl = acl;
> +	else
> +		richacl_put(acl);
>  	return status;
>  }
>  
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 383a027..8ced33d 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -2319,7 +2319,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  		sb->s_time_gran = 1;
>  	}
>  
> @@ -2346,7 +2346,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  	}
>  
>   	nfs_initialise_sb(sb);
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index c0e9614..b84e194 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -176,7 +176,6 @@ struct nfs_inode {
>  	wait_queue_head_t	waitqueue;
>  
>  #if IS_ENABLED(CONFIG_NFS_V4)
> -	struct nfs4_cached_acl	*nfs4_acl;
>          /* NFSv4 state */
>  	struct list_head	open_states;
>  	struct nfs_delegation __rcu *delegation;
> diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
> index 570a7df..6c41668 100644
> --- a/include/linux/nfs_fs_sb.h
> +++ b/include/linux/nfs_fs_sb.h
> @@ -243,5 +243,7 @@ struct nfs_server {
>  #define NFS_CAP_ALLOCATE	(1U << 20)
>  #define NFS_CAP_DEALLOCATE	(1U << 21)
>  #define NFS_CAP_LAYOUTSTATS	(1U << 22)
> +#define NFS_CAP_ALLOW_ACLS	(1U << 23)
> +#define NFS_CAP_DENY_ACLS	(1U << 24)
>  
>  #endif
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index 090ade4..337c341 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -683,9 +683,10 @@ struct nfs_setattrargs {
>  
>  struct nfs_setaclargs {
>  	struct nfs4_sequence_args	seq_args;
> +	const struct nfs_server *	server;
>  	struct nfs_fh *			fh;
> -	size_t				acl_len;
>  	struct page **			acl_pages;
> +	size_t				acl_len;
>  };
>  
>  struct nfs_setaclres {
> @@ -703,9 +704,9 @@ struct nfs_getaclargs {
>  #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
>  struct nfs_getaclres {
>  	struct nfs4_sequence_res	seq_res;
> -	size_t				acl_len;
> -	size_t				acl_data_offset;
> -	int				acl_flags;
> +	const struct nfs_server *	server;
> +	struct richacl *		acl;
> +	umode_t				mode;
>  	struct page *			acl_scratch;
>  };
>  
> 

WARNING: multiple messages have this Message-ID (diff)
From: Anna Schumaker <Anna.Schumaker@netapp.com>
To: Andreas Gruenbacher <andreas.gruenbacher@gmail.com>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	"Theodore Ts'o" <tytso@mit.edu>,
	Andreas Dilger <adilger.kernel@dilger.ca>,
	"J. Bruce Fields" <bfields@fieldses.org>,
	Jeff Layton <jlayton@poochiereds.net>,
	Trond Myklebust <trond.myklebust@primarydata.com>,
	Anna Schumaker <anna.schumaker@netapp.com>,
	Dave Chinner <david@fromorbit.com>, <linux-ext4@vger.kernel.org>,
	<xfs@oss.sgi.com>, <linux-kernel@vger.kernel.org>,
	<linux-fsdevel@vger.kernel.org>, <linux-nfs@vger.kernel.org>,
	<linux-cifs@vger.kernel.org>, <linux-api@vger.kernel.org>
Cc: Andreas Gruenbacher <agruenba@redhat.com>
Subject: Re: [PATCH v10 44/46] nfs: Add richacl support
Date: Mon, 12 Oct 2015 10:39:26 -0400	[thread overview]
Message-ID: <561BC61E.6010704@Netapp.com> (raw)
In-Reply-To: <1444604337-17651-45-git-send-email-andreas.gruenbacher@gmail.com>

Hi Andreas,

On 10/11/2015 06:58 PM, Andreas Gruenbacher wrote:
> From: Andreas Gruenbacher <agruenba@redhat.com>
> 
> Add support for the "system.richacl" xattr in nfs.  The existing
> "system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
> and gids; the "system.richacl" xattr does, and only keeps the
> on-the-wire names when there is no mapping.  This allows to copy
> permissions across different file systems.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfs/inode.c            |   3 -
>  fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
>  fs/nfs/nfs4xdr.c          | 179 ++++++++++--
>  fs/nfs/super.c            |   4 +-
>  include/linux/nfs_fs.h    |   1 -
>  include/linux/nfs_fs_sb.h |   2 +
>  include/linux/nfs_xdr.h   |   9 +-
>  7 files changed, 673 insertions(+), 223 deletions(-)
> 
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 326d9e1..843d15d 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -1852,9 +1852,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
>  		return NULL;
>  	nfsi->flags = 0UL;
>  	nfsi->cache_validity = 0UL;
> -#if IS_ENABLED(CONFIG_NFS_V4)
> -	nfsi->nfs4_acl = NULL;
> -#endif /* CONFIG_NFS_V4 */
>  	return &nfsi->vfs_inode;
>  }
>  EXPORT_SYMBOL_GPL(nfs_alloc_inode);
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index eec5c4c..a686251 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -55,6 +55,9 @@
>  #include <linux/xattr.h>
>  #include <linux/utsname.h>
>  #include <linux/freezer.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "delegation.h"
> @@ -2982,15 +2985,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
>  			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
>  		}
>  		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
> -		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
> -				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
> +		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
> +				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
>  				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
>  				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
>  				NFS_CAP_CTIME|NFS_CAP_MTIME|
>  				NFS_CAP_SECURITY_LABEL);
> -		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
> -				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> -			server->caps |= NFS_CAP_ACLS;
> +		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
> +			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> +				server->caps |= NFS_CAP_ALLOW_ACLS;
> +			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
> +				server->caps |= NFS_CAP_DENY_ACLS;
> +		}
>  		if (res.has_links != 0)
>  			server->caps |= NFS_CAP_HARDLINKS;
>  		if (res.has_symlinks != 0)
> @@ -4518,45 +4524,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
>  	return 0;
>  }
>  
> -static inline int nfs4_server_supports_acls(struct nfs_server *server)
> -{
> -	return server->caps & NFS_CAP_ACLS;
> -}
> -
> -/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
> - * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
> - * the stack.
> +/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages on the stack when encoding or decoding acls.
>   */
> -#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
> -
> -static int buf_to_pages_noslab(const void *buf, size_t buflen,
> -		struct page **pages)
> -{
> -	struct page *newpage, **spages;
> -	int rc = 0;
> -	size_t len;
> -	spages = pages;
> -
> -	do {
> -		len = min_t(size_t, PAGE_SIZE, buflen);
> -		newpage = alloc_page(GFP_KERNEL);
> -
> -		if (newpage == NULL)
> -			goto unwind;
> -		memcpy(page_address(newpage), buf, len);
> -                buf += len;
> -                buflen -= len;
> -		*pages++ = newpage;
> -		rc++;
> -	} while (buflen != 0);
> -
> -	return rc;
> -
> -unwind:
> -	for(; rc > 0; rc--)
> -		__free_page(spages[rc-1]);
> -	return -ENOMEM;
> -}
> +#define NFS4ACL_SIZE_MAX 65536
>  
>  struct nfs4_cached_acl {
>  	int cached;
> @@ -4564,66 +4536,9 @@ struct nfs4_cached_acl {
>  	char data[0];
>  };
>  
> -static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -
> -	spin_lock(&inode->i_lock);
> -	kfree(nfsi->nfs4_acl);
> -	nfsi->nfs4_acl = acl;
> -	spin_unlock(&inode->i_lock);
> -}
> -
>  static void nfs4_zap_acl_attr(struct inode *inode)
>  {
> -	nfs4_set_cached_acl(inode, NULL);
> -}
> -
> -static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -	struct nfs4_cached_acl *acl;
> -	int ret = -ENOENT;
> -
> -	spin_lock(&inode->i_lock);
> -	acl = nfsi->nfs4_acl;
> -	if (acl == NULL)
> -		goto out;
> -	if (buf == NULL) /* user is just asking for length */
> -		goto out_len;
> -	if (acl->cached == 0)
> -		goto out;
> -	ret = -ERANGE; /* see getxattr(2) man page */
> -	if (acl->len > buflen)
> -		goto out;
> -	memcpy(buf, acl->data, acl->len);
> -out_len:
> -	ret = acl->len;
> -out:
> -	spin_unlock(&inode->i_lock);
> -	return ret;
> -}
> -
> -static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
> -{
> -	struct nfs4_cached_acl *acl;
> -	size_t buflen = sizeof(*acl) + acl_len;
> -
> -	if (buflen <= PAGE_SIZE) {
> -		acl = kmalloc(buflen, GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 1;
> -		_copy_from_pages(acl->data, pages, pgbase, acl_len);
> -	} else {
> -		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 0;
> -	}
> -	acl->len = acl_len;
> -out:
> -	nfs4_set_cached_acl(inode, acl);
> +	forget_cached_richacl(inode);
>  }
>  
>  /*
> @@ -4636,121 +4551,269 @@ out:
>   * length. The next getxattr call will then produce another round trip to
>   * the server, this time with the input buf of the required size.
>   */
> -static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
>  {
> -	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
>  	struct nfs_getaclargs args = {
>  		.fh = NFS_FH(inode),
>  		.acl_pages = pages,
> -		.acl_len = buflen,
> +		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
>  	};
>  	struct nfs_getaclres res = {
> -		.acl_len = buflen,
> +		.server = server,
>  	};
>  	struct rpc_message msg = {
>  		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
>  		.rpc_argp = &args,
>  		.rpc_resp = &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
> -	int ret = -ENOMEM, i;
> +	int err, i;
>  
> -	/* As long as we're doing a round trip to the server anyway,
> -	 * let's be prepared for a page of acl data. */
> -	if (npages == 0)
> -		npages = 1;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -
> -	for (i = 0; i < npages; i++) {
> -		pages[i] = alloc_page(GFP_KERNEL);
> -		if (!pages[i])
> +	if (ARRAY_SIZE(pages) > 1) {
> +		/* for decoding across pages */
> +		res.acl_scratch = alloc_page(GFP_KERNEL);
> +		err = -ENOMEM;
> +		if (!res.acl_scratch)
>  			goto out_free;
>  	}
>  
> -	/* for decoding across pages */
> -	res.acl_scratch = alloc_page(GFP_KERNEL);
> -	if (!res.acl_scratch)
> -		goto out_free;
> -
> -	args.acl_len = npages * PAGE_SIZE;
> -
> -	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
> -		__func__, buf, buflen, npages, args.acl_len);
> -	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
> +	dprintk("%s  args.acl_len %zu\n",
> +		__func__, args.acl_len);
> +	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
>  			     &msg, &args.seq_args, &res.seq_res, 0);
> -	if (ret)
> +	if (err)
>  		goto out_free;
>  
> -	/* Handle the case where the passed-in buffer is too short */
> -	if (res.acl_flags & NFS4_ACL_TRUNC) {
> -		/* Did the user only issue a request for the acl length? */
> -		if (buf == NULL)
> -			goto out_ok;
> -		ret = -ERANGE;
> -		goto out_free;
> -	}
> -	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
> -	if (buf) {
> -		if (res.acl_len > buflen) {
> -			ret = -ERANGE;
> -			goto out_free;
> -		}
> -		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
> -	}
> -out_ok:
> -	ret = res.acl_len;
> +	richacl_compute_max_masks(res.acl);
> +	/* FIXME: Set inode->i_mode from res->mode?  */
> +	set_cached_richacl(inode, res.acl);
> +	err = 0;
> +
>  out_free:
> -	for (i = 0; i < npages; i++)
> -		if (pages[i])
> -			__free_page(pages[i]);
> +	if (err) {
> +		richacl_put(res.acl);
> +		res.acl = ERR_PTR(err);
> +	}
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		__free_page(pages[i]);
>  	if (res.acl_scratch)
>  		__free_page(res.acl_scratch);
> -	return ret;
> +	return res.acl;
>  }
>  
> -static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
>  {
>  	struct nfs4_exception exception = { };
> -	ssize_t ret;
> +	struct richacl *acl;
>  	do {
> -		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
> -		trace_nfs4_get_acl(inode, ret);
> -		if (ret >= 0)
> +		acl = __nfs4_get_acl_uncached(inode);
> +		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
> +		if (!IS_ERR(acl))
>  			break;
> -		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
> +		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
> +			      PTR_ERR(acl), &exception));
>  	} while (exception.retry);
> -	return ret;
> +	return acl;
>  }
>  
> -static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_proc_get_acl(struct inode *inode)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> +	struct richacl *acl;
>  	int ret;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return ERR_PTR(-EOPNOTSUPP);
>  	ret = nfs_revalidate_inode(server, inode);
>  	if (ret < 0)
> -		return ret;
> +		return ERR_PTR(ret);
>  	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
>  		nfs_zap_acl_cache(inode);
> -	ret = nfs4_read_cached_acl(inode, buf, buflen);
> -	if (ret != -ENOENT)
> -		/* -ENOENT is returned if there is no ACL or if there is an ACL
> -		 * but no cached acl data, just the acl length */
> -		return ret;
> -	return nfs4_get_acl_uncached(inode, buf, buflen);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	return nfs4_get_acl_uncached(inode);
> +}
> +
> +static int
> +richacl_supported(struct nfs_server *server, struct richacl *acl)
> +{
> +	struct richace *ace;
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return -EOPNOTSUPP;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_allow(ace)) {
> +			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
> +				return -EINVAL;
> +		} else if (richace_is_deny(ace)) {
> +			if (!(server->caps & NFS_CAP_DENY_ACLS))
> +				return -EINVAL;
> +		} else
> +			return -EINVAL;
> +	}
> +	return 0;
>  }
>  
> -static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int
> +nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
> +		 kuid_t uid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve uid %d to string\n",
> +				from_kuid(&init_user_ns, uid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static int
> +nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
> +		  kgid_t gid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve gid %d to string\n",
> +				from_kgid(&init_user_ns, gid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static unsigned int
> +nfs4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static int
> +nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
> +		    struct richace *ace, struct richacl *acl)
> +{
> +	const char *who;
> +	__be32 *p;
> +
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return -EIO;
> +		}
> +		p = xdr_reserve_space(xdr, 4 + len);
> +		if (!p)
> +			return -EIO;
> +		xdr_encode_opaque(p, who, len);
> +		return 0;
> +	} else {
> +		who = richace_unmapped_identifier(ace, acl);
> +		if (who) {
> +			unsigned int len = strlen(who);
> +
> +			p = xdr_reserve_space(xdr, 4 + len);
> +			if (!p)
> +				return -EIO;
> +			xdr_encode_opaque(p, who, len);
> +			return 0;
> +		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +			return nfs4_encode_group(xdr, server, ace->e_id.gid);
> +		else
> +			return nfs4_encode_user(xdr, server, ace->e_id.uid);
> +	}
> +}
> +
> +static int
> +nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
> +		const struct nfs_server *server)
> +{
> +	int minorversion = server->nfs_client->cl_minorversion;
> +	unsigned int ace_mask = nfs4_ace_mask(minorversion);
> +	struct xdr_stream xdr;
> +	struct xdr_buf buf;
> +	__be32 *p;
> +	struct richace *ace;
> +
> +	/* Reject acls not understood by the server */
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
> +	} else {
> +		if (acl->a_flags)
> +			return -EINVAL;
> +		richacl_for_each_entry(ace, acl) {
> +			if (ace->e_flags & RICHACE_INHERITED_ACE)
> +				return -EINVAL;
> +		}
> +	}
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_mask & ~ace_mask)
> +			return -EINVAL;
> +	}
> +
> +	xdr_init_encode_pages(&xdr, &buf, pages, len);
> +
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(&xdr, 4);
> +		if (!p)
> +			goto fail;
> +		*p = cpu_to_be32(acl ? acl->a_flags : 0);
> +	}
> +
> +	p = xdr_reserve_space(&xdr, 4);
> +	if (!p)
> +		goto fail;
> +	if (!acl) {
> +		*p++ = cpu_to_be32(0);
> +		return buf.len;
> +	}
> +	*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		p = xdr_reserve_space(&xdr, 4*3);
> +		if (!p)
> +			goto fail;
> +		*p++ = cpu_to_be32(ace->e_type);
> +		*p++ = cpu_to_be32(ace->e_flags &
> +			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
> +		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
> +			goto fail;
> +	}
> +
> +	return buf.len;
> +
> +fail:
> +	return -ENOMEM;
> +}
> +
> +static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> -	struct page *pages[NFS4ACL_MAXPAGES];
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
>  	struct nfs_setaclargs arg = {
> +		.server		= server,
>  		.fh		= NFS_FH(inode),
>  		.acl_pages	= pages,
> -		.acl_len	= buflen,
>  	};
>  	struct nfs_setaclres res;
>  	struct rpc_message msg = {
> @@ -4758,16 +4821,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  		.rpc_argp	= &arg,
>  		.rpc_resp	= &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
>  	int ret, i;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
> -	if (i < 0)
> -		return i;
> +	ret = richacl_supported(server, acl);
> +	if (ret)
> +		return ret;
> +
> +	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
> +	if (ret < 0) {
> +		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +			put_page(pages[i]);
> +		return ret;
> +	}
> +	arg.acl_len = ret;
> +
>  	nfs4_inode_return_delegation(inode);
>  	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
>  
> @@ -4775,8 +4842,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	 * Free each page after tx, so the only ref left is
>  	 * held by the network stack
>  	 */
> -	for (; i > 0; i--)
> -		put_page(pages[i-1]);
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		put_page(pages[i]);
>  
>  	/*
>  	 * Acl update can result in inode attribute update.
> @@ -4790,12 +4857,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	return ret;
>  }
>  
> -static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs4_exception exception = { };
>  	int err;
>  	do {
> -		err = __nfs4_proc_set_acl(inode, buf, buflen);
> +		err = __nfs4_proc_set_acl(inode, acl);
>  		trace_nfs4_set_acl(inode, err);
>  		err = nfs4_handle_exception(NFS_SERVER(inode), err,
>  				&exception);
> @@ -6257,34 +6324,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
>  	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
>  }
>  
> +static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
> +				  const void *buf, size_t buflen,
> +				  int flags, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	if (buf) {
> +		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +		error = richacl_apply_masks(&acl, inode->i_uid);
> +	} else {
> +		/*
> +		 * "Remove the acl"; only permissions granted by the mode
> +		 * remain.  We are using the cached mode here which could be
> +		 * outdated; should we do a GETATTR first to narrow down the
> +		 * race window?
> +		 */
> +		acl = richacl_from_mode(inode->i_mode);
> +		error = 0;
> +	}
> +
> +	if (!error)
> +		error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
> +				  void *buf, size_t buflen, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +	umode_t mode = inode->i_mode & S_IFMT;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = -ENODATA;
> +	if (richacl_equiv_mode(acl, &mode) == 0 &&
> +	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
> +		goto out;
> +	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
> +out:
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
> +				      size_t list_len, const char *name,
> +				      size_t name_len, int handler_flags)
> +{
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
> +	size_t len = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return 0;
> +
> +	if (list && len <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, len);
> +	return len;
> +}
> +
>  #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
>  
> +static int richacl_to_nfs4_acl(struct nfs_server *server,
> +			       const struct richacl *acl,
> +			       void *buf, size_t buflen)
> +{
> +	const struct richace *ace;
> +	__be32 *p = buf;
> +	size_t size = 0;
> +
> +	size += sizeof(*p);
> +	if (buflen >= size)
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		char who_buf[IDMAP_NAMESZ];
> +		const char *who = who_buf;
> +		int who_len;
> +
> +		size += 3 * sizeof(*p);
> +		if (buflen >= size) {
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags &
> +					   ~(RICHACE_INHERITED_ACE |
> +					     RICHACE_UNMAPPED_WHO |
> +					     RICHACE_SPECIAL_WHO));
> +			*p++ = cpu_to_be32(ace->e_mask);
> +		}
> +
> +		if (richace_is_unix_user(ace)) {
> +			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
> +						      who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (richace_is_unix_group(ace)) {
> +			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
> +						       who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +			if (!nfs4acl_special_id_to_who(ace->e_id.special,
> +						       &who, &who_len))
> +				return -EIO;
> +		} else {
> +			who = richace_unmapped_identifier(ace, acl);
> +			if (who)
> +				who_len = strlen(who);
> +			else
> +				return -EIO;
> +		}
> +
> +		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
> +		if (buflen >= size) {
> +			unsigned int padding = -who_len & (sizeof(*p) - 1);
> +
> +			*p++ = cpu_to_be32(who_len);
> +			memcpy(p, who, who_len);
> +			memset((char *)p + who_len, 0, padding);
> +			p += DIV_ROUND_UP(who_len, sizeof(*p));
> +		}
> +	}
> +	if (buflen && buflen < size)
> +		return -ERANGE;
> +	return size;
> +}
> +
> +static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
> +					     const void *buf, size_t buflen)
> +{
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	const __be32 *p = buf;
> +	int count, err;
> +
> +	if (buflen < sizeof(*p))
> +		return ERR_PTR(-EINVAL);
> +	count = be32_to_cpu(*p++);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EINVAL);
> +	buflen -= sizeof(*p);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		u32 who_len, size;
> +		int special_id;
> +		char *who;
> +
> +		err = -EINVAL;
> +		if (buflen < 4 * sizeof(*p))
> +			goto out;
> +		ace->e_type = be32_to_cpu(*p++);
> +		ace->e_flags = be32_to_cpu(*p++);
> +		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpu(*p++);
> +		who_len = be32_to_cpu(*p++);
> +		buflen -= 4 * sizeof(*p);
> +		size = ALIGN(who_len, 4);
> +		if (buflen < size || size == 0)
> +			goto out;
> +		who = (char *)p;
> +		special_id = nfs4acl_who_to_special_id(who, who_len);
> +		if (special_id >= 0) {
> +			ace->e_flags |= RICHACE_SPECIAL_WHO;
> +			ace->e_id.special = special_id;
> +		} else {
> +			bool unmappable;
> +
> +			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +				err = nfs_map_group_to_gid(server, who, who_len,
> +							   &ace->e_id.gid);
> +				if (err) {
> +					dprintk("%s: nfs_map_group_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  group nobody and unmappable groups! */
> +				unmappable = gid_eq(ace->e_id.gid,
> +					make_kgid(&init_user_ns, 99));
> +			} else {
> +				err = nfs_map_name_to_uid(server, who, who_len,
> +							  &ace->e_id.uid);
> +				if (err) {
> +					dprintk("%s: nfs_map_name_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  user nobody and unmappable users! */
> +				unmappable = uid_eq(ace->e_id.uid,
> +					make_kuid(&init_user_ns, 99));
> +			}
> +			if (unmappable) {
> +				err = -ENOMEM;
> +				if (richacl_add_unmapped_identifier(&acl, &ace,
> +					who, who_len, GFP_NOFS))
> +					goto out;
> +			}
> +		}
> +		p += size / sizeof(*p);
> +		buflen -= size;
> +	}
> +	err = -EINVAL;
> +	if (buflen != 0)
> +		goto out;
> +	err = 0;
> +
> +out:
> +	if (err) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(err);
> +	}
> +	return acl;
> +}

I'm not a fan of the "one giant function" approach.  Is there any way to split richacl_from_nfs4_acl() into several smaller functions?

Thanks,
Anna

> +
>  static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
>  				   const void *buf, size_t buflen,
>  				   int flags, int type)
>  {
> -	if (strcmp(key, "") != 0)
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (!buf || strcmp(key, "") != 0)
>  		return -EINVAL;
>  
> -	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
> +	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
>  				   void *buf, size_t buflen, int type)
>  {
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
>  	if (strcmp(key, "") != 0)
>  		return -EINVAL;
> -
> -	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
>  				       size_t list_len, const char *name,
>  				       size_t name_len, int type)
>  {
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
>  	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
>  
> -	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
>  		return 0;
>  
>  	if (list && len <= list_len)
> @@ -8837,6 +9153,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
>  	.clone_server	= nfs_clone_server,
>  };
>  
> +static const struct xattr_handler nfs4_xattr_richacl_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= nfs4_xattr_list_richacl,
> +	.get	= nfs4_xattr_get_richacl,
> +	.set	= nfs4_xattr_set_richacl,
> +};
> +
>  static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  	.prefix	= XATTR_NAME_NFSV4_ACL,
>  	.list	= nfs4_xattr_list_nfs4_acl,
> @@ -8845,6 +9168,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  };
>  
>  const struct xattr_handler *nfs4_xattr_handlers[] = {
> +	&nfs4_xattr_richacl_handler,
>  	&nfs4_xattr_nfs4_acl_handler,
>  #ifdef CONFIG_NFS_V4_SECURITY_LABEL
>  	&nfs4_xattr_nfs4_label_handler,
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index eefed15..f2507d7 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -52,6 +52,10 @@
>  #include <linux/nfs.h>
>  #include <linux/nfs4.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/nfs_idmap.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "internal.h"
> @@ -1650,16 +1654,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
>  static void
>  encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
>  {
> -	__be32 *p;
> +	int attrlen_offset;
> +	__be32 attrlen, *p;
>  
>  	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
>  	encode_nfs4_stateid(xdr, &zero_stateid);
> +
> +	/* Encode attribute bitmap. */
>  	p = reserve_space(xdr, 2*4);
>  	*p++ = cpu_to_be32(1);
>  	*p = cpu_to_be32(FATTR4_WORD0_ACL);
> -	p = reserve_space(xdr, 4);
> -	*p = cpu_to_be32(arg->acl_len);
> +
> +	attrlen_offset = xdr->buf->len;
> +	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
> +
>  	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
> +
> +	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
> +	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
>  }
>  
>  static void
> @@ -2488,7 +2500,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
>  	encode_sequence(xdr, &args->seq_args, &hdr);
>  	encode_putfh(xdr, args->fh, &hdr);
>  	replen = hdr.replen + op_decode_hdr_maxsz + 1;
> -	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
> +	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
>  
>  	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
>  		args->acl_pages, 0, args->acl_len);
> @@ -5260,24 +5272,135 @@ decode_restorefh(struct xdr_stream *xdr)
>  	return decode_op_hdr(xdr, OP_RESTOREFH);
>  }
>  
> +static int
> +nfs4_decode_ace_who(struct richace *ace,
> +		    const char **unmapped, unsigned int *unmapped_len,
> +		    const struct nfs_server *server,
> +		    struct xdr_stream *xdr)
> +{
> +	char *who;
> +	u32 len;
> +	int special_id;
> +	__be32 *p;
> +	int error;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (!p)
> +		return -ENOMEM;  /* acl truncated */
> +	len = be32_to_cpup(p++);
> +	if (len >= XDR_MAX_NETOBJ) {
> +		dprintk("%s: name too long (%u)!\n",
> +			__func__, len);
> +		return -EIO;
> +	}
> +	who = (char *)xdr_inline_decode(xdr, len);
> +	if (!who)
> +		return -ENOMEM;  /* acl truncated */
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return 0;
> +	}
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
> +		if (error) {
> +			dprintk("%s: nfs_map_group_to_gid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between group nobody and
> +			  unmappable groups! */
> +		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	} else {
> +		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
> +		if (error) {
> +			dprintk("%s: nfs_map_name_to_uid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between user nobody and
> +			  unmappable users! */
> +		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static struct richacl *
> +decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
> +{
> +	struct richacl *acl;
> +	struct richace *ace;
> +	uint32_t count;
> +	__be32 *p;
> +	int status;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (unlikely(!p))
> +		return ERR_PTR(-ENOMEM);  /* acl truncated */
> +	count = be32_to_cpup(p);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EIO);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		const char *unmapped = NULL;
> +		unsigned int unmapped_len;
> +
> +		p = xdr_inline_decode(xdr, 4*3);
> +		status = -ENOMEM;
> +		if (unlikely(!p))
> +			goto out;  /* acl truncated */
> +		ace->e_type = be32_to_cpup(p++);
> +		ace->e_flags = be32_to_cpup(p++);
> +		status = -EIO;
> +		if (ace->e_flags &
> +		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpup(p++);
> +		status = nfs4_decode_ace_who(ace, &unmapped,
> +					     &unmapped_len, server,
> +					     xdr);
> +		if (status)
> +			goto out;
> +		if (unmapped) {
> +			status = -ENOMEM;
> +			if (richacl_add_unmapped_identifier(&acl, &ace,
> +					unmapped, unmapped_len,
> +					GFP_NOFS))
> +				goto out;
> +		}
> +	}
> +	status = 0;
> +
> +out:
> +	if (status) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(status);
> +	}
> +	return acl;
> +}
> +
>  static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  			 struct nfs_getaclres *res)
>  {
>  	unsigned int savep;
>  	uint32_t attrlen,
>  		 bitmap[3] = {0};
> +	struct richacl *acl = NULL;
>  	int status;
> -	unsigned int pg_offset;
>  
> -	res->acl_len = 0;
>  	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
>  		goto out;
> -
> -	xdr_enter_page(xdr, xdr->buf->page_len);
> -
> -	/* Calculate the offset of the page data */
> -	pg_offset = xdr->buf->head[0].iov_len;
> -
>  	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
>  		goto out;
>  	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
> @@ -5286,24 +5409,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
>  		return -EIO;
>  	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
> -
> -		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
> -		 * are stored with the acl data to handle the problem of
> -		 * variable length bitmaps.*/
> -		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
> -		res->acl_len = attrlen;
> -
> -		/* Check for receive buffer overflow */
> -		if (res->acl_len > (xdr->nwords << 2) ||
> -		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
> -			res->acl_flags |= NFS4_ACL_TRUNC;
> -			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
> -					attrlen, xdr->nwords << 2);
> -		}
> +		acl = decode_acl_entries(xdr, res->server);
> +		status = PTR_ERR(acl);
> +		if (IS_ERR(acl))
> +			goto out;
> +		bitmap[0] &= ~FATTR4_WORD0_ACL;
>  	} else
>  		status = -EOPNOTSUPP;
>  
> +	status = -EIO;
> +	if (unlikely(bitmap[0]))
> +		goto out;
> +
> +	status = decode_attr_mode(xdr, bitmap, &res->mode);
> +	if (status < 0)
> +		goto out;
> +	status = 0;
> +
>  out:
> +	if (status == 0)
> +		res->acl = acl;
> +	else
> +		richacl_put(acl);
>  	return status;
>  }
>  
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 383a027..8ced33d 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -2319,7 +2319,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  		sb->s_time_gran = 1;
>  	}
>  
> @@ -2346,7 +2346,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  	}
>  
>   	nfs_initialise_sb(sb);
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index c0e9614..b84e194 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -176,7 +176,6 @@ struct nfs_inode {
>  	wait_queue_head_t	waitqueue;
>  
>  #if IS_ENABLED(CONFIG_NFS_V4)
> -	struct nfs4_cached_acl	*nfs4_acl;
>          /* NFSv4 state */
>  	struct list_head	open_states;
>  	struct nfs_delegation __rcu *delegation;
> diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
> index 570a7df..6c41668 100644
> --- a/include/linux/nfs_fs_sb.h
> +++ b/include/linux/nfs_fs_sb.h
> @@ -243,5 +243,7 @@ struct nfs_server {
>  #define NFS_CAP_ALLOCATE	(1U << 20)
>  #define NFS_CAP_DEALLOCATE	(1U << 21)
>  #define NFS_CAP_LAYOUTSTATS	(1U << 22)
> +#define NFS_CAP_ALLOW_ACLS	(1U << 23)
> +#define NFS_CAP_DENY_ACLS	(1U << 24)
>  
>  #endif
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index 090ade4..337c341 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -683,9 +683,10 @@ struct nfs_setattrargs {
>  
>  struct nfs_setaclargs {
>  	struct nfs4_sequence_args	seq_args;
> +	const struct nfs_server *	server;
>  	struct nfs_fh *			fh;
> -	size_t				acl_len;
>  	struct page **			acl_pages;
> +	size_t				acl_len;
>  };
>  
>  struct nfs_setaclres {
> @@ -703,9 +704,9 @@ struct nfs_getaclargs {
>  #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
>  struct nfs_getaclres {
>  	struct nfs4_sequence_res	seq_res;
> -	size_t				acl_len;
> -	size_t				acl_data_offset;
> -	int				acl_flags;
> +	const struct nfs_server *	server;
> +	struct richacl *		acl;
> +	umode_t				mode;
>  	struct page *			acl_scratch;
>  };
>  
> 


WARNING: multiple messages have this Message-ID (diff)
From: Anna Schumaker <Anna.Schumaker@netapp.com>
To: Andreas Gruenbacher <andreas.gruenbacher@gmail.com>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Theodore Ts'o <tytso@mit.edu>,
	Andreas Dilger <adilger.kernel@dilger.ca>,
	"J. Bruce Fields" <bfields@fieldses.org>,
	Jeff Layton <jlayton@poochiereds.net>,
	Trond Myklebust <trond.myklebust@primarydata.com>,
	Anna Schumaker <anna.schumaker@netapp.com>,
	Dave Chinner <david@fromorbit.com>,
	linux-ext4@vger.kernel.org, xfs@oss.sgi.com,
	linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-nfs@vger.kernel.org, linux-cifs@vger.kernel.org,
	linux-api@vger.kernel.org
Cc: Andreas Gruenbacher <agruenba@redhat.com>
Subject: Re: [PATCH v10 44/46] nfs: Add richacl support
Date: Mon, 12 Oct 2015 10:39:26 -0400	[thread overview]
Message-ID: <561BC61E.6010704@Netapp.com> (raw)
In-Reply-To: <1444604337-17651-45-git-send-email-andreas.gruenbacher@gmail.com>

Hi Andreas,

On 10/11/2015 06:58 PM, Andreas Gruenbacher wrote:
> From: Andreas Gruenbacher <agruenba@redhat.com>
> 
> Add support for the "system.richacl" xattr in nfs.  The existing
> "system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
> and gids; the "system.richacl" xattr does, and only keeps the
> on-the-wire names when there is no mapping.  This allows to copy
> permissions across different file systems.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
> ---
>  fs/nfs/inode.c            |   3 -
>  fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
>  fs/nfs/nfs4xdr.c          | 179 ++++++++++--
>  fs/nfs/super.c            |   4 +-
>  include/linux/nfs_fs.h    |   1 -
>  include/linux/nfs_fs_sb.h |   2 +
>  include/linux/nfs_xdr.h   |   9 +-
>  7 files changed, 673 insertions(+), 223 deletions(-)
> 
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 326d9e1..843d15d 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -1852,9 +1852,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
>  		return NULL;
>  	nfsi->flags = 0UL;
>  	nfsi->cache_validity = 0UL;
> -#if IS_ENABLED(CONFIG_NFS_V4)
> -	nfsi->nfs4_acl = NULL;
> -#endif /* CONFIG_NFS_V4 */
>  	return &nfsi->vfs_inode;
>  }
>  EXPORT_SYMBOL_GPL(nfs_alloc_inode);
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index eec5c4c..a686251 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -55,6 +55,9 @@
>  #include <linux/xattr.h>
>  #include <linux/utsname.h>
>  #include <linux/freezer.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "delegation.h"
> @@ -2982,15 +2985,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
>  			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
>  		}
>  		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
> -		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
> -				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
> +		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
> +				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
>  				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
>  				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
>  				NFS_CAP_CTIME|NFS_CAP_MTIME|
>  				NFS_CAP_SECURITY_LABEL);
> -		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
> -				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> -			server->caps |= NFS_CAP_ACLS;
> +		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
> +			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> +				server->caps |= NFS_CAP_ALLOW_ACLS;
> +			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
> +				server->caps |= NFS_CAP_DENY_ACLS;
> +		}
>  		if (res.has_links != 0)
>  			server->caps |= NFS_CAP_HARDLINKS;
>  		if (res.has_symlinks != 0)
> @@ -4518,45 +4524,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
>  	return 0;
>  }
>  
> -static inline int nfs4_server_supports_acls(struct nfs_server *server)
> -{
> -	return server->caps & NFS_CAP_ACLS;
> -}
> -
> -/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
> - * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
> - * the stack.
> +/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages on the stack when encoding or decoding acls.
>   */
> -#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
> -
> -static int buf_to_pages_noslab(const void *buf, size_t buflen,
> -		struct page **pages)
> -{
> -	struct page *newpage, **spages;
> -	int rc = 0;
> -	size_t len;
> -	spages = pages;
> -
> -	do {
> -		len = min_t(size_t, PAGE_SIZE, buflen);
> -		newpage = alloc_page(GFP_KERNEL);
> -
> -		if (newpage == NULL)
> -			goto unwind;
> -		memcpy(page_address(newpage), buf, len);
> -                buf += len;
> -                buflen -= len;
> -		*pages++ = newpage;
> -		rc++;
> -	} while (buflen != 0);
> -
> -	return rc;
> -
> -unwind:
> -	for(; rc > 0; rc--)
> -		__free_page(spages[rc-1]);
> -	return -ENOMEM;
> -}
> +#define NFS4ACL_SIZE_MAX 65536
>  
>  struct nfs4_cached_acl {
>  	int cached;
> @@ -4564,66 +4536,9 @@ struct nfs4_cached_acl {
>  	char data[0];
>  };
>  
> -static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -
> -	spin_lock(&inode->i_lock);
> -	kfree(nfsi->nfs4_acl);
> -	nfsi->nfs4_acl = acl;
> -	spin_unlock(&inode->i_lock);
> -}
> -
>  static void nfs4_zap_acl_attr(struct inode *inode)
>  {
> -	nfs4_set_cached_acl(inode, NULL);
> -}
> -
> -static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -	struct nfs4_cached_acl *acl;
> -	int ret = -ENOENT;
> -
> -	spin_lock(&inode->i_lock);
> -	acl = nfsi->nfs4_acl;
> -	if (acl == NULL)
> -		goto out;
> -	if (buf == NULL) /* user is just asking for length */
> -		goto out_len;
> -	if (acl->cached == 0)
> -		goto out;
> -	ret = -ERANGE; /* see getxattr(2) man page */
> -	if (acl->len > buflen)
> -		goto out;
> -	memcpy(buf, acl->data, acl->len);
> -out_len:
> -	ret = acl->len;
> -out:
> -	spin_unlock(&inode->i_lock);
> -	return ret;
> -}
> -
> -static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
> -{
> -	struct nfs4_cached_acl *acl;
> -	size_t buflen = sizeof(*acl) + acl_len;
> -
> -	if (buflen <= PAGE_SIZE) {
> -		acl = kmalloc(buflen, GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 1;
> -		_copy_from_pages(acl->data, pages, pgbase, acl_len);
> -	} else {
> -		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 0;
> -	}
> -	acl->len = acl_len;
> -out:
> -	nfs4_set_cached_acl(inode, acl);
> +	forget_cached_richacl(inode);
>  }
>  
>  /*
> @@ -4636,121 +4551,269 @@ out:
>   * length. The next getxattr call will then produce another round trip to
>   * the server, this time with the input buf of the required size.
>   */
> -static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
>  {
> -	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
>  	struct nfs_getaclargs args = {
>  		.fh = NFS_FH(inode),
>  		.acl_pages = pages,
> -		.acl_len = buflen,
> +		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
>  	};
>  	struct nfs_getaclres res = {
> -		.acl_len = buflen,
> +		.server = server,
>  	};
>  	struct rpc_message msg = {
>  		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
>  		.rpc_argp = &args,
>  		.rpc_resp = &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
> -	int ret = -ENOMEM, i;
> +	int err, i;
>  
> -	/* As long as we're doing a round trip to the server anyway,
> -	 * let's be prepared for a page of acl data. */
> -	if (npages == 0)
> -		npages = 1;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -
> -	for (i = 0; i < npages; i++) {
> -		pages[i] = alloc_page(GFP_KERNEL);
> -		if (!pages[i])
> +	if (ARRAY_SIZE(pages) > 1) {
> +		/* for decoding across pages */
> +		res.acl_scratch = alloc_page(GFP_KERNEL);
> +		err = -ENOMEM;
> +		if (!res.acl_scratch)
>  			goto out_free;
>  	}
>  
> -	/* for decoding across pages */
> -	res.acl_scratch = alloc_page(GFP_KERNEL);
> -	if (!res.acl_scratch)
> -		goto out_free;
> -
> -	args.acl_len = npages * PAGE_SIZE;
> -
> -	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
> -		__func__, buf, buflen, npages, args.acl_len);
> -	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
> +	dprintk("%s  args.acl_len %zu\n",
> +		__func__, args.acl_len);
> +	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
>  			     &msg, &args.seq_args, &res.seq_res, 0);
> -	if (ret)
> +	if (err)
>  		goto out_free;
>  
> -	/* Handle the case where the passed-in buffer is too short */
> -	if (res.acl_flags & NFS4_ACL_TRUNC) {
> -		/* Did the user only issue a request for the acl length? */
> -		if (buf == NULL)
> -			goto out_ok;
> -		ret = -ERANGE;
> -		goto out_free;
> -	}
> -	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
> -	if (buf) {
> -		if (res.acl_len > buflen) {
> -			ret = -ERANGE;
> -			goto out_free;
> -		}
> -		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
> -	}
> -out_ok:
> -	ret = res.acl_len;
> +	richacl_compute_max_masks(res.acl);
> +	/* FIXME: Set inode->i_mode from res->mode?  */
> +	set_cached_richacl(inode, res.acl);
> +	err = 0;
> +
>  out_free:
> -	for (i = 0; i < npages; i++)
> -		if (pages[i])
> -			__free_page(pages[i]);
> +	if (err) {
> +		richacl_put(res.acl);
> +		res.acl = ERR_PTR(err);
> +	}
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		__free_page(pages[i]);
>  	if (res.acl_scratch)
>  		__free_page(res.acl_scratch);
> -	return ret;
> +	return res.acl;
>  }
>  
> -static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
>  {
>  	struct nfs4_exception exception = { };
> -	ssize_t ret;
> +	struct richacl *acl;
>  	do {
> -		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
> -		trace_nfs4_get_acl(inode, ret);
> -		if (ret >= 0)
> +		acl = __nfs4_get_acl_uncached(inode);
> +		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
> +		if (!IS_ERR(acl))
>  			break;
> -		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
> +		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
> +			      PTR_ERR(acl), &exception));
>  	} while (exception.retry);
> -	return ret;
> +	return acl;
>  }
>  
> -static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_proc_get_acl(struct inode *inode)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> +	struct richacl *acl;
>  	int ret;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return ERR_PTR(-EOPNOTSUPP);
>  	ret = nfs_revalidate_inode(server, inode);
>  	if (ret < 0)
> -		return ret;
> +		return ERR_PTR(ret);
>  	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
>  		nfs_zap_acl_cache(inode);
> -	ret = nfs4_read_cached_acl(inode, buf, buflen);
> -	if (ret != -ENOENT)
> -		/* -ENOENT is returned if there is no ACL or if there is an ACL
> -		 * but no cached acl data, just the acl length */
> -		return ret;
> -	return nfs4_get_acl_uncached(inode, buf, buflen);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	return nfs4_get_acl_uncached(inode);
> +}
> +
> +static int
> +richacl_supported(struct nfs_server *server, struct richacl *acl)
> +{
> +	struct richace *ace;
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return -EOPNOTSUPP;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_allow(ace)) {
> +			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
> +				return -EINVAL;
> +		} else if (richace_is_deny(ace)) {
> +			if (!(server->caps & NFS_CAP_DENY_ACLS))
> +				return -EINVAL;
> +		} else
> +			return -EINVAL;
> +	}
> +	return 0;
>  }
>  
> -static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int
> +nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
> +		 kuid_t uid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve uid %d to string\n",
> +				from_kuid(&init_user_ns, uid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static int
> +nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
> +		  kgid_t gid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve gid %d to string\n",
> +				from_kgid(&init_user_ns, gid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static unsigned int
> +nfs4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static int
> +nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
> +		    struct richace *ace, struct richacl *acl)
> +{
> +	const char *who;
> +	__be32 *p;
> +
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return -EIO;
> +		}
> +		p = xdr_reserve_space(xdr, 4 + len);
> +		if (!p)
> +			return -EIO;
> +		xdr_encode_opaque(p, who, len);
> +		return 0;
> +	} else {
> +		who = richace_unmapped_identifier(ace, acl);
> +		if (who) {
> +			unsigned int len = strlen(who);
> +
> +			p = xdr_reserve_space(xdr, 4 + len);
> +			if (!p)
> +				return -EIO;
> +			xdr_encode_opaque(p, who, len);
> +			return 0;
> +		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +			return nfs4_encode_group(xdr, server, ace->e_id.gid);
> +		else
> +			return nfs4_encode_user(xdr, server, ace->e_id.uid);
> +	}
> +}
> +
> +static int
> +nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
> +		const struct nfs_server *server)
> +{
> +	int minorversion = server->nfs_client->cl_minorversion;
> +	unsigned int ace_mask = nfs4_ace_mask(minorversion);
> +	struct xdr_stream xdr;
> +	struct xdr_buf buf;
> +	__be32 *p;
> +	struct richace *ace;
> +
> +	/* Reject acls not understood by the server */
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
> +	} else {
> +		if (acl->a_flags)
> +			return -EINVAL;
> +		richacl_for_each_entry(ace, acl) {
> +			if (ace->e_flags & RICHACE_INHERITED_ACE)
> +				return -EINVAL;
> +		}
> +	}
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_mask & ~ace_mask)
> +			return -EINVAL;
> +	}
> +
> +	xdr_init_encode_pages(&xdr, &buf, pages, len);
> +
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(&xdr, 4);
> +		if (!p)
> +			goto fail;
> +		*p = cpu_to_be32(acl ? acl->a_flags : 0);
> +	}
> +
> +	p = xdr_reserve_space(&xdr, 4);
> +	if (!p)
> +		goto fail;
> +	if (!acl) {
> +		*p++ = cpu_to_be32(0);
> +		return buf.len;
> +	}
> +	*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		p = xdr_reserve_space(&xdr, 4*3);
> +		if (!p)
> +			goto fail;
> +		*p++ = cpu_to_be32(ace->e_type);
> +		*p++ = cpu_to_be32(ace->e_flags &
> +			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
> +		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
> +			goto fail;
> +	}
> +
> +	return buf.len;
> +
> +fail:
> +	return -ENOMEM;
> +}
> +
> +static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> -	struct page *pages[NFS4ACL_MAXPAGES];
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
>  	struct nfs_setaclargs arg = {
> +		.server		= server,
>  		.fh		= NFS_FH(inode),
>  		.acl_pages	= pages,
> -		.acl_len	= buflen,
>  	};
>  	struct nfs_setaclres res;
>  	struct rpc_message msg = {
> @@ -4758,16 +4821,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  		.rpc_argp	= &arg,
>  		.rpc_resp	= &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
>  	int ret, i;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
> -	if (i < 0)
> -		return i;
> +	ret = richacl_supported(server, acl);
> +	if (ret)
> +		return ret;
> +
> +	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
> +	if (ret < 0) {
> +		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +			put_page(pages[i]);
> +		return ret;
> +	}
> +	arg.acl_len = ret;
> +
>  	nfs4_inode_return_delegation(inode);
>  	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
>  
> @@ -4775,8 +4842,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	 * Free each page after tx, so the only ref left is
>  	 * held by the network stack
>  	 */
> -	for (; i > 0; i--)
> -		put_page(pages[i-1]);
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		put_page(pages[i]);
>  
>  	/*
>  	 * Acl update can result in inode attribute update.
> @@ -4790,12 +4857,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	return ret;
>  }
>  
> -static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs4_exception exception = { };
>  	int err;
>  	do {
> -		err = __nfs4_proc_set_acl(inode, buf, buflen);
> +		err = __nfs4_proc_set_acl(inode, acl);
>  		trace_nfs4_set_acl(inode, err);
>  		err = nfs4_handle_exception(NFS_SERVER(inode), err,
>  				&exception);
> @@ -6257,34 +6324,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
>  	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
>  }
>  
> +static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
> +				  const void *buf, size_t buflen,
> +				  int flags, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	if (buf) {
> +		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +		error = richacl_apply_masks(&acl, inode->i_uid);
> +	} else {
> +		/*
> +		 * "Remove the acl"; only permissions granted by the mode
> +		 * remain.  We are using the cached mode here which could be
> +		 * outdated; should we do a GETATTR first to narrow down the
> +		 * race window?
> +		 */
> +		acl = richacl_from_mode(inode->i_mode);
> +		error = 0;
> +	}
> +
> +	if (!error)
> +		error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
> +				  void *buf, size_t buflen, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +	umode_t mode = inode->i_mode & S_IFMT;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = -ENODATA;
> +	if (richacl_equiv_mode(acl, &mode) == 0 &&
> +	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
> +		goto out;
> +	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
> +out:
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
> +				      size_t list_len, const char *name,
> +				      size_t name_len, int handler_flags)
> +{
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
> +	size_t len = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return 0;
> +
> +	if (list && len <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, len);
> +	return len;
> +}
> +
>  #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
>  
> +static int richacl_to_nfs4_acl(struct nfs_server *server,
> +			       const struct richacl *acl,
> +			       void *buf, size_t buflen)
> +{
> +	const struct richace *ace;
> +	__be32 *p = buf;
> +	size_t size = 0;
> +
> +	size += sizeof(*p);
> +	if (buflen >= size)
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		char who_buf[IDMAP_NAMESZ];
> +		const char *who = who_buf;
> +		int who_len;
> +
> +		size += 3 * sizeof(*p);
> +		if (buflen >= size) {
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags &
> +					   ~(RICHACE_INHERITED_ACE |
> +					     RICHACE_UNMAPPED_WHO |
> +					     RICHACE_SPECIAL_WHO));
> +			*p++ = cpu_to_be32(ace->e_mask);
> +		}
> +
> +		if (richace_is_unix_user(ace)) {
> +			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
> +						      who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (richace_is_unix_group(ace)) {
> +			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
> +						       who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +			if (!nfs4acl_special_id_to_who(ace->e_id.special,
> +						       &who, &who_len))
> +				return -EIO;
> +		} else {
> +			who = richace_unmapped_identifier(ace, acl);
> +			if (who)
> +				who_len = strlen(who);
> +			else
> +				return -EIO;
> +		}
> +
> +		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
> +		if (buflen >= size) {
> +			unsigned int padding = -who_len & (sizeof(*p) - 1);
> +
> +			*p++ = cpu_to_be32(who_len);
> +			memcpy(p, who, who_len);
> +			memset((char *)p + who_len, 0, padding);
> +			p += DIV_ROUND_UP(who_len, sizeof(*p));
> +		}
> +	}
> +	if (buflen && buflen < size)
> +		return -ERANGE;
> +	return size;
> +}
> +
> +static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
> +					     const void *buf, size_t buflen)
> +{
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	const __be32 *p = buf;
> +	int count, err;
> +
> +	if (buflen < sizeof(*p))
> +		return ERR_PTR(-EINVAL);
> +	count = be32_to_cpu(*p++);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EINVAL);
> +	buflen -= sizeof(*p);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		u32 who_len, size;
> +		int special_id;
> +		char *who;
> +
> +		err = -EINVAL;
> +		if (buflen < 4 * sizeof(*p))
> +			goto out;
> +		ace->e_type = be32_to_cpu(*p++);
> +		ace->e_flags = be32_to_cpu(*p++);
> +		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpu(*p++);
> +		who_len = be32_to_cpu(*p++);
> +		buflen -= 4 * sizeof(*p);
> +		size = ALIGN(who_len, 4);
> +		if (buflen < size || size == 0)
> +			goto out;
> +		who = (char *)p;
> +		special_id = nfs4acl_who_to_special_id(who, who_len);
> +		if (special_id >= 0) {
> +			ace->e_flags |= RICHACE_SPECIAL_WHO;
> +			ace->e_id.special = special_id;
> +		} else {
> +			bool unmappable;
> +
> +			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +				err = nfs_map_group_to_gid(server, who, who_len,
> +							   &ace->e_id.gid);
> +				if (err) {
> +					dprintk("%s: nfs_map_group_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  group nobody and unmappable groups! */
> +				unmappable = gid_eq(ace->e_id.gid,
> +					make_kgid(&init_user_ns, 99));
> +			} else {
> +				err = nfs_map_name_to_uid(server, who, who_len,
> +							  &ace->e_id.uid);
> +				if (err) {
> +					dprintk("%s: nfs_map_name_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  user nobody and unmappable users! */
> +				unmappable = uid_eq(ace->e_id.uid,
> +					make_kuid(&init_user_ns, 99));
> +			}
> +			if (unmappable) {
> +				err = -ENOMEM;
> +				if (richacl_add_unmapped_identifier(&acl, &ace,
> +					who, who_len, GFP_NOFS))
> +					goto out;
> +			}
> +		}
> +		p += size / sizeof(*p);
> +		buflen -= size;
> +	}
> +	err = -EINVAL;
> +	if (buflen != 0)
> +		goto out;
> +	err = 0;
> +
> +out:
> +	if (err) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(err);
> +	}
> +	return acl;
> +}

I'm not a fan of the "one giant function" approach.  Is there any way to split richacl_from_nfs4_acl() into several smaller functions?

Thanks,
Anna

> +
>  static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
>  				   const void *buf, size_t buflen,
>  				   int flags, int type)
>  {
> -	if (strcmp(key, "") != 0)
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (!buf || strcmp(key, "") != 0)
>  		return -EINVAL;
>  
> -	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
> +	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
>  				   void *buf, size_t buflen, int type)
>  {
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
>  	if (strcmp(key, "") != 0)
>  		return -EINVAL;
> -
> -	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
>  				       size_t list_len, const char *name,
>  				       size_t name_len, int type)
>  {
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
>  	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
>  
> -	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
>  		return 0;
>  
>  	if (list && len <= list_len)
> @@ -8837,6 +9153,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
>  	.clone_server	= nfs_clone_server,
>  };
>  
> +static const struct xattr_handler nfs4_xattr_richacl_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= nfs4_xattr_list_richacl,
> +	.get	= nfs4_xattr_get_richacl,
> +	.set	= nfs4_xattr_set_richacl,
> +};
> +
>  static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  	.prefix	= XATTR_NAME_NFSV4_ACL,
>  	.list	= nfs4_xattr_list_nfs4_acl,
> @@ -8845,6 +9168,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  };
>  
>  const struct xattr_handler *nfs4_xattr_handlers[] = {
> +	&nfs4_xattr_richacl_handler,
>  	&nfs4_xattr_nfs4_acl_handler,
>  #ifdef CONFIG_NFS_V4_SECURITY_LABEL
>  	&nfs4_xattr_nfs4_label_handler,
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index eefed15..f2507d7 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -52,6 +52,10 @@
>  #include <linux/nfs.h>
>  #include <linux/nfs4.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/nfs_idmap.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "internal.h"
> @@ -1650,16 +1654,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
>  static void
>  encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
>  {
> -	__be32 *p;
> +	int attrlen_offset;
> +	__be32 attrlen, *p;
>  
>  	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
>  	encode_nfs4_stateid(xdr, &zero_stateid);
> +
> +	/* Encode attribute bitmap. */
>  	p = reserve_space(xdr, 2*4);
>  	*p++ = cpu_to_be32(1);
>  	*p = cpu_to_be32(FATTR4_WORD0_ACL);
> -	p = reserve_space(xdr, 4);
> -	*p = cpu_to_be32(arg->acl_len);
> +
> +	attrlen_offset = xdr->buf->len;
> +	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
> +
>  	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
> +
> +	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
> +	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
>  }
>  
>  static void
> @@ -2488,7 +2500,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
>  	encode_sequence(xdr, &args->seq_args, &hdr);
>  	encode_putfh(xdr, args->fh, &hdr);
>  	replen = hdr.replen + op_decode_hdr_maxsz + 1;
> -	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
> +	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
>  
>  	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
>  		args->acl_pages, 0, args->acl_len);
> @@ -5260,24 +5272,135 @@ decode_restorefh(struct xdr_stream *xdr)
>  	return decode_op_hdr(xdr, OP_RESTOREFH);
>  }
>  
> +static int
> +nfs4_decode_ace_who(struct richace *ace,
> +		    const char **unmapped, unsigned int *unmapped_len,
> +		    const struct nfs_server *server,
> +		    struct xdr_stream *xdr)
> +{
> +	char *who;
> +	u32 len;
> +	int special_id;
> +	__be32 *p;
> +	int error;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (!p)
> +		return -ENOMEM;  /* acl truncated */
> +	len = be32_to_cpup(p++);
> +	if (len >= XDR_MAX_NETOBJ) {
> +		dprintk("%s: name too long (%u)!\n",
> +			__func__, len);
> +		return -EIO;
> +	}
> +	who = (char *)xdr_inline_decode(xdr, len);
> +	if (!who)
> +		return -ENOMEM;  /* acl truncated */
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return 0;
> +	}
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
> +		if (error) {
> +			dprintk("%s: nfs_map_group_to_gid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between group nobody and
> +			  unmappable groups! */
> +		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	} else {
> +		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
> +		if (error) {
> +			dprintk("%s: nfs_map_name_to_uid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between user nobody and
> +			  unmappable users! */
> +		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static struct richacl *
> +decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
> +{
> +	struct richacl *acl;
> +	struct richace *ace;
> +	uint32_t count;
> +	__be32 *p;
> +	int status;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (unlikely(!p))
> +		return ERR_PTR(-ENOMEM);  /* acl truncated */
> +	count = be32_to_cpup(p);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EIO);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		const char *unmapped = NULL;
> +		unsigned int unmapped_len;
> +
> +		p = xdr_inline_decode(xdr, 4*3);
> +		status = -ENOMEM;
> +		if (unlikely(!p))
> +			goto out;  /* acl truncated */
> +		ace->e_type = be32_to_cpup(p++);
> +		ace->e_flags = be32_to_cpup(p++);
> +		status = -EIO;
> +		if (ace->e_flags &
> +		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpup(p++);
> +		status = nfs4_decode_ace_who(ace, &unmapped,
> +					     &unmapped_len, server,
> +					     xdr);
> +		if (status)
> +			goto out;
> +		if (unmapped) {
> +			status = -ENOMEM;
> +			if (richacl_add_unmapped_identifier(&acl, &ace,
> +					unmapped, unmapped_len,
> +					GFP_NOFS))
> +				goto out;
> +		}
> +	}
> +	status = 0;
> +
> +out:
> +	if (status) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(status);
> +	}
> +	return acl;
> +}
> +
>  static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  			 struct nfs_getaclres *res)
>  {
>  	unsigned int savep;
>  	uint32_t attrlen,
>  		 bitmap[3] = {0};
> +	struct richacl *acl = NULL;
>  	int status;
> -	unsigned int pg_offset;
>  
> -	res->acl_len = 0;
>  	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
>  		goto out;
> -
> -	xdr_enter_page(xdr, xdr->buf->page_len);
> -
> -	/* Calculate the offset of the page data */
> -	pg_offset = xdr->buf->head[0].iov_len;
> -
>  	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
>  		goto out;
>  	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
> @@ -5286,24 +5409,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
>  		return -EIO;
>  	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
> -
> -		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
> -		 * are stored with the acl data to handle the problem of
> -		 * variable length bitmaps.*/
> -		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
> -		res->acl_len = attrlen;
> -
> -		/* Check for receive buffer overflow */
> -		if (res->acl_len > (xdr->nwords << 2) ||
> -		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
> -			res->acl_flags |= NFS4_ACL_TRUNC;
> -			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
> -					attrlen, xdr->nwords << 2);
> -		}
> +		acl = decode_acl_entries(xdr, res->server);
> +		status = PTR_ERR(acl);
> +		if (IS_ERR(acl))
> +			goto out;
> +		bitmap[0] &= ~FATTR4_WORD0_ACL;
>  	} else
>  		status = -EOPNOTSUPP;
>  
> +	status = -EIO;
> +	if (unlikely(bitmap[0]))
> +		goto out;
> +
> +	status = decode_attr_mode(xdr, bitmap, &res->mode);
> +	if (status < 0)
> +		goto out;
> +	status = 0;
> +
>  out:
> +	if (status == 0)
> +		res->acl = acl;
> +	else
> +		richacl_put(acl);
>  	return status;
>  }
>  
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 383a027..8ced33d 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -2319,7 +2319,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  		sb->s_time_gran = 1;
>  	}
>  
> @@ -2346,7 +2346,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  	}
>  
>   	nfs_initialise_sb(sb);
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index c0e9614..b84e194 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -176,7 +176,6 @@ struct nfs_inode {
>  	wait_queue_head_t	waitqueue;
>  
>  #if IS_ENABLED(CONFIG_NFS_V4)
> -	struct nfs4_cached_acl	*nfs4_acl;
>          /* NFSv4 state */
>  	struct list_head	open_states;
>  	struct nfs_delegation __rcu *delegation;
> diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
> index 570a7df..6c41668 100644
> --- a/include/linux/nfs_fs_sb.h
> +++ b/include/linux/nfs_fs_sb.h
> @@ -243,5 +243,7 @@ struct nfs_server {
>  #define NFS_CAP_ALLOCATE	(1U << 20)
>  #define NFS_CAP_DEALLOCATE	(1U << 21)
>  #define NFS_CAP_LAYOUTSTATS	(1U << 22)
> +#define NFS_CAP_ALLOW_ACLS	(1U << 23)
> +#define NFS_CAP_DENY_ACLS	(1U << 24)
>  
>  #endif
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index 090ade4..337c341 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -683,9 +683,10 @@ struct nfs_setattrargs {
>  
>  struct nfs_setaclargs {
>  	struct nfs4_sequence_args	seq_args;
> +	const struct nfs_server *	server;
>  	struct nfs_fh *			fh;
> -	size_t				acl_len;
>  	struct page **			acl_pages;
> +	size_t				acl_len;
>  };
>  
>  struct nfs_setaclres {
> @@ -703,9 +704,9 @@ struct nfs_getaclargs {
>  #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
>  struct nfs_getaclres {
>  	struct nfs4_sequence_res	seq_res;
> -	size_t				acl_len;
> -	size_t				acl_data_offset;
> -	int				acl_flags;
> +	const struct nfs_server *	server;
> +	struct richacl *		acl;
> +	umode_t				mode;
>  	struct page *			acl_scratch;
>  };
>  
> 

_______________________________________________
xfs mailing list
xfs@oss.sgi.com
http://oss.sgi.com/mailman/listinfo/xfs

WARNING: multiple messages have this Message-ID (diff)
From: Anna Schumaker <Anna.Schumaker-HgOvQuBEEgTQT0dZR+AlfA@public.gmane.org>
To: Andreas Gruenbacher
	<andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Alexander Viro
	<viro-RmSDqhL/yNMiFSDQTTA3OLVCufUGDwFn@public.gmane.org>,
	Theodore Ts'o <tytso-3s7WtUTddSA@public.gmane.org>,
	Andreas Dilger
	<adilger.kernel-m1MBpc4rdrD3fQ9qLvQP4Q@public.gmane.org>,
	"J. Bruce Fields"
	<bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org>,
	Jeff Layton <jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org>,
	Trond Myklebust
	<trond.myklebust-7I+n7zu2hftEKMMhf/gKZA@public.gmane.org>,
	Anna Schumaker
	<anna.schumaker-HgOvQuBEEgTQT0dZR+AlfA@public.gmane.org>,
	Dave Chinner <david-FqsqvQoI3Ljby3iVrkZq2A@public.gmane.org>,
	linux-ext4-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	xfs-VZNHf3L845pBDgjK7y7TUQ@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-api-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
Subject: Re: [PATCH v10 44/46] nfs: Add richacl support
Date: Mon, 12 Oct 2015 10:39:26 -0400	[thread overview]
Message-ID: <561BC61E.6010704@Netapp.com> (raw)
In-Reply-To: <1444604337-17651-45-git-send-email-andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>

Hi Andreas,

On 10/11/2015 06:58 PM, Andreas Gruenbacher wrote:
> From: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> 
> Add support for the "system.richacl" xattr in nfs.  The existing
> "system.nfs4_acl" xattr on nfs doesn't map user and group names to uids
> and gids; the "system.richacl" xattr does, and only keeps the
> on-the-wire names when there is no mapping.  This allows to copy
> permissions across different file systems.
> 
> Signed-off-by: Andreas Gruenbacher <agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
> ---
>  fs/nfs/inode.c            |   3 -
>  fs/nfs/nfs4proc.c         | 698 +++++++++++++++++++++++++++++++++-------------
>  fs/nfs/nfs4xdr.c          | 179 ++++++++++--
>  fs/nfs/super.c            |   4 +-
>  include/linux/nfs_fs.h    |   1 -
>  include/linux/nfs_fs_sb.h |   2 +
>  include/linux/nfs_xdr.h   |   9 +-
>  7 files changed, 673 insertions(+), 223 deletions(-)
> 
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 326d9e1..843d15d 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -1852,9 +1852,6 @@ struct inode *nfs_alloc_inode(struct super_block *sb)
>  		return NULL;
>  	nfsi->flags = 0UL;
>  	nfsi->cache_validity = 0UL;
> -#if IS_ENABLED(CONFIG_NFS_V4)
> -	nfsi->nfs4_acl = NULL;
> -#endif /* CONFIG_NFS_V4 */
>  	return &nfsi->vfs_inode;
>  }
>  EXPORT_SYMBOL_GPL(nfs_alloc_inode);
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index eec5c4c..a686251 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -55,6 +55,9 @@
>  #include <linux/xattr.h>
>  #include <linux/utsname.h>
>  #include <linux/freezer.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "delegation.h"
> @@ -2982,15 +2985,18 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f
>  			res.attr_bitmask[2] &= FATTR4_WORD2_NFS42_MASK;
>  		}
>  		memcpy(server->attr_bitmask, res.attr_bitmask, sizeof(server->attr_bitmask));
> -		server->caps &= ~(NFS_CAP_ACLS|NFS_CAP_HARDLINKS|
> -				NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
> +		server->caps &= ~(NFS_CAP_ALLOW_ACLS|NFS_CAP_DENY_ACLS|
> +				NFS_CAP_HARDLINKS|NFS_CAP_SYMLINKS|NFS_CAP_FILEID|
>  				NFS_CAP_MODE|NFS_CAP_NLINK|NFS_CAP_OWNER|
>  				NFS_CAP_OWNER_GROUP|NFS_CAP_ATIME|
>  				NFS_CAP_CTIME|NFS_CAP_MTIME|
>  				NFS_CAP_SECURITY_LABEL);
> -		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL &&
> -				res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> -			server->caps |= NFS_CAP_ACLS;
> +		if (res.attr_bitmask[0] & FATTR4_WORD0_ACL) {
> +			if (res.acl_bitmask & ACL4_SUPPORT_ALLOW_ACL)
> +				server->caps |= NFS_CAP_ALLOW_ACLS;
> +			if (res.acl_bitmask & ACL4_SUPPORT_DENY_ACL)
> +				server->caps |= NFS_CAP_DENY_ACLS;
> +		}
>  		if (res.has_links != 0)
>  			server->caps |= NFS_CAP_HARDLINKS;
>  		if (res.has_symlinks != 0)
> @@ -4518,45 +4524,11 @@ static int nfs4_proc_renew(struct nfs_client *clp, struct rpc_cred *cred)
>  	return 0;
>  }
>  
> -static inline int nfs4_server_supports_acls(struct nfs_server *server)
> -{
> -	return server->caps & NFS_CAP_ACLS;
> -}
> -
> -/* Assuming that XATTR_SIZE_MAX is a multiple of PAGE_SIZE, and that
> - * it's OK to put sizeof(void) * (XATTR_SIZE_MAX/PAGE_SIZE) bytes on
> - * the stack.
> +/* A arbitrary limit; we allocate at most DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages and put an array of DIV_ROUND_UP(NFS4ACL_SIZE_MAX,
> + * PAGE_SIZE) pages on the stack when encoding or decoding acls.
>   */
> -#define NFS4ACL_MAXPAGES DIV_ROUND_UP(XATTR_SIZE_MAX, PAGE_SIZE)
> -
> -static int buf_to_pages_noslab(const void *buf, size_t buflen,
> -		struct page **pages)
> -{
> -	struct page *newpage, **spages;
> -	int rc = 0;
> -	size_t len;
> -	spages = pages;
> -
> -	do {
> -		len = min_t(size_t, PAGE_SIZE, buflen);
> -		newpage = alloc_page(GFP_KERNEL);
> -
> -		if (newpage == NULL)
> -			goto unwind;
> -		memcpy(page_address(newpage), buf, len);
> -                buf += len;
> -                buflen -= len;
> -		*pages++ = newpage;
> -		rc++;
> -	} while (buflen != 0);
> -
> -	return rc;
> -
> -unwind:
> -	for(; rc > 0; rc--)
> -		__free_page(spages[rc-1]);
> -	return -ENOMEM;
> -}
> +#define NFS4ACL_SIZE_MAX 65536
>  
>  struct nfs4_cached_acl {
>  	int cached;
> @@ -4564,66 +4536,9 @@ struct nfs4_cached_acl {
>  	char data[0];
>  };
>  
> -static void nfs4_set_cached_acl(struct inode *inode, struct nfs4_cached_acl *acl)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -
> -	spin_lock(&inode->i_lock);
> -	kfree(nfsi->nfs4_acl);
> -	nfsi->nfs4_acl = acl;
> -	spin_unlock(&inode->i_lock);
> -}
> -
>  static void nfs4_zap_acl_attr(struct inode *inode)
>  {
> -	nfs4_set_cached_acl(inode, NULL);
> -}
> -
> -static inline ssize_t nfs4_read_cached_acl(struct inode *inode, char *buf, size_t buflen)
> -{
> -	struct nfs_inode *nfsi = NFS_I(inode);
> -	struct nfs4_cached_acl *acl;
> -	int ret = -ENOENT;
> -
> -	spin_lock(&inode->i_lock);
> -	acl = nfsi->nfs4_acl;
> -	if (acl == NULL)
> -		goto out;
> -	if (buf == NULL) /* user is just asking for length */
> -		goto out_len;
> -	if (acl->cached == 0)
> -		goto out;
> -	ret = -ERANGE; /* see getxattr(2) man page */
> -	if (acl->len > buflen)
> -		goto out;
> -	memcpy(buf, acl->data, acl->len);
> -out_len:
> -	ret = acl->len;
> -out:
> -	spin_unlock(&inode->i_lock);
> -	return ret;
> -}
> -
> -static void nfs4_write_cached_acl(struct inode *inode, struct page **pages, size_t pgbase, size_t acl_len)
> -{
> -	struct nfs4_cached_acl *acl;
> -	size_t buflen = sizeof(*acl) + acl_len;
> -
> -	if (buflen <= PAGE_SIZE) {
> -		acl = kmalloc(buflen, GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 1;
> -		_copy_from_pages(acl->data, pages, pgbase, acl_len);
> -	} else {
> -		acl = kmalloc(sizeof(*acl), GFP_KERNEL);
> -		if (acl == NULL)
> -			goto out;
> -		acl->cached = 0;
> -	}
> -	acl->len = acl_len;
> -out:
> -	nfs4_set_cached_acl(inode, acl);
> +	forget_cached_richacl(inode);
>  }
>  
>  /*
> @@ -4636,121 +4551,269 @@ out:
>   * length. The next getxattr call will then produce another round trip to
>   * the server, this time with the input buf of the required size.
>   */
> -static ssize_t __nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *__nfs4_get_acl_uncached(struct inode *inode)
>  {
> -	struct page *pages[NFS4ACL_MAXPAGES] = {NULL, };
> +	struct nfs_server *server = NFS_SERVER(inode);
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE)] = {};
>  	struct nfs_getaclargs args = {
>  		.fh = NFS_FH(inode),
>  		.acl_pages = pages,
> -		.acl_len = buflen,
> +		.acl_len = ARRAY_SIZE(pages) * PAGE_SIZE,
>  	};
>  	struct nfs_getaclres res = {
> -		.acl_len = buflen,
> +		.server = server,
>  	};
>  	struct rpc_message msg = {
>  		.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_GETACL],
>  		.rpc_argp = &args,
>  		.rpc_resp = &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
> -	int ret = -ENOMEM, i;
> +	int err, i;
>  
> -	/* As long as we're doing a round trip to the server anyway,
> -	 * let's be prepared for a page of acl data. */
> -	if (npages == 0)
> -		npages = 1;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -
> -	for (i = 0; i < npages; i++) {
> -		pages[i] = alloc_page(GFP_KERNEL);
> -		if (!pages[i])
> +	if (ARRAY_SIZE(pages) > 1) {
> +		/* for decoding across pages */
> +		res.acl_scratch = alloc_page(GFP_KERNEL);
> +		err = -ENOMEM;
> +		if (!res.acl_scratch)
>  			goto out_free;
>  	}
>  
> -	/* for decoding across pages */
> -	res.acl_scratch = alloc_page(GFP_KERNEL);
> -	if (!res.acl_scratch)
> -		goto out_free;
> -
> -	args.acl_len = npages * PAGE_SIZE;
> -
> -	dprintk("%s  buf %p buflen %zu npages %d args.acl_len %zu\n",
> -		__func__, buf, buflen, npages, args.acl_len);
> -	ret = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
> +	dprintk("%s  args.acl_len %zu\n",
> +		__func__, args.acl_len);
> +	err = nfs4_call_sync(NFS_SERVER(inode)->client, NFS_SERVER(inode),
>  			     &msg, &args.seq_args, &res.seq_res, 0);
> -	if (ret)
> +	if (err)
>  		goto out_free;
>  
> -	/* Handle the case where the passed-in buffer is too short */
> -	if (res.acl_flags & NFS4_ACL_TRUNC) {
> -		/* Did the user only issue a request for the acl length? */
> -		if (buf == NULL)
> -			goto out_ok;
> -		ret = -ERANGE;
> -		goto out_free;
> -	}
> -	nfs4_write_cached_acl(inode, pages, res.acl_data_offset, res.acl_len);
> -	if (buf) {
> -		if (res.acl_len > buflen) {
> -			ret = -ERANGE;
> -			goto out_free;
> -		}
> -		_copy_from_pages(buf, pages, res.acl_data_offset, res.acl_len);
> -	}
> -out_ok:
> -	ret = res.acl_len;
> +	richacl_compute_max_masks(res.acl);
> +	/* FIXME: Set inode->i_mode from res->mode?  */
> +	set_cached_richacl(inode, res.acl);
> +	err = 0;
> +
>  out_free:
> -	for (i = 0; i < npages; i++)
> -		if (pages[i])
> -			__free_page(pages[i]);
> +	if (err) {
> +		richacl_put(res.acl);
> +		res.acl = ERR_PTR(err);
> +	}
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		__free_page(pages[i]);
>  	if (res.acl_scratch)
>  		__free_page(res.acl_scratch);
> -	return ret;
> +	return res.acl;
>  }
>  
> -static ssize_t nfs4_get_acl_uncached(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_get_acl_uncached(struct inode *inode)
>  {
>  	struct nfs4_exception exception = { };
> -	ssize_t ret;
> +	struct richacl *acl;
>  	do {
> -		ret = __nfs4_get_acl_uncached(inode, buf, buflen);
> -		trace_nfs4_get_acl(inode, ret);
> -		if (ret >= 0)
> +		acl = __nfs4_get_acl_uncached(inode);
> +		trace_nfs4_get_acl(inode, IS_ERR(acl) ? PTR_ERR(acl) : 0);
> +		if (!IS_ERR(acl))
>  			break;
> -		ret = nfs4_handle_exception(NFS_SERVER(inode), ret, &exception);
> +		acl = ERR_PTR(nfs4_handle_exception(NFS_SERVER(inode),
> +			      PTR_ERR(acl), &exception));
>  	} while (exception.retry);
> -	return ret;
> +	return acl;
>  }
>  
> -static ssize_t nfs4_proc_get_acl(struct inode *inode, void *buf, size_t buflen)
> +static struct richacl *nfs4_proc_get_acl(struct inode *inode)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> +	struct richacl *acl;
>  	int ret;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return ERR_PTR(-EOPNOTSUPP);
>  	ret = nfs_revalidate_inode(server, inode);
>  	if (ret < 0)
> -		return ret;
> +		return ERR_PTR(ret);
>  	if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_ACL)
>  		nfs_zap_acl_cache(inode);
> -	ret = nfs4_read_cached_acl(inode, buf, buflen);
> -	if (ret != -ENOENT)
> -		/* -ENOENT is returned if there is no ACL or if there is an ACL
> -		 * but no cached acl data, just the acl length */
> -		return ret;
> -	return nfs4_get_acl_uncached(inode, buf, buflen);
> +	acl = get_cached_richacl(inode);
> +	if (acl != ACL_NOT_CACHED)
> +		return acl;
> +	return nfs4_get_acl_uncached(inode);
> +}
> +
> +static int
> +richacl_supported(struct nfs_server *server, struct richacl *acl)
> +{
> +	struct richace *ace;
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return -EOPNOTSUPP;
> +
> +	richacl_for_each_entry(ace, acl) {
> +		if (richace_is_allow(ace)) {
> +			if (!(server->caps & NFS_CAP_ALLOW_ACLS))
> +				return -EINVAL;
> +		} else if (richace_is_deny(ace)) {
> +			if (!(server->caps & NFS_CAP_DENY_ACLS))
> +				return -EINVAL;
> +		} else
> +			return -EINVAL;
> +	}
> +	return 0;
>  }
>  
> -static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int
> +nfs4_encode_user(struct xdr_stream *xdr, const struct nfs_server *server,
> +		 kuid_t uid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_uid_to_name(server, uid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve uid %d to string\n",
> +				from_kuid(&init_user_ns, uid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static int
> +nfs4_encode_group(struct xdr_stream *xdr, const struct nfs_server *server,
> +		  kgid_t gid)
> +{
> +	char name[IDMAP_NAMESZ];
> +	int len;
> +	__be32 *p;
> +
> +	len = nfs_map_gid_to_group(server, gid, name, IDMAP_NAMESZ);
> +	if (len < 0) {
> +		dprintk("nfs: couldn't resolve gid %d to string\n",
> +				from_kgid(&init_user_ns, gid));
> +		return -ENOENT;
> +	}
> +	p = xdr_reserve_space(xdr, 4 + len);
> +	if (!p)
> +		return -EIO;
> +	p = xdr_encode_opaque(p, name, len);
> +	return 0;
> +}
> +
> +static unsigned int
> +nfs4_ace_mask(int minorversion)
> +{
> +	return minorversion == 0 ? NFS40_ACE_MASK_ALL : NFS4_ACE_MASK_ALL;
> +}
> +
> +static int
> +nfs4_encode_ace_who(struct xdr_stream *xdr, const struct nfs_server *server,
> +		    struct richace *ace, struct richacl *acl)
> +{
> +	const char *who;
> +	__be32 *p;
> +
> +	if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +		unsigned int special_id = ace->e_id.special;
> +		const char *who;
> +		unsigned int len;
> +
> +		if (!nfs4acl_special_id_to_who(special_id, &who, &len)) {
> +			WARN_ON_ONCE(1);
> +			return -EIO;
> +		}
> +		p = xdr_reserve_space(xdr, 4 + len);
> +		if (!p)
> +			return -EIO;
> +		xdr_encode_opaque(p, who, len);
> +		return 0;
> +	} else {
> +		who = richace_unmapped_identifier(ace, acl);
> +		if (who) {
> +			unsigned int len = strlen(who);
> +
> +			p = xdr_reserve_space(xdr, 4 + len);
> +			if (!p)
> +				return -EIO;
> +			xdr_encode_opaque(p, who, len);
> +			return 0;
> +		} else if (ace->e_flags & RICHACE_IDENTIFIER_GROUP)
> +			return nfs4_encode_group(xdr, server, ace->e_id.gid);
> +		else
> +			return nfs4_encode_user(xdr, server, ace->e_id.uid);
> +	}
> +}
> +
> +static int
> +nfs4_encode_acl(struct page **pages, unsigned int len, struct richacl *acl,
> +		const struct nfs_server *server)
> +{
> +	int minorversion = server->nfs_client->cl_minorversion;
> +	unsigned int ace_mask = nfs4_ace_mask(minorversion);
> +	struct xdr_stream xdr;
> +	struct xdr_buf buf;
> +	__be32 *p;
> +	struct richace *ace;
> +
> +	/* Reject acls not understood by the server */
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		BUILD_BUG_ON(NFS4_ACE_MASK_ALL != RICHACE_VALID_MASK);
> +	} else {
> +		if (acl->a_flags)
> +			return -EINVAL;
> +		richacl_for_each_entry(ace, acl) {
> +			if (ace->e_flags & RICHACE_INHERITED_ACE)
> +				return -EINVAL;
> +		}
> +	}
> +	richacl_for_each_entry(ace, acl) {
> +		if (ace->e_mask & ~ace_mask)
> +			return -EINVAL;
> +	}
> +
> +	xdr_init_encode_pages(&xdr, &buf, pages, len);
> +
> +	if (server->attr_bitmask[1] & FATTR4_WORD1_DACL) {
> +		p = xdr_reserve_space(&xdr, 4);
> +		if (!p)
> +			goto fail;
> +		*p = cpu_to_be32(acl ? acl->a_flags : 0);
> +	}
> +
> +	p = xdr_reserve_space(&xdr, 4);
> +	if (!p)
> +		goto fail;
> +	if (!acl) {
> +		*p++ = cpu_to_be32(0);
> +		return buf.len;
> +	}
> +	*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		p = xdr_reserve_space(&xdr, 4*3);
> +		if (!p)
> +			goto fail;
> +		*p++ = cpu_to_be32(ace->e_type);
> +		*p++ = cpu_to_be32(ace->e_flags &
> +			~(RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO));
> +		*p++ = cpu_to_be32(ace->e_mask & NFS4_ACE_MASK_ALL);
> +		if (nfs4_encode_ace_who(&xdr, server, ace, acl) != 0)
> +			goto fail;
> +	}
> +
> +	return buf.len;
> +
> +fail:
> +	return -ENOMEM;
> +}
> +
> +static int __nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs_server *server = NFS_SERVER(inode);
> -	struct page *pages[NFS4ACL_MAXPAGES];
> +	struct page *pages[DIV_ROUND_UP(NFS4ACL_SIZE_MAX, PAGE_SIZE) + 1 /* scratch */] = {};
>  	struct nfs_setaclargs arg = {
> +		.server		= server,
>  		.fh		= NFS_FH(inode),
>  		.acl_pages	= pages,
> -		.acl_len	= buflen,
>  	};
>  	struct nfs_setaclres res;
>  	struct rpc_message msg = {
> @@ -4758,16 +4821,20 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  		.rpc_argp	= &arg,
>  		.rpc_resp	= &res,
>  	};
> -	unsigned int npages = DIV_ROUND_UP(buflen, PAGE_SIZE);
>  	int ret, i;
>  
> -	if (!nfs4_server_supports_acls(server))
> -		return -EOPNOTSUPP;
> -	if (npages > ARRAY_SIZE(pages))
> -		return -ERANGE;
> -	i = buf_to_pages_noslab(buf, buflen, arg.acl_pages);
> -	if (i < 0)
> -		return i;
> +	ret = richacl_supported(server, acl);
> +	if (ret)
> +		return ret;
> +
> +	ret = nfs4_encode_acl(pages, NFS4ACL_SIZE_MAX, acl, server);
> +	if (ret < 0) {
> +		for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +			put_page(pages[i]);
> +		return ret;
> +	}
> +	arg.acl_len = ret;
> +
>  	nfs4_inode_return_delegation(inode);
>  	ret = nfs4_call_sync(server->client, server, &msg, &arg.seq_args, &res.seq_res, 1);
>  
> @@ -4775,8 +4842,8 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	 * Free each page after tx, so the only ref left is
>  	 * held by the network stack
>  	 */
> -	for (; i > 0; i--)
> -		put_page(pages[i-1]);
> +	for (i = 0; i < ARRAY_SIZE(pages) && pages[i]; i++)
> +		put_page(pages[i]);
>  
>  	/*
>  	 * Acl update can result in inode attribute update.
> @@ -4790,12 +4857,12 @@ static int __nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t bufl
>  	return ret;
>  }
>  
> -static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen)
> +static int nfs4_proc_set_acl(struct inode *inode, struct richacl *acl)
>  {
>  	struct nfs4_exception exception = { };
>  	int err;
>  	do {
> -		err = __nfs4_proc_set_acl(inode, buf, buflen);
> +		err = __nfs4_proc_set_acl(inode, acl);
>  		trace_nfs4_set_acl(inode, err);
>  		err = nfs4_handle_exception(NFS_SERVER(inode), err,
>  				&exception);
> @@ -6257,34 +6324,283 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp)
>  	rpc_call_async(server->client, &msg, 0, &nfs4_release_lockowner_ops, data);
>  }
>  
> +static int nfs4_xattr_set_richacl(struct dentry *dentry, const char *key,
> +				  const void *buf, size_t buflen,
> +				  int flags, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	if (buf) {
> +		acl = richacl_from_xattr(&init_user_ns, buf, buflen);
> +		if (IS_ERR(acl))
> +			return PTR_ERR(acl);
> +		error = richacl_apply_masks(&acl, inode->i_uid);
> +	} else {
> +		/*
> +		 * "Remove the acl"; only permissions granted by the mode
> +		 * remain.  We are using the cached mode here which could be
> +		 * outdated; should we do a GETATTR first to narrow down the
> +		 * race window?
> +		 */
> +		acl = richacl_from_mode(inode->i_mode);
> +		error = 0;
> +	}
> +
> +	if (!error)
> +		error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static int nfs4_xattr_get_richacl(struct dentry *dentry, const char *key,
> +				  void *buf, size_t buflen, int handler_flags)
> +{
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +	umode_t mode = inode->i_mode & S_IFMT;
> +
> +	if (strcmp(key, "") != 0)
> +		return -EINVAL;
> +
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = -ENODATA;
> +	if (richacl_equiv_mode(acl, &mode) == 0 &&
> +	    ((mode ^ inode->i_mode) & S_IRWXUGO) == 0)
> +		goto out;
> +	error = richacl_to_xattr(&init_user_ns, acl, buf, buflen);
> +out:
> +	richacl_put(acl);
> +	return error;
> +}
> +
> +static size_t nfs4_xattr_list_richacl(struct dentry *dentry, char *list,
> +				      size_t list_len, const char *name,
> +				      size_t name_len, int handler_flags)
> +{
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
> +	size_t len = sizeof(XATTR_NAME_RICHACL);
> +
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
> +		return 0;
> +
> +	if (list && len <= list_len)
> +		memcpy(list, XATTR_NAME_RICHACL, len);
> +	return len;
> +}
> +
>  #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl"
>  
> +static int richacl_to_nfs4_acl(struct nfs_server *server,
> +			       const struct richacl *acl,
> +			       void *buf, size_t buflen)
> +{
> +	const struct richace *ace;
> +	__be32 *p = buf;
> +	size_t size = 0;
> +
> +	size += sizeof(*p);
> +	if (buflen >= size)
> +		*p++ = cpu_to_be32(acl->a_count);
> +
> +	richacl_for_each_entry(ace, acl) {
> +		char who_buf[IDMAP_NAMESZ];
> +		const char *who = who_buf;
> +		int who_len;
> +
> +		size += 3 * sizeof(*p);
> +		if (buflen >= size) {
> +			*p++ = cpu_to_be32(ace->e_type);
> +			*p++ = cpu_to_be32(ace->e_flags &
> +					   ~(RICHACE_INHERITED_ACE |
> +					     RICHACE_UNMAPPED_WHO |
> +					     RICHACE_SPECIAL_WHO));
> +			*p++ = cpu_to_be32(ace->e_mask);
> +		}
> +
> +		if (richace_is_unix_user(ace)) {
> +			who_len = nfs_map_uid_to_name(server, ace->e_id.uid,
> +						      who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (richace_is_unix_group(ace)) {
> +			who_len = nfs_map_gid_to_group(server, ace->e_id.gid,
> +						       who_buf, sizeof(who_buf));
> +			if (who_len < 0)
> +				return -EIO;
> +		} else if (ace->e_flags & RICHACE_SPECIAL_WHO) {
> +			if (!nfs4acl_special_id_to_who(ace->e_id.special,
> +						       &who, &who_len))
> +				return -EIO;
> +		} else {
> +			who = richace_unmapped_identifier(ace, acl);
> +			if (who)
> +				who_len = strlen(who);
> +			else
> +				return -EIO;
> +		}
> +
> +		size += sizeof(*p) + ALIGN(who_len, sizeof(*p));
> +		if (buflen >= size) {
> +			unsigned int padding = -who_len & (sizeof(*p) - 1);
> +
> +			*p++ = cpu_to_be32(who_len);
> +			memcpy(p, who, who_len);
> +			memset((char *)p + who_len, 0, padding);
> +			p += DIV_ROUND_UP(who_len, sizeof(*p));
> +		}
> +	}
> +	if (buflen && buflen < size)
> +		return -ERANGE;
> +	return size;
> +}
> +
> +static struct richacl *richacl_from_nfs4_acl(struct nfs_server *server,
> +					     const void *buf, size_t buflen)
> +{
> +	struct richacl *acl = NULL;
> +	struct richace *ace;
> +	const __be32 *p = buf;
> +	int count, err;
> +
> +	if (buflen < sizeof(*p))
> +		return ERR_PTR(-EINVAL);
> +	count = be32_to_cpu(*p++);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EINVAL);
> +	buflen -= sizeof(*p);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		u32 who_len, size;
> +		int special_id;
> +		char *who;
> +
> +		err = -EINVAL;
> +		if (buflen < 4 * sizeof(*p))
> +			goto out;
> +		ace->e_type = be32_to_cpu(*p++);
> +		ace->e_flags = be32_to_cpu(*p++);
> +		if (ace->e_flags & (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpu(*p++);
> +		who_len = be32_to_cpu(*p++);
> +		buflen -= 4 * sizeof(*p);
> +		size = ALIGN(who_len, 4);
> +		if (buflen < size || size == 0)
> +			goto out;
> +		who = (char *)p;
> +		special_id = nfs4acl_who_to_special_id(who, who_len);
> +		if (special_id >= 0) {
> +			ace->e_flags |= RICHACE_SPECIAL_WHO;
> +			ace->e_id.special = special_id;
> +		} else {
> +			bool unmappable;
> +
> +			if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +				err = nfs_map_group_to_gid(server, who, who_len,
> +							   &ace->e_id.gid);
> +				if (err) {
> +					dprintk("%s: nfs_map_group_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  group nobody and unmappable groups! */
> +				unmappable = gid_eq(ace->e_id.gid,
> +					make_kgid(&init_user_ns, 99));
> +			} else {
> +				err = nfs_map_name_to_uid(server, who, who_len,
> +							  &ace->e_id.uid);
> +				if (err) {
> +					dprintk("%s: nfs_map_name_to_gid "
> +						"failed!\n", __func__);
> +					goto out;
> +				}
> +				/* FIXME: nfsidmap doesn't distinguish between
> +					  user nobody and unmappable users! */
> +				unmappable = uid_eq(ace->e_id.uid,
> +					make_kuid(&init_user_ns, 99));
> +			}
> +			if (unmappable) {
> +				err = -ENOMEM;
> +				if (richacl_add_unmapped_identifier(&acl, &ace,
> +					who, who_len, GFP_NOFS))
> +					goto out;
> +			}
> +		}
> +		p += size / sizeof(*p);
> +		buflen -= size;
> +	}
> +	err = -EINVAL;
> +	if (buflen != 0)
> +		goto out;
> +	err = 0;
> +
> +out:
> +	if (err) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(err);
> +	}
> +	return acl;
> +}

I'm not a fan of the "one giant function" approach.  Is there any way to split richacl_from_nfs4_acl() into several smaller functions?

Thanks,
Anna

> +
>  static int nfs4_xattr_set_nfs4_acl(struct dentry *dentry, const char *key,
>  				   const void *buf, size_t buflen,
>  				   int flags, int type)
>  {
> -	if (strcmp(key, "") != 0)
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
> +	if (!buf || strcmp(key, "") != 0)
>  		return -EINVAL;
>  
> -	return nfs4_proc_set_acl(d_inode(dentry), buf, buflen);
> +	acl = richacl_from_nfs4_acl(NFS_SERVER(inode), (void *)buf, buflen);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	error = nfs4_proc_set_acl(inode, acl);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static int nfs4_xattr_get_nfs4_acl(struct dentry *dentry, const char *key,
>  				   void *buf, size_t buflen, int type)
>  {
> +	struct inode *inode = d_inode(dentry);
> +	struct richacl *acl;
> +	int error;
> +
>  	if (strcmp(key, "") != 0)
>  		return -EINVAL;
> -
> -	return nfs4_proc_get_acl(d_inode(dentry), buf, buflen);
> +	acl = nfs4_proc_get_acl(inode);
> +	if (IS_ERR(acl))
> +		return PTR_ERR(acl);
> +	if (acl == NULL)
> +		return -ENODATA;
> +	error = richacl_to_nfs4_acl(NFS_SERVER(inode), acl, buf, buflen);
> +	richacl_put(acl);
> +	return error;
>  }
>  
>  static size_t nfs4_xattr_list_nfs4_acl(struct dentry *dentry, char *list,
>  				       size_t list_len, const char *name,
>  				       size_t name_len, int type)
>  {
> +	struct nfs_server *server = NFS_SERVER(d_inode(dentry));
>  	size_t len = sizeof(XATTR_NAME_NFSV4_ACL);
>  
> -	if (!nfs4_server_supports_acls(NFS_SERVER(d_inode(dentry))))
> +	if (!(server->caps & (NFS_CAP_ALLOW_ACLS | NFS_CAP_DENY_ACLS)))
>  		return 0;
>  
>  	if (list && len <= list_len)
> @@ -8837,6 +9153,13 @@ const struct nfs_rpc_ops nfs_v4_clientops = {
>  	.clone_server	= nfs_clone_server,
>  };
>  
> +static const struct xattr_handler nfs4_xattr_richacl_handler = {
> +	.prefix	= XATTR_NAME_RICHACL,
> +	.list	= nfs4_xattr_list_richacl,
> +	.get	= nfs4_xattr_get_richacl,
> +	.set	= nfs4_xattr_set_richacl,
> +};
> +
>  static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  	.prefix	= XATTR_NAME_NFSV4_ACL,
>  	.list	= nfs4_xattr_list_nfs4_acl,
> @@ -8845,6 +9168,7 @@ static const struct xattr_handler nfs4_xattr_nfs4_acl_handler = {
>  };
>  
>  const struct xattr_handler *nfs4_xattr_handlers[] = {
> +	&nfs4_xattr_richacl_handler,
>  	&nfs4_xattr_nfs4_acl_handler,
>  #ifdef CONFIG_NFS_V4_SECURITY_LABEL
>  	&nfs4_xattr_nfs4_label_handler,
> diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
> index eefed15..f2507d7 100644
> --- a/fs/nfs/nfs4xdr.c
> +++ b/fs/nfs/nfs4xdr.c
> @@ -52,6 +52,10 @@
>  #include <linux/nfs.h>
>  #include <linux/nfs4.h>
>  #include <linux/nfs_fs.h>
> +#include <linux/nfs_idmap.h>
> +#include <linux/richacl.h>
> +#include <linux/richacl_xattr.h>  /* for RICHACL_XATTR_MAX_COUNT */
> +#include <linux/nfs4acl.h>
>  
>  #include "nfs4_fs.h"
>  #include "internal.h"
> @@ -1650,16 +1654,24 @@ encode_restorefh(struct xdr_stream *xdr, struct compound_hdr *hdr)
>  static void
>  encode_setacl(struct xdr_stream *xdr, struct nfs_setaclargs *arg, struct compound_hdr *hdr)
>  {
> -	__be32 *p;
> +	int attrlen_offset;
> +	__be32 attrlen, *p;
>  
>  	encode_op_hdr(xdr, OP_SETATTR, decode_setacl_maxsz, hdr);
>  	encode_nfs4_stateid(xdr, &zero_stateid);
> +
> +	/* Encode attribute bitmap. */
>  	p = reserve_space(xdr, 2*4);
>  	*p++ = cpu_to_be32(1);
>  	*p = cpu_to_be32(FATTR4_WORD0_ACL);
> -	p = reserve_space(xdr, 4);
> -	*p = cpu_to_be32(arg->acl_len);
> +
> +	attrlen_offset = xdr->buf->len;
> +	xdr_reserve_space(xdr, 4);  /* to be backfilled later */
> +
>  	xdr_write_pages(xdr, arg->acl_pages, 0, arg->acl_len);
> +
> +	attrlen = htonl(xdr->buf->len - attrlen_offset - 4);
> +	write_bytes_to_xdr_buf(xdr->buf, attrlen_offset, &attrlen, 4);
>  }
>  
>  static void
> @@ -2488,7 +2500,7 @@ static void nfs4_xdr_enc_getacl(struct rpc_rqst *req, struct xdr_stream *xdr,
>  	encode_sequence(xdr, &args->seq_args, &hdr);
>  	encode_putfh(xdr, args->fh, &hdr);
>  	replen = hdr.replen + op_decode_hdr_maxsz + 1;
> -	encode_getattr_two(xdr, FATTR4_WORD0_ACL, 0, &hdr);
> +	encode_getattr_two(xdr, FATTR4_WORD0_ACL, FATTR4_WORD1_MODE, &hdr);
>  
>  	xdr_inline_pages(&req->rq_rcv_buf, replen << 2,
>  		args->acl_pages, 0, args->acl_len);
> @@ -5260,24 +5272,135 @@ decode_restorefh(struct xdr_stream *xdr)
>  	return decode_op_hdr(xdr, OP_RESTOREFH);
>  }
>  
> +static int
> +nfs4_decode_ace_who(struct richace *ace,
> +		    const char **unmapped, unsigned int *unmapped_len,
> +		    const struct nfs_server *server,
> +		    struct xdr_stream *xdr)
> +{
> +	char *who;
> +	u32 len;
> +	int special_id;
> +	__be32 *p;
> +	int error;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (!p)
> +		return -ENOMEM;  /* acl truncated */
> +	len = be32_to_cpup(p++);
> +	if (len >= XDR_MAX_NETOBJ) {
> +		dprintk("%s: name too long (%u)!\n",
> +			__func__, len);
> +		return -EIO;
> +	}
> +	who = (char *)xdr_inline_decode(xdr, len);
> +	if (!who)
> +		return -ENOMEM;  /* acl truncated */
> +
> +	special_id = nfs4acl_who_to_special_id(who, len);
> +	if (special_id >= 0) {
> +		ace->e_flags |= RICHACE_SPECIAL_WHO;
> +		ace->e_flags &= ~RICHACE_IDENTIFIER_GROUP;
> +		ace->e_id.special = special_id;
> +		return 0;
> +	}
> +	if (ace->e_flags & RICHACE_IDENTIFIER_GROUP) {
> +		error = nfs_map_group_to_gid(server, who, len, &ace->e_id.gid);
> +		if (error) {
> +			dprintk("%s: nfs_map_group_to_gid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between group nobody and
> +			  unmappable groups! */
> +		if (gid_eq(ace->e_id.gid, make_kgid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	} else {
> +		error = nfs_map_name_to_uid(server, who, len, &ace->e_id.uid);
> +		if (error) {
> +			dprintk("%s: nfs_map_name_to_uid failed!\n",
> +					__func__);
> +			return error;
> +		}
> +		/* FIXME: nfsidmap doesn't distinguish between user nobody and
> +			  unmappable users! */
> +		if (uid_eq(ace->e_id.uid, make_kuid(&init_user_ns, 99))) {
> +			*unmapped = who;
> +			*unmapped_len = len;
> +		}
> +	}
> +	return 0;
> +}
> +
> +static struct richacl *
> +decode_acl_entries(struct xdr_stream *xdr, const struct nfs_server *server)
> +{
> +	struct richacl *acl;
> +	struct richace *ace;
> +	uint32_t count;
> +	__be32 *p;
> +	int status;
> +
> +	p = xdr_inline_decode(xdr, 4);
> +	if (unlikely(!p))
> +		return ERR_PTR(-ENOMEM);  /* acl truncated */
> +	count = be32_to_cpup(p);
> +	if (count > RICHACL_XATTR_MAX_COUNT)
> +		return ERR_PTR(-EIO);
> +	acl = richacl_alloc(count, GFP_NOFS);
> +	if (!acl)
> +		return ERR_PTR(-ENOMEM);
> +	richacl_for_each_entry(ace, acl) {
> +		const char *unmapped = NULL;
> +		unsigned int unmapped_len;
> +
> +		p = xdr_inline_decode(xdr, 4*3);
> +		status = -ENOMEM;
> +		if (unlikely(!p))
> +			goto out;  /* acl truncated */
> +		ace->e_type = be32_to_cpup(p++);
> +		ace->e_flags = be32_to_cpup(p++);
> +		status = -EIO;
> +		if (ace->e_flags &
> +		    (RICHACE_SPECIAL_WHO | RICHACE_UNMAPPED_WHO))
> +			goto out;
> +		ace->e_mask = be32_to_cpup(p++);
> +		status = nfs4_decode_ace_who(ace, &unmapped,
> +					     &unmapped_len, server,
> +					     xdr);
> +		if (status)
> +			goto out;
> +		if (unmapped) {
> +			status = -ENOMEM;
> +			if (richacl_add_unmapped_identifier(&acl, &ace,
> +					unmapped, unmapped_len,
> +					GFP_NOFS))
> +				goto out;
> +		}
> +	}
> +	status = 0;
> +
> +out:
> +	if (status) {
> +		richacl_put(acl);
> +		acl = ERR_PTR(status);
> +	}
> +	return acl;
> +}
> +
>  static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  			 struct nfs_getaclres *res)
>  {
>  	unsigned int savep;
>  	uint32_t attrlen,
>  		 bitmap[3] = {0};
> +	struct richacl *acl = NULL;
>  	int status;
> -	unsigned int pg_offset;
>  
> -	res->acl_len = 0;
>  	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
>  		goto out;
> -
> -	xdr_enter_page(xdr, xdr->buf->page_len);
> -
> -	/* Calculate the offset of the page data */
> -	pg_offset = xdr->buf->head[0].iov_len;
> -
>  	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
>  		goto out;
>  	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
> @@ -5286,24 +5409,28 @@ static int decode_getacl(struct xdr_stream *xdr, struct rpc_rqst *req,
>  	if (unlikely(bitmap[0] & (FATTR4_WORD0_ACL - 1U)))
>  		return -EIO;
>  	if (likely(bitmap[0] & FATTR4_WORD0_ACL)) {
> -
> -		/* The bitmap (xdr len + bitmaps) and the attr xdr len words
> -		 * are stored with the acl data to handle the problem of
> -		 * variable length bitmaps.*/
> -		res->acl_data_offset = xdr_stream_pos(xdr) - pg_offset;
> -		res->acl_len = attrlen;
> -
> -		/* Check for receive buffer overflow */
> -		if (res->acl_len > (xdr->nwords << 2) ||
> -		    res->acl_len + res->acl_data_offset > xdr->buf->page_len) {
> -			res->acl_flags |= NFS4_ACL_TRUNC;
> -			dprintk("NFS: acl reply: attrlen %u > page_len %u\n",
> -					attrlen, xdr->nwords << 2);
> -		}
> +		acl = decode_acl_entries(xdr, res->server);
> +		status = PTR_ERR(acl);
> +		if (IS_ERR(acl))
> +			goto out;
> +		bitmap[0] &= ~FATTR4_WORD0_ACL;
>  	} else
>  		status = -EOPNOTSUPP;
>  
> +	status = -EIO;
> +	if (unlikely(bitmap[0]))
> +		goto out;
> +
> +	status = decode_attr_mode(xdr, bitmap, &res->mode);
> +	if (status < 0)
> +		goto out;
> +	status = 0;
> +
>  out:
> +	if (status == 0)
> +		res->acl = acl;
> +	else
> +		richacl_put(acl);
>  	return status;
>  }
>  
> diff --git a/fs/nfs/super.c b/fs/nfs/super.c
> index 383a027..8ced33d 100644
> --- a/fs/nfs/super.c
> +++ b/fs/nfs/super.c
> @@ -2319,7 +2319,7 @@ void nfs_fill_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  		sb->s_time_gran = 1;
>  	}
>  
> @@ -2346,7 +2346,7 @@ void nfs_clone_super(struct super_block *sb, struct nfs_mount_info *mount_info)
>  		/* The VFS shouldn't apply the umask to mode bits. We will do
>  		 * so ourselves when necessary.
>  		 */
> -		sb->s_flags |= MS_POSIXACL;
> +		sb->s_flags |= MS_RICHACL;
>  	}
>  
>   	nfs_initialise_sb(sb);
> diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
> index c0e9614..b84e194 100644
> --- a/include/linux/nfs_fs.h
> +++ b/include/linux/nfs_fs.h
> @@ -176,7 +176,6 @@ struct nfs_inode {
>  	wait_queue_head_t	waitqueue;
>  
>  #if IS_ENABLED(CONFIG_NFS_V4)
> -	struct nfs4_cached_acl	*nfs4_acl;
>          /* NFSv4 state */
>  	struct list_head	open_states;
>  	struct nfs_delegation __rcu *delegation;
> diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
> index 570a7df..6c41668 100644
> --- a/include/linux/nfs_fs_sb.h
> +++ b/include/linux/nfs_fs_sb.h
> @@ -243,5 +243,7 @@ struct nfs_server {
>  #define NFS_CAP_ALLOCATE	(1U << 20)
>  #define NFS_CAP_DEALLOCATE	(1U << 21)
>  #define NFS_CAP_LAYOUTSTATS	(1U << 22)
> +#define NFS_CAP_ALLOW_ACLS	(1U << 23)
> +#define NFS_CAP_DENY_ACLS	(1U << 24)
>  
>  #endif
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index 090ade4..337c341 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -683,9 +683,10 @@ struct nfs_setattrargs {
>  
>  struct nfs_setaclargs {
>  	struct nfs4_sequence_args	seq_args;
> +	const struct nfs_server *	server;
>  	struct nfs_fh *			fh;
> -	size_t				acl_len;
>  	struct page **			acl_pages;
> +	size_t				acl_len;
>  };
>  
>  struct nfs_setaclres {
> @@ -703,9 +704,9 @@ struct nfs_getaclargs {
>  #define NFS4_ACL_TRUNC		0x0001	/* ACL was truncated */
>  struct nfs_getaclres {
>  	struct nfs4_sequence_res	seq_res;
> -	size_t				acl_len;
> -	size_t				acl_data_offset;
> -	int				acl_flags;
> +	const struct nfs_server *	server;
> +	struct richacl *		acl;
> +	umode_t				mode;
>  	struct page *			acl_scratch;
>  };
>  
> 

  parent reply	other threads:[~2015-10-12 14:39 UTC|newest]

Thread overview: 126+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-10-11 22:58 [PATCH v10 00/46] Richacls Andreas Gruenbacher
2015-10-11 22:58 ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 01/46] vfs: Add IS_ACL() and IS_RICHACL() tests Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 02/46] vfs: Add MAY_CREATE_FILE and MAY_CREATE_DIR permission flags Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 03/46] vfs: Add MAY_DELETE_SELF and MAY_DELETE_CHILD " Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 04/46] vfs: Make the inode passed to inode_change_ok non-const Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 05/46] vfs: Add permission flags for setting file attributes Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 06/46] richacl: In-memory representation and helper functions Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 07/46] richacl: Permission mapping functions Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 08/46] richacl: Compute maximum file masks from an acl Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 09/46] richacl: Permission check algorithm Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
     [not found] ` <1444604337-17651-1-git-send-email-andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2015-10-11 22:58   ` [PATCH v10 10/46] vfs: Cache base_acl objects in inodes Andreas Gruenbacher
2015-10-11 22:58     ` Andreas Gruenbacher
2015-10-11 22:58     ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 11/46] vfs: Add get_richacl and set_richacl inode operations Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 12/46] vfs: Cache richacl in struct inode Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 13/46] richacl: Update the file masks in chmod() Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 14/46] richacl: Check if an acl is equivalent to a file mode Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 15/46] richacl: Create-time inheritance Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 16/46] richacl: Automatic Inheritance Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 17/46] richacl: xattr mapping functions Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 18/46] richacl: Add richacl xattr handler Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 19/46] vfs: Add richacl permission checking Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 20/46] ext4: Add richacl support Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 21/46] ext4: Add richacl feature flag Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 22/46] xfs: Fix error path in xfs_get_acl Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 23/46] xfs: Make xfs_set_mode non-static Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 23:37   ` Dave Chinner
2015-10-11 23:37     ` Dave Chinner
2015-10-11 22:58 ` [PATCH v10 24/46] xfs: Add richacl support Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
     [not found]   ` <1444604337-17651-25-git-send-email-andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2015-10-12  0:10     ` Dave Chinner
2015-10-12  0:10       ` Dave Chinner
2015-10-12  0:10       ` Dave Chinner
2015-10-12  1:51       ` Andreas Grünbacher
2015-10-12  1:51         ` Andreas Grünbacher
2015-10-12  1:51         ` Andreas Grünbacher
     [not found]         ` <CAHpGcMKeJHDegs2cYKaJdX4Tw43Jp30Nv_2WoSNZfBzGJKu=BQ-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-10-12  4:05           ` Dave Chinner
2015-10-12  4:05             ` Dave Chinner
2015-10-12  4:05             ` Dave Chinner
2015-10-12  5:57             ` Andreas Gruenbacher
2015-10-12  5:57               ` Andreas Gruenbacher
2015-10-12  5:57               ` Andreas Gruenbacher
     [not found]               ` <CAHc6FU55eOK4gWH1bhKvoujQ1zkT+we0xcfPUOeWrF_X0XHXZg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2015-10-13 19:21                 ` Austin S Hemmelgarn
2015-10-13 19:21                   ` Austin S Hemmelgarn
2015-10-13 19:21                   ` Austin S Hemmelgarn
2015-10-13 13:39             ` Andreas Gruenbacher
2015-10-13 13:39               ` Andreas Gruenbacher
2015-10-13 13:39               ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 25/46] richacl: acl editing helper functions Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 26/46] richacl: Move everyone@ aces down the acl Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 27/46] richacl: Propagate everyone@ permissions to other aces Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 28/46] richacl: Set the owner permissions to the owner mask Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 29/46] richacl: Set the other permissions to the other mask Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 30/46] richacl: Isolate the owner and group classes Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 31/46] richacl: Apply the file masks to a richacl Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 32/46] richacl: Create richacl from mode values Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 33/46] nfsd: Keep list of acls to dispose of in compoundargs Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 34/46] nfsd: Use richacls as internal acl representation Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 35/46] nfsd: Add richacl support Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 36/46] nfsd: Add support for the v4.1 dacl attribute Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 37/46] nfsd: Add support for the MAY_CREATE_{FILE, DIR} permissions Andreas Gruenbacher
2015-10-11 22:58   ` [PATCH v10 37/46] nfsd: Add support for the MAY_CREATE_{FILE,DIR} permissions Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 38/46] richacl: Add support for unmapped identifiers Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-12  0:22   ` Dave Chinner
2015-10-12  0:22     ` Dave Chinner
2015-10-12  1:53     ` Andreas Grünbacher
2015-10-12  1:53       ` Andreas Grünbacher
2015-10-12  1:53       ` Andreas Grünbacher
2015-10-11 22:58 ` [PATCH v10 39/46] ext4: Don't allow unmapped identifiers in richacls Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 40/46] sunrpc: Allow to demand-allocate pages to encode into Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 41/46] sunrpc: Add xdr_init_encode_pages Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 42/46] nfs: Fix GETATTR bitmap verification Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 43/46] nfs: Remove unused xdr page offsets in getacl/setacl arguments Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 44/46] nfs: Add richacl support Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
     [not found]   ` <1444604337-17651-45-git-send-email-andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2015-10-12 14:39     ` Anna Schumaker [this message]
2015-10-12 14:39       ` Anna Schumaker
2015-10-12 14:39       ` Anna Schumaker
2015-10-12 14:39       ` Anna Schumaker
2015-10-12 19:49       ` Andreas Gruenbacher
2015-10-12 19:49         ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 45/46] nfs: Add support for the v4.1 dacl attribute Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher
2015-10-11 22:58 ` [PATCH v10 46/46] richacl: uapi header split Andreas Gruenbacher
2015-10-11 22:58   ` Andreas Gruenbacher

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=561BC61E.6010704@Netapp.com \
    --to=anna.schumaker-hgovqubeegtqt0dzr+alfa@public.gmane.org \
    --cc=adilger.kernel-m1MBpc4rdrD3fQ9qLvQP4Q@public.gmane.org \
    --cc=agruenba-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org \
    --cc=andreas.gruenbacher-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org \
    --cc=bfields-uC3wQj2KruNg9hUCZPvPmw@public.gmane.org \
    --cc=david-FqsqvQoI3Ljby3iVrkZq2A@public.gmane.org \
    --cc=jlayton-vpEMnDpepFuMZCB2o+C8xQ@public.gmane.org \
    --cc=linux-api-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-cifs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-ext4-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-fsdevel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=linux-nfs-u79uwXL29TY76Z2rM5mHXA@public.gmane.org \
    --cc=trond.myklebust-7I+n7zu2hftEKMMhf/gKZA@public.gmane.org \
    --cc=tytso-3s7WtUTddSA@public.gmane.org \
    --cc=viro-RmSDqhL/yNMiFSDQTTA3OLVCufUGDwFn@public.gmane.org \
    --cc=xfs-VZNHf3L845pBDgjK7y7TUQ@public.gmane.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.