]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists
authorChuck Lever <chuck.lever@oracle.com>
Mon, 27 Apr 2026 13:50:47 +0000 (09:50 -0400)
committerChuck Lever <cel@kernel.org>
Tue, 9 Jun 2026 20:32:59 +0000 (16:32 -0400)
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>
include/linux/sunrpc/xdr.h
net/sunrpc/xdr.c

index b639a6fafcbc6f5714c53fbff07118ecd65d5833..f82446993fde6b6fce910c4423d6a10ff01f6bca 100644 (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)
 {
index e83d5d0be78b79cbf53e63a5fb6882bc674c66e1..516833b4c1144415fb3dffbebd4ceaeb22a7470c 100644 (file)
@@ -187,6 +187,205 @@ bvec_overflow:
 }
 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