Commit 23e50fe3 authored by Frank van der Linden's avatar Frank van der Linden Committed by Chuck Lever
Browse files

nfsd: implement the xattr functions and en/decode logic



Implement the main entry points for the *XATTR operations.

Add functions to calculate the reply size for the user extended attribute
operations, and implement the XDR encode / decode logic for these
operations.

Add the user extended attributes operations to nfsd4_ops.

Signed-off-by: default avatarFrank van der Linden <fllinden@amazon.com>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent 6178713b
Loading
Loading
Loading
Loading
+120 −0
Original line number Diff line number Diff line
@@ -2097,6 +2097,68 @@ out:
}
#endif /* CONFIG_NFSD_PNFS */

static __be32
nfsd4_getxattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
	       union nfsd4_op_u *u)
{
	struct nfsd4_getxattr *getxattr = &u->getxattr;

	return nfsd_getxattr(rqstp, &cstate->current_fh,
			     getxattr->getxa_name, &getxattr->getxa_buf,
			     &getxattr->getxa_len);
}

static __be32
nfsd4_setxattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
	   union nfsd4_op_u *u)
{
	struct nfsd4_setxattr *setxattr = &u->setxattr;
	__be32 ret;

	if (opens_in_grace(SVC_NET(rqstp)))
		return nfserr_grace;

	ret = nfsd_setxattr(rqstp, &cstate->current_fh, setxattr->setxa_name,
			    setxattr->setxa_buf, setxattr->setxa_len,
			    setxattr->setxa_flags);

	if (!ret)
		set_change_info(&setxattr->setxa_cinfo, &cstate->current_fh);

	return ret;
}

static __be32
nfsd4_listxattrs(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
	   union nfsd4_op_u *u)
{
	/*
	 * Get the entire list, then copy out only the user attributes
	 * in the encode function.
	 */
	return nfsd_listxattr(rqstp, &cstate->current_fh,
			     &u->listxattrs.lsxa_buf, &u->listxattrs.lsxa_len);
}

static __be32
nfsd4_removexattr(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
	   union nfsd4_op_u *u)
{
	struct nfsd4_removexattr *removexattr = &u->removexattr;
	__be32 ret;

	if (opens_in_grace(SVC_NET(rqstp)))
		return nfserr_grace;

	ret = nfsd_removexattr(rqstp, &cstate->current_fh,
	    removexattr->rmxa_name);

	if (!ret)
		set_change_info(&removexattr->rmxa_cinfo, &cstate->current_fh);

	return ret;
}

/*
 * NULL call.
 */
@@ -2706,6 +2768,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);
}

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[] = {
	[OP_ACCESS] = {
		.op_func = nfsd4_access,
@@ -3087,6 +3185,28 @@ static const struct nfsd4_operation nfsd4_ops[] = {
		.op_name = "OP_COPY_NOTIFY",
		.op_rsize_bop = nfsd4_copy_notify_rsize,
	},
	[OP_GETXATTR] = {
		.op_func = nfsd4_getxattr,
		.op_name = "OP_GETXATTR",
		.op_rsize_bop = nfsd4_getxattr_rsize,
	},
	[OP_SETXATTR] = {
		.op_func = nfsd4_setxattr,
		.op_flags = OP_MODIFIES_SOMETHING | OP_CACHEME,
		.op_name = "OP_SETXATTR",
		.op_rsize_bop = nfsd4_setxattr_rsize,
	},
	[OP_LISTXATTRS] = {
		.op_func = nfsd4_listxattrs,
		.op_name = "OP_LISTXATTRS",
		.op_rsize_bop = nfsd4_listxattrs_rsize,
	},
	[OP_REMOVEXATTR] = {
		.op_func = nfsd4_removexattr,
		.op_flags = OP_MODIFIES_SOMETHING | OP_CACHEME,
		.op_name = "OP_REMOVEXATTR",
		.op_rsize_bop = nfsd4_removexattr_rsize,
	},
};

/**
+450 −0
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@
#include <linux/pagemap.h>
#include <linux/sunrpc/svcauth_gss.h>
#include <linux/sunrpc/addr.h>
#include <linux/xattr.h>
#include <uapi/linux/xattr.h>

#include "idmap.h"
#include "acl.h"
@@ -1877,6 +1879,208 @@ 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 data into buffer. Uses head and pages constructed by
 * svcxdr_construct_vector.
 */
static __be32
nfsd4_vbuf_from_vector(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 __be32
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. nfsd_getxattr will probe the length of the xattr,
 * check it against getxa_len, and allocate + return the value.
 */
static __be32
nfsd4_decode_getxattr(struct nfsd4_compoundargs *argp,
		      struct nfsd4_getxattr *getxattr)
{
	__be32 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_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_vector(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;

	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)
{
@@ -1973,6 +2177,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 +4667,241 @@ 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, err;

	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;

	err = nfsd4_vbuf_to_stream(xdr, getxattr->getxa_buf,
				    getxattr->getxa_len);

	kvfree(getxattr->getxa_buf);

	return err;
}

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 __be32
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;
	__be32 status;
	__be32 *p;
	u32 nuser;

	eof = 1;

	status = nfsd4_listxattr_validate_cookie(listxattrs, &offset);
	if (status)
		goto out;

	/*
	 * 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) {
		status = nfserr_resource;
		goto out;
	}

	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.
				 */
				status = nfserr_toosmall;
				goto out;
			}
			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) {
			status = nfserr_resource;
			goto out;
		}

		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) {
		status = nfserr_badcookie;
		goto out;
	}

wreof:
	p = xdr_reserve_space(xdr, 4);
	if (!p) {
		status = nfserr_resource;
		goto out;
	}
	*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);
out:
	if (listxattrs->lsxa_len)
		kvfree(listxattrs->lsxa_buf);
	return status;
}

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 +4991,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,
};

/*
+1 −1
Original line number Diff line number Diff line
@@ -165,7 +165,7 @@ Needs to be updated if more operations are defined in future.*/
#define FIRST_NFS4_OP	OP_ACCESS
#define LAST_NFS40_OP	OP_RELEASE_LOCKOWNER
#define LAST_NFS41_OP	OP_RECLAIM_COMPLETE
#define LAST_NFS42_OP	OP_CLONE
#define LAST_NFS42_OP	OP_REMOVEXATTR
#define LAST_NFS4_OP	LAST_NFS42_OP

enum nfsstat4 {