SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists

The crypto/krb5 library accepts data in scatterlist form, but
the GSS-API layer presents RPC payloads as struct xdr_buf.
Bridge that gap with a pair of helper functions:

  xdr_buf_to_sg()        - populate a caller-supplied scatterlist
                           array from a byte range
  xdr_buf_to_sg_alloc()  - populate a caller-supplied inline
                           scatterlist, chaining to a heap-
                           allocated overflow for large payloads

The inline array (typically stack-allocated at eight entries)
covers the common case of small RPCs with no heap allocation
on the encrypt/decrypt path. Only buffers spanning many pages
incur a kmalloc for the chained extension.

The segment-walking logic follows the same head, page array,
tail traversal as xdr_process_buf(), but populates a
scatterlist directly rather than invoking a per-segment
callback. sg_next() traversal makes the walker safe for
chained scatterlists. Once subsequent patches reroute all
per-message crypto operations through crypto/krb5,
xdr_process_buf() loses its last callers and is removed.

Assisted-by: Claude:claude-opus-4-6
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Chuck Lever 2026-04-27 09:50:47 -04:00 committed by Chuck Lever
parent f8b942fc38
commit e9be933959
2 changed files with 214 additions and 0 deletions

View File

@ -140,6 +140,21 @@ int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp);
void xdr_free_bvec(struct xdr_buf *buf);
unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
const struct xdr_buf *xdr);
int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
unsigned int len, struct scatterlist *sg, unsigned int nsg);
int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
unsigned int len, struct scatterlist *sg_head,
unsigned int sg_head_nents,
struct scatterlist **sg_overflow, gfp_t gfp);
/*
* Inline scatterlist entries for xdr_buf_to_sg_alloc(). Sized to cover the
* head kvec, tail kvec, and a few page fragments without any heap allocation.
*/
enum {
XDR_BUF_TO_SG_NENTS = 8,
};
static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len)
{

View File

@ -187,6 +187,205 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
}
EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
/**
* xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range
* @buf: xdr_buf to map
* @offset: starting byte offset within @buf
* @len: number of bytes to cover
* @sg: scatterlist array initialized with sg_init_table()
* @nsg: number of entries available in @sg
*
* @sg is traversed with sg_next(), so callers may pass a list
* assembled with sg_chain().
*
* Return: on success, the number of scatterlist entries used; the
* last used entry is marked with sg_mark_end(). On failure, a
* negative errno.
*/
int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
unsigned int len, struct scatterlist *sg, unsigned int nsg)
{
unsigned int page_len, thislen, page_offset;
struct scatterlist *cur = sg, *prev = NULL;
int nents = 0;
int i;
if (len == 0)
return 0;
if (offset >= buf->head[0].iov_len) {
offset -= buf->head[0].iov_len;
} else {
thislen = min_t(unsigned int,
buf->head[0].iov_len - offset, len);
if (nents >= nsg)
return -ENOSPC;
sg_set_buf(cur, buf->head[0].iov_base + offset,
thislen);
prev = cur;
cur = sg_next(cur);
nents++;
len -= thislen;
offset = 0;
}
if (len == 0)
goto done;
if (offset >= buf->page_len) {
offset -= buf->page_len;
} else {
page_len = min(buf->page_len - offset, len);
len -= page_len;
page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
i = (offset + buf->page_base) >> PAGE_SHIFT;
thislen = PAGE_SIZE - page_offset;
do {
if (thislen > page_len)
thislen = page_len;
if (nents >= nsg)
return -ENOSPC;
sg_set_page(cur, buf->pages[i],
thislen, page_offset);
prev = cur;
cur = sg_next(cur);
nents++;
page_len -= thislen;
i++;
page_offset = 0;
thislen = PAGE_SIZE;
} while (page_len != 0);
offset = 0;
}
if (len == 0)
goto done;
if (offset < buf->tail[0].iov_len) {
thislen = min_t(unsigned int,
buf->tail[0].iov_len - offset, len);
if (nents >= nsg)
return -ENOSPC;
sg_set_buf(cur, buf->tail[0].iov_base + offset,
thislen);
prev = cur;
nents++;
len -= thislen;
}
if (len != 0)
return -EINVAL;
done:
if (prev)
sg_mark_end(prev);
return nents;
}
EXPORT_SYMBOL_GPL(xdr_buf_to_sg);
/*
* Count the scatterlist entries needed to cover [offset, offset + len)
* within @buf. Mirrors the walk in xdr_buf_to_sg() so the caller can
* size an allocation that matches the requested sub-range rather than
* the full xdr_buf.
*/
static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf,
unsigned int offset, unsigned int len)
{
unsigned int nsg = 0, thislen, page_offset;
if (len == 0)
return 0;
if (offset < buf->head[0].iov_len) {
thislen = min_t(unsigned int,
buf->head[0].iov_len - offset, len);
nsg++;
len -= thislen;
offset = 0;
} else {
offset -= buf->head[0].iov_len;
}
if (len == 0)
return nsg;
if (offset < buf->page_len) {
thislen = min(buf->page_len - offset, len);
page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE);
len -= thislen;
offset = 0;
} else {
offset -= buf->page_len;
}
if (len == 0)
return nsg;
if (offset < buf->tail[0].iov_len)
nsg++;
return nsg;
}
/**
* xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range
* @buf: xdr_buf to map
* @offset: starting byte offset within @buf
* @len: number of bytes to cover
* @sg_head: caller-provided scatterlist array (typically stack-allocated)
* @sg_head_nents: number of entries in @sg_head
* @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed
* @gfp: memory allocation flags for overflow
*
* Populates @sg_head directly when the xdr_buf fits. When more
* entries are needed, an overflow scatterlist is allocated and
* chained from @sg_head so that the result is traversable with
* sg_next().
*
* Return: on success, the number of populated scatterlist entries
* (counting only data entries, not chain entries). @sg_head is
* the head of the resulting list. Caller must kfree @sg_overflow
* when done. On failure, a negative errno.
*/
int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
unsigned int len, struct scatterlist *sg_head,
unsigned int sg_head_nents,
struct scatterlist **sg_overflow, gfp_t gfp)
{
unsigned int nsg;
int ret;
*sg_overflow = NULL;
if (len == 0)
return 0;
nsg = xdr_buf_sg_nents(buf, offset, len);
if (nsg == 0)
return -EINVAL;
if (nsg <= sg_head_nents) {
sg_init_table(sg_head, nsg);
} else {
/* +1 replaces the slot sg_chain() consumes as the link. */
unsigned int overflow_nents = nsg - sg_head_nents + 1;
struct scatterlist *overflow;
overflow = kmalloc_array(overflow_nents, sizeof(*overflow),
gfp);
if (!overflow)
return -ENOMEM;
sg_init_table(sg_head, sg_head_nents);
sg_init_table(overflow, overflow_nents);
sg_chain(sg_head, sg_head_nents, overflow);
*sg_overflow = overflow;
}
ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg);
if (ret < 0) {
kfree(*sg_overflow);
*sg_overflow = NULL;
}
return ret;
}
EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc);
/**
* xdr_inline_pages - Prepare receive buffer for a large reply
* @xdr: xdr_buf into which reply will be placed