Skip to content

Commit

Permalink
NFSv4.2: add client side XDR handling for extended attributes
Browse files Browse the repository at this point in the history
Define the argument and response structures that will be used for
RFC 8276 extended attribute RPC calls, and implement the necessary
functions to encode/decode the extended attribute operations.

Signed-off-by: Frank van der Linden <fllinden@amazon.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
  • Loading branch information
Frank van der Linden authored and Trond Myklebust committed Jul 13, 2020
1 parent b78ef84 commit 3e1f021
Show file tree
Hide file tree
Showing 3 changed files with 430 additions and 3 deletions.
368 changes: 366 additions & 2 deletions fs/nfs/nfs42xdr.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@
decode_clone_maxsz + \
decode_getattr_maxsz)

#ifdef CONFIG_NFS_V4_2
/* Not limited by NFS itself, limited by the generic xattr code */
#define nfs4_xattr_name_maxsz XDR_QUADLEN(XATTR_NAME_MAX)

Expand Down Expand Up @@ -241,7 +240,6 @@ const u32 nfs42_maxlistxattrs_overhead = ((RPC_MAX_HEADER_WITH_AUTH +
compound_decode_hdr_maxsz +
decode_sequence_maxsz +
decode_putfh_maxsz + 3) * XDR_UNIT);
#endif

static void encode_fallocate(struct xdr_stream *xdr,
const struct nfs42_falloc_args *args)
Expand Down Expand Up @@ -407,6 +405,210 @@ static void encode_layouterror(struct xdr_stream *xdr,
encode_device_error(xdr, &args->errors[0]);
}

static void encode_setxattr(struct xdr_stream *xdr,
const struct nfs42_setxattrargs *arg,
struct compound_hdr *hdr)
{
__be32 *p;

BUILD_BUG_ON(XATTR_CREATE != SETXATTR4_CREATE);
BUILD_BUG_ON(XATTR_REPLACE != SETXATTR4_REPLACE);

encode_op_hdr(xdr, OP_SETXATTR, decode_setxattr_maxsz, hdr);
p = reserve_space(xdr, 4);
*p = cpu_to_be32(arg->xattr_flags);
encode_string(xdr, strlen(arg->xattr_name), arg->xattr_name);
p = reserve_space(xdr, 4);
*p = cpu_to_be32(arg->xattr_len);
if (arg->xattr_len)
xdr_write_pages(xdr, arg->xattr_pages, 0, arg->xattr_len);
}

static int decode_setxattr(struct xdr_stream *xdr,
struct nfs4_change_info *cinfo)
{
int status;

status = decode_op_hdr(xdr, OP_SETXATTR);
if (status)
goto out;
status = decode_change_info(xdr, cinfo);
out:
return status;
}


static void encode_getxattr(struct xdr_stream *xdr, const char *name,
struct compound_hdr *hdr)
{
encode_op_hdr(xdr, OP_GETXATTR, decode_getxattr_maxsz, hdr);
encode_string(xdr, strlen(name), name);
}

static int decode_getxattr(struct xdr_stream *xdr,
struct nfs42_getxattrres *res,
struct rpc_rqst *req)
{
int status;
__be32 *p;
u32 len, rdlen;

status = decode_op_hdr(xdr, OP_GETXATTR);
if (status)
return status;

p = xdr_inline_decode(xdr, 4);
if (unlikely(!p))
return -EIO;

len = be32_to_cpup(p);
if (len > req->rq_rcv_buf.page_len)
return -ERANGE;

res->xattr_len = len;

if (len > 0) {
rdlen = xdr_read_pages(xdr, len);
if (rdlen < len)
return -EIO;
}

return 0;
}

static void encode_removexattr(struct xdr_stream *xdr, const char *name,
struct compound_hdr *hdr)
{
encode_op_hdr(xdr, OP_REMOVEXATTR, decode_removexattr_maxsz, hdr);
encode_string(xdr, strlen(name), name);
}


static int decode_removexattr(struct xdr_stream *xdr,
struct nfs4_change_info *cinfo)
{
int status;

status = decode_op_hdr(xdr, OP_REMOVEXATTR);
if (status)
goto out;

status = decode_change_info(xdr, cinfo);
out:
return status;
}

static void encode_listxattrs(struct xdr_stream *xdr,
const struct nfs42_listxattrsargs *arg,
struct compound_hdr *hdr)
{
__be32 *p;

encode_op_hdr(xdr, OP_LISTXATTRS, decode_listxattrs_maxsz + 1, hdr);

p = reserve_space(xdr, 12);
if (unlikely(!p))
return;

p = xdr_encode_hyper(p, arg->cookie);
/*
* RFC 8276 says to specify the full max length of the LISTXATTRS
* XDR reply. Count is set to the XDR length of the names array
* plus the EOF marker. So, add the cookie and the names count.
*/
*p = cpu_to_be32(arg->count + 8 + 4);
}

static int decode_listxattrs(struct xdr_stream *xdr,
struct nfs42_listxattrsres *res)
{
int status;
__be32 *p;
u32 count, len, ulen;
size_t left, copied;
char *buf;

status = decode_op_hdr(xdr, OP_LISTXATTRS);
if (status) {
/*
* Special case: for LISTXATTRS, NFS4ERR_TOOSMALL
* should be translated to ERANGE.
*/
if (status == -ETOOSMALL)
status = -ERANGE;
goto out;
}

p = xdr_inline_decode(xdr, 8);
if (unlikely(!p))
return -EIO;

xdr_decode_hyper(p, &res->cookie);

p = xdr_inline_decode(xdr, 4);
if (unlikely(!p))
return -EIO;

left = res->xattr_len;
buf = res->xattr_buf;

count = be32_to_cpup(p);
copied = 0;

/*
* We have asked for enough room to encode the maximum number
* of possible attribute names, so everything should fit.
*
* But, don't rely on that assumption. Just decode entries
* until they don't fit anymore, just in case the server did
* something odd.
*/
while (count--) {
p = xdr_inline_decode(xdr, 4);
if (unlikely(!p))
return -EIO;

len = be32_to_cpup(p);
if (len > (XATTR_NAME_MAX - XATTR_USER_PREFIX_LEN)) {
status = -ERANGE;
goto out;
}

p = xdr_inline_decode(xdr, len);
if (unlikely(!p))
return -EIO;

ulen = len + XATTR_USER_PREFIX_LEN + 1;
if (buf) {
if (ulen > left) {
status = -ERANGE;
goto out;
}

memcpy(buf, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN);
memcpy(buf + XATTR_USER_PREFIX_LEN, p, len);

buf[ulen - 1] = 0;
buf += ulen;
left -= ulen;
}
copied += ulen;
}

p = xdr_inline_decode(xdr, 4);
if (unlikely(!p))
return -EIO;

res->eof = be32_to_cpup(p);
res->copied = copied;

out:
if (status == -ERANGE && res->xattr_len == XATTR_LIST_MAX)
status = -E2BIG;

return status;
}

/*
* Encode ALLOCATE request
*/
Expand Down Expand Up @@ -1062,4 +1264,166 @@ static int nfs4_xdr_dec_layouterror(struct rpc_rqst *rqstp,
return status;
}

#ifdef CONFIG_NFS_V4_2
static void nfs4_xdr_enc_setxattr(struct rpc_rqst *req, struct xdr_stream *xdr,
const void *data)
{
const struct nfs42_setxattrargs *args = data;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};

encode_compound_hdr(xdr, req, &hdr);
encode_sequence(xdr, &args->seq_args, &hdr);
encode_putfh(xdr, args->fh, &hdr);
encode_setxattr(xdr, args, &hdr);
encode_nops(&hdr);
}

static int nfs4_xdr_dec_setxattr(struct rpc_rqst *req, struct xdr_stream *xdr,
void *data)
{
struct nfs42_setxattrres *res = data;
struct compound_hdr hdr;
int status;

status = decode_compound_hdr(xdr, &hdr);
if (status)
goto out;
status = decode_sequence(xdr, &res->seq_res, req);
if (status)
goto out;
status = decode_putfh(xdr);
if (status)
goto out;

status = decode_setxattr(xdr, &res->cinfo);
out:
return status;
}

static void nfs4_xdr_enc_getxattr(struct rpc_rqst *req, struct xdr_stream *xdr,
const void *data)
{
const struct nfs42_getxattrargs *args = data;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};
size_t plen;

encode_compound_hdr(xdr, req, &hdr);
encode_sequence(xdr, &args->seq_args, &hdr);
encode_putfh(xdr, args->fh, &hdr);
encode_getxattr(xdr, args->xattr_name, &hdr);

plen = args->xattr_len ? args->xattr_len : XATTR_SIZE_MAX;

rpc_prepare_reply_pages(req, args->xattr_pages, 0, plen,
hdr.replen);
req->rq_rcv_buf.flags |= XDRBUF_SPARSE_PAGES;

encode_nops(&hdr);
}

static int nfs4_xdr_dec_getxattr(struct rpc_rqst *rqstp,
struct xdr_stream *xdr, void *data)
{
struct nfs42_getxattrres *res = data;
struct compound_hdr hdr;
int status;

status = decode_compound_hdr(xdr, &hdr);
if (status)
goto out;
status = decode_sequence(xdr, &res->seq_res, rqstp);
if (status)
goto out;
status = decode_putfh(xdr);
if (status)
goto out;
status = decode_getxattr(xdr, res, rqstp);
out:
return status;
}

static void nfs4_xdr_enc_listxattrs(struct rpc_rqst *req,
struct xdr_stream *xdr, const void *data)
{
const struct nfs42_listxattrsargs *args = data;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};

encode_compound_hdr(xdr, req, &hdr);
encode_sequence(xdr, &args->seq_args, &hdr);
encode_putfh(xdr, args->fh, &hdr);
encode_listxattrs(xdr, args, &hdr);

rpc_prepare_reply_pages(req, args->xattr_pages, 0, args->count,
hdr.replen);
req->rq_rcv_buf.flags |= XDRBUF_SPARSE_PAGES;

encode_nops(&hdr);
}

static int nfs4_xdr_dec_listxattrs(struct rpc_rqst *rqstp,
struct xdr_stream *xdr, void *data)
{
struct nfs42_listxattrsres *res = data;
struct compound_hdr hdr;
int status;

xdr_set_scratch_buffer(xdr, page_address(res->scratch), PAGE_SIZE);

status = decode_compound_hdr(xdr, &hdr);
if (status)
goto out;
status = decode_sequence(xdr, &res->seq_res, rqstp);
if (status)
goto out;
status = decode_putfh(xdr);
if (status)
goto out;
status = decode_listxattrs(xdr, res);
out:
return status;
}

static void nfs4_xdr_enc_removexattr(struct rpc_rqst *req,
struct xdr_stream *xdr, const void *data)
{
const struct nfs42_removexattrargs *args = data;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};

encode_compound_hdr(xdr, req, &hdr);
encode_sequence(xdr, &args->seq_args, &hdr);
encode_putfh(xdr, args->fh, &hdr);
encode_removexattr(xdr, args->xattr_name, &hdr);
encode_nops(&hdr);
}

static int nfs4_xdr_dec_removexattr(struct rpc_rqst *req,
struct xdr_stream *xdr, void *data)
{
struct nfs42_removexattrres *res = data;
struct compound_hdr hdr;
int status;

status = decode_compound_hdr(xdr, &hdr);
if (status)
goto out;
status = decode_sequence(xdr, &res->seq_res, req);
if (status)
goto out;
status = decode_putfh(xdr);
if (status)
goto out;

status = decode_removexattr(xdr, &res->cinfo);
out:
return status;
}
#endif
#endif /* __LINUX_FS_NFS_NFS4_2XDR_H */
Loading

0 comments on commit 3e1f021

Please sign in to comment.