Re: [PATCH 11/14] nfsd: add user xattr RPC XDR encoding/decoding logic

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




> On Mar 11, 2020, at 3:59 PM, Frank van der Linden <fllinden@xxxxxxxxxx> wrote:
> 
> Add functions to calculate the reply size for the user extended attribute
> operations, and implement the XDR encode / decode logic for these
> operations.
> 
> Signed-off-by: Frank van der Linden <fllinden@xxxxxxxxxx>
> ---
> fs/nfsd/nfs4proc.c |  36 +++++
> fs/nfsd/nfs4xdr.c  | 448 +++++++++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 484 insertions(+)
> 
> diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
> index a76b9025a357..44d488bdebd9 100644
> --- a/fs/nfsd/nfs4proc.c
> +++ b/fs/nfsd/nfs4proc.c
> @@ -2778,6 +2778,42 @@ static inline u32 nfsd4_seek_rsize(struct svc_rqst *rqstp, struct nfsd4_op *op)
> 	return (op_encode_hdr_size + 3) * sizeof(__be32);
> }
> 
> +static inline u32 nfsd4_getxattr_rsize(struct svc_rqst *rqstp,
> +				       struct nfsd4_op *op)
> +{
> +	u32 maxcount, rlen;
> +
> +	maxcount = svc_max_payload(rqstp);
> +	rlen = min_t(u32, XATTR_SIZE_MAX, maxcount);
> +
> +	return (op_encode_hdr_size + 1 + XDR_QUADLEN(rlen)) * sizeof(__be32);

These should be added in the same patch that adds OP_GETXATTR and friends.

Also, Trond recently added xdr_align_size which I prefer over the
use of XDR_QUADLEN in new code.


> +}
> +
> +static inline u32 nfsd4_setxattr_rsize(struct svc_rqst *rqstp,
> +				       struct nfsd4_op *op)
> +{
> +	return (op_encode_hdr_size + op_encode_change_info_maxsz)
> +		* sizeof(__be32);
> +}
> +static inline u32 nfsd4_listxattrs_rsize(struct svc_rqst *rqstp,
> +					 struct nfsd4_op *op)
> +{
> +	u32 maxcount, rlen;
> +
> +	maxcount = svc_max_payload(rqstp);
> +	rlen = min(op->u.listxattrs.lsxa_maxcount, maxcount);
> +
> +	return (op_encode_hdr_size + 4 + XDR_QUADLEN(rlen)) * sizeof(__be32);
> +}
> +
> +static inline u32 nfsd4_removexattr_rsize(struct svc_rqst *rqstp,
> +					  struct nfsd4_op *op)
> +{
> +	return (op_encode_hdr_size + op_encode_change_info_maxsz)
> +		* sizeof(__be32);
> +}
> +
> +
> static const struct nfsd4_operation nfsd4_ops[LAST_NFS4_OP + 1] = {
> 	[OP_ACCESS] = {
> 		.op_func = nfsd4_access,
> diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c
> index b12d7ac6f52c..41c8b95ca1c5 100644
> --- a/fs/nfsd/nfs4xdr.c
> +++ b/fs/nfsd/nfs4xdr.c
> @@ -1879,6 +1879,224 @@ nfsd4_decode_seek(struct nfsd4_compoundargs *argp, struct nfsd4_seek *seek)
> 	DECODE_TAIL;
> }
> 
> +/*
> + * XDR data that is more than PAGE_SIZE in size is normally part of a
> + * read or write. However, the size of extended attributes is limited
> + * by the maximum request size, and then further limited by the underlying
> + * filesystem limits. This can exceed PAGE_SIZE (currently, XATTR_SIZE_MAX
> + * is 64k). Since there is no kvec- or page-based interface to xattrs,
> + * and we're not dealing with contiguous pages, we need to do some copying.
> + */
> +
> +/*
> + * Decode int to buffer. Uses head and pages constructed by
> + * svcxdr_construct_vector.
> + */
> +static int
> +nfsd4_vbuf_from_stream(struct nfsd4_compoundargs *argp, struct kvec *head,
> +		       struct page **pages, char **bufp, u32 buflen)
> +{
> +	char *tmp, *dp;
> +	u32 len;
> +
> +	if (buflen <= head->iov_len) {
> +		/*
> +		 * We're in luck, the head has enough space. Just return
> +		 * the head, no need for copying.
> +		 */
> +		*bufp = head->iov_base;
> +		return 0;
> +	}
> +
> +	tmp = svcxdr_tmpalloc(argp, buflen);
> +	if (tmp == NULL)
> +		return nfserr_jukebox;
> +
> +	dp = tmp;
> +	memcpy(dp, head->iov_base, head->iov_len);
> +	buflen -= head->iov_len;
> +	dp += head->iov_len;
> +
> +	while (buflen > 0) {
> +		len = min_t(u32, buflen, PAGE_SIZE);
> +		memcpy(dp, page_address(*pages), len);
> +
> +		buflen -= len;
> +		dp += len;
> +		pages++;
> +	}
> +
> +	*bufp = tmp;
> +	return 0;
> +}
> +
> +/*
> + * Get a user extended attribute name from the XDR buffer.
> + * It will not have the "user." prefix, so prepend it.
> + * Lastly, check for nul characters in the name.
> + */
> +static int
> +nfsd4_decode_xattr_name(struct nfsd4_compoundargs *argp, char **namep)
> +{
> +	DECODE_HEAD;
> +	char *name, *sp, *dp;
> +	u32 namelen, cnt;
> +
> +	READ_BUF(4);
> +	namelen = be32_to_cpup(p++);
> +
> +	if (namelen > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN))
> +		return nfserr_nametoolong;
> +
> +	if (namelen == 0)
> +		goto xdr_error;
> +
> +	READ_BUF(namelen);
> +
> +	name = svcxdr_tmpalloc(argp, namelen + XATTR_USER_PREFIX_LEN + 1);
> +	if (!name)
> +		return nfserr_jukebox;
> +
> +	memcpy(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
> +
> +	/*
> +	 * Copy the extended attribute name over while checking for 0
> +	 * characters.
> +	 */
> +	sp = (char *)p;
> +	dp = name + XATTR_USER_PREFIX_LEN;
> +	cnt = namelen;
> +
> +	while (cnt-- > 0) {
> +		if (*sp == '\0')
> +			goto xdr_error;
> +		*dp++ = *sp++;
> +	}
> +	*dp = '\0';
> +
> +	*namep = name;
> +
> +	DECODE_TAIL;
> +}
> +
> +/*
> + * A GETXATTR op request comes without a length specifier. We just set the
> + * maximum length for the reply based on XATTR_SIZE_MAX and the maximum
> + * channel reply size, allocate a buffer of that length and pass it to
> + * vfs_getxattr.
> + */
> +static __be32
> +nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp,
> +		      struct nfsd4_getxattr *getxattr)
> +{
> +	int status;
> +	u32 maxcount;
> +
> +	status = nfsd4_decode_xattr_name(argp, &getxattr->getxa_name);
> +	if (status)
> +		return status;
> +
> +	maxcount = svc_max_payload(argp->rqstp);
> +	maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount);
> +
> +	getxattr->getxa_buf = svcxdr_tmpalloc(argp, maxcount);
> +	if (!getxattr->getxa_buf)
> +		status = nfserr_jukebox;
> +	getxattr->getxa_len = maxcount;
> +
> +	return status;
> +}
> +
> +static __be32
> +nfsd4_decode_setxattr(struct nfsd4_compoundargs *argp,
> +		      struct nfsd4_setxattr *setxattr)
> +{
> +	DECODE_HEAD;
> +	u32 flags, maxcount, size;
> +	struct kvec head;
> +	struct page **pagelist;
> +
> +	READ_BUF(4);
> +	flags = be32_to_cpup(p++);
> +
> +	if (flags > SETXATTR4_REPLACE)
> +		return nfserr_inval;
> +	setxattr->setxa_flags = flags;
> +
> +	status = nfsd4_decode_xattr_name(argp, &setxattr->setxa_name);
> +	if (status)
> +		return status;
> +
> +	maxcount = svc_max_payload(argp->rqstp);
> +	maxcount = min_t(u32, XATTR_SIZE_MAX, maxcount);
> +
> +	READ_BUF(4);
> +	size = be32_to_cpup(p++);
> +	if (size > maxcount)
> +		return nfserr_xattr2big;
> +
> +	setxattr->setxa_len = size;
> +	if (size > 0) {
> +		status = svcxdr_construct_vector(argp, &head, &pagelist, size);
> +		if (status)
> +			return status;
> +
> +		status = nfsd4_vbuf_from_stream(argp, &head, pagelist,
> +		    &setxattr->setxa_buf, size);
> +	}
> +
> +	DECODE_TAIL;
> +}
> +
> +static __be32
> +nfsd4_decode_listxattrs(struct nfsd4_compoundargs *argp,
> +			struct nfsd4_listxattrs *listxattrs)
> +{
> +	DECODE_HEAD;
> +	u32 maxcount;
> +
> +	READ_BUF(12);
> +	p = xdr_decode_hyper(p, &listxattrs->lsxa_cookie);
> +
> +	/*
> +	 * If the cookie  is too large to have even one user.x attribute
> +	 * plus trailing '\0' left in a maximum size buffer, it's invalid.
> +	 */
> +	if (listxattrs->lsxa_cookie >=
> +	    (XATTR_LIST_MAX / (XATTR_USER_PREFIX_LEN + 2)))
> +		return nfserr_badcookie;
> +
> +	maxcount = be32_to_cpup(p++);
> +	if (maxcount < 8)
> +		/* Always need at least 2 words (length and one character) */
> +		return nfserr_inval;
> +
> +	maxcount = min(maxcount, svc_max_payload(argp->rqstp));
> +	listxattrs->lsxa_maxcount = maxcount;
> +
> +	/*
> +	 * Unfortunately, there is no interface to only list xattrs for
> +	 * one prefix. So there is no good way to convert maxcount to
> +	 * a maximum value to pass to vfs_listxattr, as we don't know
> +	 * how many of the returned attributes will be user attributes.
> +	 *
> +	 * So, always ask vfs_listxattr for the maximum size, and encode
> +	 * as many as possible.
> +	 */

Well, this approach worries me a little bit. Wouldn't it be better if the
VFS provided the APIs? Review by linux-fsdevel might help here.


> +	listxattrs->lsxa_buf = svcxdr_tmpalloc(argp, XATTR_LIST_MAX);
> +	if (!listxattrs->lsxa_buf)
> +		status = nfserr_jukebox;
> +
> +	DECODE_TAIL;
> +}
> +
> +static __be32
> +nfsd4_decode_removexattr(struct nfsd4_compoundargs *argp,
> +			 struct nfsd4_removexattr *removexattr)
> +{
> +	return nfsd4_decode_xattr_name(argp, &removexattr->rmxa_name);
> +}
> +
> static __be32
> nfsd4_decode_noop(struct nfsd4_compoundargs *argp, void *p)
> {
> @@ -1975,6 +2193,11 @@ static const nfsd4_dec nfsd4_dec_ops[] = {
> 	[OP_SEEK]		= (nfsd4_dec)nfsd4_decode_seek,
> 	[OP_WRITE_SAME]		= (nfsd4_dec)nfsd4_decode_notsupp,
> 	[OP_CLONE]		= (nfsd4_dec)nfsd4_decode_clone,
> +	/* RFC 8276 extended atributes operations */
> +	[OP_GETXATTR]		= (nfsd4_dec)nfsd4_decode_getxattr,
> +	[OP_SETXATTR]		= (nfsd4_dec)nfsd4_decode_setxattr,
> +	[OP_LISTXATTRS]		= (nfsd4_dec)nfsd4_decode_listxattrs,
> +	[OP_REMOVEXATTR]	= (nfsd4_dec)nfsd4_decode_removexattr,
> };
> 
> static inline bool
> @@ -4458,6 +4681,225 @@ nfsd4_encode_noop(struct nfsd4_compoundres *resp, __be32 nfserr, void *p)
> 	return nfserr;
> }
> 
> +/*
> + * Encode kmalloc-ed buffer in to XDR stream.
> + */
> +static int
> +nfsd4_vbuf_to_stream(struct xdr_stream *xdr, char *buf, u32 buflen)
> +{
> +	u32 cplen;
> +	__be32 *p;
> +
> +	cplen = min_t(unsigned long, buflen,
> +		      ((void *)xdr->end - (void *)xdr->p));
> +	p = xdr_reserve_space(xdr, cplen);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	memcpy(p, buf, cplen);
> +	buf += cplen;
> +	buflen -= cplen;
> +
> +	while (buflen) {
> +		cplen = min_t(u32, buflen, PAGE_SIZE);
> +		p = xdr_reserve_space(xdr, cplen);
> +		if (!p)
> +			return nfserr_resource;
> +
> +		memcpy(p, buf, cplen);
> +
> +		if (cplen < PAGE_SIZE) {
> +			/*
> +			 * We're done, with a length that wasn't page
> +			 * aligned, so possibly not word aligned. Pad
> +			 * any trailing bytes with 0.
> +			 */
> +			xdr_encode_opaque_fixed(p, NULL, cplen);
> +			break;
> +		}
> +
> +		buflen -= PAGE_SIZE;
> +		buf += PAGE_SIZE;
> +	}
> +
> +	return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_getxattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> +		      struct nfsd4_getxattr *getxattr)
> +{
> +	struct xdr_stream *xdr = &resp->xdr;
> +	__be32 *p;
> +
> +	p = xdr_reserve_space(xdr, 4);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	*p = cpu_to_be32(getxattr->getxa_len);
> +
> +	if (getxattr->getxa_len == 0)
> +		return 0;
> +
> +	return nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf,
> +				    getxattr->getxa_len);
> +}
> +
> +static __be32
> +nfsd4_encode_setxattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> +		      struct nfsd4_setxattr *setxattr)
> +{
> +	struct xdr_stream *xdr = &resp->xdr;
> +	__be32 *p;
> +
> +	p = xdr_reserve_space(xdr, 20);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	encode_cinfo(p, &setxattr->setxa_cinfo);
> +
> +	return 0;
> +}
> +
> +/*
> + * See if there are cookie values that can be rejected outright.
> + */
> +static int
> +nfsd4_listxattr_validate_cookie(struct nfsd4_listxattrs *listxattrs,
> +				u32 *offsetp)
> +{
> +	u64 cookie = listxattrs->lsxa_cookie;
> +
> +	/*
> +	 * If the cookie is larger than the maximum number we can fit
> +	 * in either the buffer we just got back from vfs_listxattr, or,
> +	 * XDR-encoded, in the return buffer, it's invalid.
> +	 */
> +	if (cookie > (listxattrs->lsxa_len) / (XATTR_USER_PREFIX_LEN + 2))
> +		return nfserr_badcookie;
> +
> +	if (cookie > (listxattrs->lsxa_maxcount /
> +		      (XDR_QUADLEN(XATTR_USER_PREFIX_LEN + 2) + 4)))
> +		return nfserr_badcookie;
> +
> +	*offsetp = (u32)cookie;
> +	return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_listxattrs(struct nfsd4_compoundres *resp, __be32 nfserr,
> +			struct nfsd4_listxattrs *listxattrs)
> +{
> +	struct xdr_stream *xdr = &resp->xdr;
> +	u32 cookie_offset, count_offset, eof;
> +	u32 left, xdrleft, slen, count;
> +	u32 xdrlen, offset;
> +	u64 cookie;
> +	char *sp;
> +	int status;
> +	__be32 *p;
> +	u32 nuser;
> +
> +	eof = 1;
> +
> +	status = nfsd4_listxattr_validate_cookie(listxattrs, &offset);
> +	if (status)
> +		return status;
> +
> +	/*
> +	 * Reserve space for the cookie and the name array count. Record
> +	 * the offsets to save them later.
> +	 */
> +	cookie_offset = xdr->buf->len;
> +	count_offset = cookie_offset + 8;
> +	p = xdr_reserve_space(xdr, 12);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	count = 0;
> +	left = listxattrs->lsxa_len;
> +	sp = listxattrs->lsxa_buf;
> +	nuser = 0;
> +
> +	xdrleft = listxattrs->lsxa_maxcount;
> +
> +	while (left > 0 && xdrleft > 0) {
> +		slen = strlen(sp);
> +
> +		/*
> +		 * Check if this a user. attribute, skip it if not.
> +		 */
> +		if (strncmp(sp, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
> +			goto contloop;
> +
> +		slen -= XATTR_USER_PREFIX_LEN;
> +		xdrlen = 4 + ((slen + 3) & ~3);
> +		if (xdrlen > xdrleft) {
> +			if (count == 0) {
> +				/*
> +				 * Can't even fit the first attribute name.
> +				 */
> +				return nfserr_toosmall;
> +			}
> +			eof = 0;
> +			goto wreof;
> +		}
> +
> +		left -= XATTR_USER_PREFIX_LEN;
> +		sp += XATTR_USER_PREFIX_LEN;
> +		if (nuser++ < offset)
> +			goto contloop;
> +
> +
> +		p = xdr_reserve_space(xdr, xdrlen);
> +		if (!p)
> +			return nfserr_resource;
> +
> +		p = xdr_encode_opaque(p, sp, slen);
> +
> +		xdrleft -= xdrlen;
> +		count++;
> +contloop:
> +		sp += slen + 1;
> +		left -= slen + 1;
> +	}
> +
> +	/*
> +	 * If there were user attributes to copy, but we didn't copy
> +	 * any, the offset was too large (e.g. the cookie was invalid).
> +	 */
> +	if (nuser > 0 && count == 0)
> +		return nfserr_badcookie;
> +
> +wreof:
> +	p = xdr_reserve_space(xdr, 4);
> +	if (!p)
> +		return nfserr_resource;
> +	*p = cpu_to_be32(eof);
> +
> +	cookie = offset + count;
> +
> +	write_bytes_to_xdr_buf(xdr->buf, cookie_offset, &cookie, 8);
> +	count = htonl(count);
> +	write_bytes_to_xdr_buf(xdr->buf, count_offset, &count, 4);
> +	return 0;
> +}
> +
> +static __be32
> +nfsd4_encode_removexattr(struct nfsd4_compoundres *resp, __be32 nfserr,
> +			 struct nfsd4_removexattr *removexattr)
> +{
> +	struct xdr_stream *xdr = &resp->xdr;
> +	__be32 *p;
> +
> +	p = xdr_reserve_space(xdr, 20);
> +	if (!p)
> +		return nfserr_resource;
> +
> +	p = encode_cinfo(p, &removexattr->rmxa_cinfo);
> +	return 0;
> +}
> +
> typedef __be32(* nfsd4_enc)(struct nfsd4_compoundres *, __be32, void *);
> 
> /*
> @@ -4547,6 +4989,12 @@ static const nfsd4_enc nfsd4_enc_ops[] = {
> 	[OP_SEEK]		= (nfsd4_enc)nfsd4_encode_seek,
> 	[OP_WRITE_SAME]		= (nfsd4_enc)nfsd4_encode_noop,
> 	[OP_CLONE]		= (nfsd4_enc)nfsd4_encode_noop,
> +
> +	/* RFC 8276 extended atributes operations */
> +	[OP_GETXATTR]		= (nfsd4_enc)nfsd4_encode_getxattr,
> +	[OP_SETXATTR]		= (nfsd4_enc)nfsd4_encode_setxattr,
> +	[OP_LISTXATTRS]		= (nfsd4_enc)nfsd4_encode_listxattrs,
> +	[OP_REMOVEXATTR]	= (nfsd4_enc)nfsd4_encode_removexattr,
> };
> 
> /*
> -- 
> 2.16.6
> 

--
Chuck Lever







[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux