SUNRPC: Introduce xdr_stream_move_subsegment()

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>
This commit is contained in:
Anna Schumaker 2022-07-21 14:21:31 -04:00 committed by Trond Myklebust
parent 33ce83ef0b
commit 4f5f3b6028
2 changed files with 61 additions and 0 deletions

View 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.

View 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