]> git.ipfire.org Git - people/ms/linux.git/blobdiff - fs/nfs/nfs42xdr.c
Merge branch 'for-6.0/dax' into libnvdimm-fixes
[people/ms/linux.git] / fs / nfs / nfs42xdr.c
index 271e5f92ed0195982d50f22952519a19e274b1da..b56f05113d367d6ecfb2f4827e0bac892f8a2193 100644 (file)
@@ -1025,82 +1025,95 @@ 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;
-       __be32 *p;
-
-       p = xdr_inline_decode(xdr, 8 + 4);
-       if (!p)
-               return 1;
+       union {
+               struct {
+                       uint64_t length;
+               } hole;
+
+               struct {
+                       uint32_t length;
+                       unsigned int from;
+               } data;
+       };
+};
 
-       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;
-       return 0;
+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_hole(struct xdr_stream *xdr,
-                                struct nfs_pgio_args *args,
-                                struct nfs_pgio_res *res, uint32_t *eof)
+static int decode_read_plus_segment(struct xdr_stream *xdr,
+                                   struct read_plus_segment *seg)
 {
-       uint64_t offset, length, recvd;
        __be32 *p;
 
-       p = xdr_inline_decode(xdr, 8 + 8);
+       p = xdr_inline_decode(xdr, 4);
        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;
+               return -EIO;
+       seg->type = be32_to_cpup(p++);
+
+       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 (recvd < length)
-               return 1;
+               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 process_read_plus_segment(struct xdr_stream *xdr,
+                                    struct nfs_pgio_args *args,
+                                    struct nfs_pgio_res *res,
+                                    struct read_plus_segment *seg)
+{
+       unsigned long offset = seg->offset;
+       unsigned long length = read_plus_segment_length(seg);
+       unsigned int bufpos;
+
+       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)
 {
        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)