SUNRPC: Introduce xdr_stream_move_subsegment()
authorAnna Schumaker <Anna.Schumaker@Netapp.com>
Thu, 21 Jul 2022 18:21:31 +0000 (14:21 -0400)
committerTrond Myklebust <trond.myklebust@hammerspace.com>
Sat, 23 Jul 2022 19:38:29 +0000 (15:38 -0400)
I do this by creating an xdr subsegment for the range we will be
operating over. This lets me shift data to the correct place without
potentially overwriting anything already there.

Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>
include/linux/sunrpc/xdr.h
net/sunrpc/xdr.c

index 5860f32e3958037d5604a2feab8619bd159c7ca7..7dcc6c31fe2997d606c15983daf036ebb084909b 100644 (file)
@@ -262,6 +262,8 @@ extern unsigned int xdr_align_data(struct xdr_stream *, unsigned int offset, uns
 extern unsigned int xdr_expand_hole(struct xdr_stream *, unsigned int offset, unsigned int length);
 extern bool xdr_stream_subsegment(struct xdr_stream *xdr, struct xdr_buf *subbuf,
                                  unsigned int len);
+extern unsigned int xdr_stream_move_subsegment(struct xdr_stream *xdr, unsigned int offset,
+                                              unsigned int target, unsigned int length);
 
 /**
  * xdr_set_scratch_buffer - Attach a scratch buffer for decoding data.
index 5d2b3e6979fb9c4b759a7e5a650298d68c6ef3a1..8ba11a75429737efe41f1cef1fdbc52c3ff70ccb 100644 (file)
@@ -775,6 +775,34 @@ static void xdr_buf_pages_shift_left(const struct xdr_buf *buf,
        xdr_buf_tail_copy_left(buf, 0, len - buf->page_len, shift);
 }
 
+static void xdr_buf_head_shift_left(const struct xdr_buf *buf,
+                                   unsigned int base, unsigned int len,
+                                   unsigned int shift)
+{
+       const struct kvec *head = buf->head;
+       unsigned int bytes;
+
+       if (!shift || !len)
+               return;
+
+       if (shift > base) {
+               bytes = (shift - base);
+               if (bytes >= len)
+                       return;
+               base += bytes;
+               len -= bytes;
+       }
+
+       if (base < head->iov_len) {
+               bytes = min_t(unsigned int, len, head->iov_len - base);
+               memmove(head->iov_base + (base - shift),
+                       head->iov_base + base, bytes);
+               base += bytes;
+               len -= bytes;
+       }
+       xdr_buf_pages_shift_left(buf, base - head->iov_len, len, shift);
+}
+
 /**
  * xdr_shrink_bufhead
  * @buf: xdr_buf
@@ -1680,6 +1708,37 @@ bool xdr_stream_subsegment(struct xdr_stream *xdr, struct xdr_buf *subbuf,
 }
 EXPORT_SYMBOL_GPL(xdr_stream_subsegment);
 
+/**
+ * xdr_stream_move_subsegment - Move part of a stream to another position
+ * @xdr: the source xdr_stream
+ * @offset: the source offset of the segment
+ * @target: the target offset of the segment
+ * @length: the number of bytes to move
+ *
+ * Moves @length bytes from @offset to @target in the xdr_stream, overwriting
+ * anything in its space. Returns the number of bytes in the segment.
+ */
+unsigned int xdr_stream_move_subsegment(struct xdr_stream *xdr, unsigned int offset,
+                                       unsigned int target, unsigned int length)
+{
+       struct xdr_buf buf;
+       unsigned int shift;
+
+       if (offset < target) {
+               shift = target - offset;
+               if (xdr_buf_subsegment(xdr->buf, &buf, offset, shift + length) < 0)
+                       return 0;
+               xdr_buf_head_shift_right(&buf, 0, length, shift);
+       } else if (offset > target) {
+               shift = offset - target;
+               if (xdr_buf_subsegment(xdr->buf, &buf, target, shift + length) < 0)
+                       return 0;
+               xdr_buf_head_shift_left(&buf, shift, length, shift);
+       }
+       return length;
+}
+EXPORT_SYMBOL_GPL(xdr_stream_move_subsegment);
+
 /**
  * xdr_buf_trim - lop at most "len" bytes off the end of "buf"
  * @buf: buf to be trimmed