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: Frank van der Linden <fllinden@amazon.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Frank van der Linden 2020-06-23 22:39:26 +00:00 committed by Chuck Lever
parent 6178713bd4
commit 23e50fe3a5
3 changed files with 571 additions and 1 deletions

View File

@ -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,
},
};
/**

View File

@ -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,
};
/*

View File

@ -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 {