Commit d3b00a80 authored by Anna Schumaker's avatar Anna Schumaker Committed by Trond Myklebust
Browse files

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: default avatarAnna Schumaker <Anna.Schumaker@Netapp.com>
Signed-off-by: default avatarTrond Myklebust <trond.myklebust@hammerspace.com>
parent e1bd8760
Loading
Loading
Loading
Loading
+88 −82
Original line number Diff line number Diff line
@@ -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,
static int process_read_plus_segment(struct xdr_stream *xdr,
				     struct nfs_pgio_args *args,
				 struct nfs_pgio_res *res, uint32_t *eof)
				     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 (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;
	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;
	}

	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;

	for (i = 0; i < segments; i++) {
		p = xdr_inline_decode(xdr, 4);
		if (!p)
			goto early_out;
		return status;

		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;
	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++) {
		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)