NFS: Replace the READ_PLUS decoding code

We now take a 2-step process that allows us to place data and hole
segments directly at their final position in the xdr_stream without
needing to do a bunch of redundant copies to expand holes. Due to the
variable lengths of each segment, the xdr metadata might cross page
boundaries which I account for by setting a small scratch buffer so
xdr_inline_decode() won't fail.

Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
This commit is contained in:
Anna Schumaker 2022-07-21 14:21:34 -04:00 committed by Trond Myklebust
parent e1bd87608d
commit d3b00a802c

View File

@ -1025,73 +1025,84 @@ static int decode_deallocate(struct xdr_stream *xdr, struct nfs42_falloc_res *re
return decode_op_hdr(xdr, OP_DEALLOCATE);
}
static int decode_read_plus_data(struct xdr_stream *xdr,
struct nfs_pgio_args *args,
struct nfs_pgio_res *res)
{
uint32_t count, recvd;
struct read_plus_segment {
enum data_content4 type;
uint64_t offset;
union {
struct {
uint64_t length;
} hole;
struct {
uint32_t length;
unsigned int from;
} data;
};
};
static inline uint64_t read_plus_segment_length(struct read_plus_segment *seg)
{
return seg->type == NFS4_CONTENT_DATA ? seg->data.length : seg->hole.length;
}
static int decode_read_plus_segment(struct xdr_stream *xdr,
struct read_plus_segment *seg)
{
__be32 *p;
p = xdr_inline_decode(xdr, 8 + 4);
p = xdr_inline_decode(xdr, 4);
if (!p)
return 1;
return -EIO;
seg->type = be32_to_cpup(p++);
p = xdr_decode_hyper(p, &offset);
count = be32_to_cpup(p);
recvd = xdr_align_data(xdr, res->count, xdr_align_size(count));
if (recvd > count)
recvd = count;
if (res->count + recvd > args->count) {
if (args->count > res->count)
res->count += args->count - res->count;
return 1;
}
res->count += recvd;
if (count > recvd)
return 1;
p = xdr_inline_decode(xdr, seg->type == NFS4_CONTENT_DATA ? 12 : 16);
if (!p)
return -EIO;
p = xdr_decode_hyper(p, &seg->offset);
if (seg->type == NFS4_CONTENT_DATA) {
struct xdr_buf buf;
uint32_t len = be32_to_cpup(p);
seg->data.length = len;
seg->data.from = xdr_stream_pos(xdr);
if (!xdr_stream_subsegment(xdr, &buf, xdr_align_size(len)))
return -EIO;
} else if (seg->type == NFS4_CONTENT_HOLE) {
xdr_decode_hyper(p, &seg->hole.length);
} else
return -EINVAL;
return 0;
}
static int decode_read_plus_hole(struct xdr_stream *xdr,
struct nfs_pgio_args *args,
struct nfs_pgio_res *res, uint32_t *eof)
static int process_read_plus_segment(struct xdr_stream *xdr,
struct nfs_pgio_args *args,
struct nfs_pgio_res *res,
struct read_plus_segment *seg)
{
uint64_t offset, length, recvd;
__be32 *p;
unsigned long offset = seg->offset;
unsigned long length = read_plus_segment_length(seg);
unsigned int bufpos;
p = xdr_inline_decode(xdr, 8 + 8);
if (!p)
return 1;
p = xdr_decode_hyper(p, &offset);
p = xdr_decode_hyper(p, &length);
if (offset != args->offset + res->count) {
/* Server returned an out-of-sequence extent */
if (offset > args->offset + res->count ||
offset + length < args->offset + res->count) {
dprintk("NFS: server returned out of sequence extent: "
"offset/size = %llu/%llu != expected %llu\n",
(unsigned long long)offset,
(unsigned long long)length,
(unsigned long long)(args->offset +
res->count));
return 1;
}
length -= args->offset + res->count - offset;
if (offset + length < args->offset)
return 0;
else if (offset > args->offset + args->count) {
res->eof = 0;
return 0;
} else if (offset < args->offset) {
length -= (args->offset - offset);
offset = args->offset;
} else if (offset + length > args->offset + args->count) {
length = (args->offset + args->count) - offset;
res->eof = 0;
}
if (length + res->count > args->count) {
*eof = 0;
if (unlikely(res->count >= args->count))
return 1;
length = args->count - res->count;
}
recvd = xdr_expand_hole(xdr, res->count, length);
res->count += recvd;
if (recvd < length)
return 1;
return 0;
bufpos = xdr->buf->head[0].iov_len + (offset - args->offset);
if (seg->type == NFS4_CONTENT_HOLE)
return xdr_stream_zero(xdr, bufpos, length);
else
return xdr_stream_move_subsegment(xdr, seg->data.from, bufpos, length);
}
static int decode_read_plus(struct xdr_stream *xdr, struct nfs_pgio_res *res)
@ -1099,8 +1110,10 @@ static int decode_read_plus(struct xdr_stream *xdr, struct nfs_pgio_res *res)
struct nfs_pgio_header *hdr =
container_of(res, struct nfs_pgio_header, res);
struct nfs_pgio_args *args = &hdr->args;
uint32_t eof, segments, type;
uint32_t segments;
struct read_plus_segment *segs;
int status, i;
char scratch_buf[16];
__be32 *p;
status = decode_op_hdr(xdr, OP_READ_PLUS);
@ -1112,38 +1125,31 @@ static int decode_read_plus(struct xdr_stream *xdr, struct nfs_pgio_res *res)
return -EIO;
res->count = 0;
eof = be32_to_cpup(p++);
res->eof = be32_to_cpup(p++);
segments = be32_to_cpup(p++);
if (segments == 0)
goto out;
return status;
segs = kmalloc_array(segments, sizeof(*segs), GFP_KERNEL);
if (!segs)
return -ENOMEM;
xdr_set_scratch_buffer(xdr, &scratch_buf, 32);
status = -EIO;
for (i = 0; i < segments; i++) {
p = xdr_inline_decode(xdr, 4);
if (!p)
goto early_out;
type = be32_to_cpup(p++);
if (type == NFS4_CONTENT_DATA)
status = decode_read_plus_data(xdr, args, res);
else if (type == NFS4_CONTENT_HOLE)
status = decode_read_plus_hole(xdr, args, res, &eof);
else
return -EINVAL;
status = decode_read_plus_segment(xdr, &segs[i]);
if (status < 0)
return status;
if (status > 0)
goto early_out;
goto out;
}
xdr_set_pagelen(xdr, xdr_align_size(args->count));
for (i = segments; i > 0; i--)
res->count += process_read_plus_segment(xdr, args, res, &segs[i-1]);
status = 0;
out:
res->eof = eof;
return 0;
early_out:
if (unlikely(!i))
return -EIO;
res->eof = 0;
return 0;
kfree(segs);
return status;
}
static int decode_seek(struct xdr_stream *xdr, struct nfs42_seek_res *res)