[RFC 2/3] ksmbd: add splice-based read payload helper
From: wang zhaolong
Date: Mon Jul 13 2026 - 03:15:00 EST
From: Wang Zhaolong <wangzhaolong@xxxxxxxxx>
Add ksmbd_vfs_read_payload() to collect page-cache backed splice buffers
into a bio_vec payload. The actor takes a reference to every page so the
payload remains valid after the internal splice pipe is drained.
Preserve the buffered READ path's directory, zero-length, access and
byte-range lock semantics. Streams, non-regular files, O_DIRECT, DAX and
files without splice_read support return -EOPNOTSUPP with the offset
unchanged, allowing the caller to retry through kernel_read(). A short read
before EOF is handled the same way to preserve the existing READ data path.
Signed-off-by: Wang Zhaolong <wangzhaolong@xxxxxxxxx>
---
fs/smb/server/vfs.c | 195 ++++++++++++++++++++++++++++++++++++++------
fs/smb/server/vfs.h | 4 +
2 files changed, 176 insertions(+), 23 deletions(-)
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index d0a0ad15d803..3a3f1b8e2d33 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -17,10 +17,13 @@
#include <linux/dcache.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched/xacct.h>
#include <linux/crc32c.h>
+#include <linux/net.h>
+#include <linux/overflow.h>
+#include <linux/pipe_fs_i.h>
#include <linux/splice.h>
#include <linux/fileattr.h>
#include "glob.h"
#include "oplock.h"
@@ -326,10 +329,33 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end,
out:
spin_unlock(&ctx->flc_lock);
return error;
}
+static int ksmbd_vfs_check_read_access(struct ksmbd_work *work,
+ struct ksmbd_file *fp)
+{
+ if (work->conn->connection_type &&
+ !(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) {
+ pr_err("no right to read(%pD)\n", fp->filp);
+ return -EACCES;
+ }
+ return 0;
+}
+
+static int ksmbd_vfs_check_read_range(struct ksmbd_work *work,
+ struct ksmbd_file *fp, loff_t pos,
+ size_t count)
+{
+ if (!work->tcon->posix_extensions &&
+ check_lock_range(fp->filp, pos, pos + count - 1, READ)) {
+ pr_err("unable to read due to lock\n");
+ return -EAGAIN;
+ }
+ return 0;
+}
+
/**
* ksmbd_vfs_read() - vfs helper for smb file read
* @work: smb work
* @fp: ksmbd file pointer
* @count: read byte count
@@ -340,47 +366,170 @@ static int check_lock_range(struct file *filp, loff_t start, loff_t end,
*/
int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
loff_t *pos, char *rbuf)
{
struct file *filp = fp->filp;
- ssize_t nbytes = 0;
- struct inode *inode = file_inode(filp);
+ ssize_t nbytes;
+ int ret;
- if (S_ISDIR(inode->i_mode))
+ if (S_ISDIR(file_inode(filp)->i_mode))
return -EISDIR;
-
if (unlikely(count == 0))
return 0;
-
- if (work->conn->connection_type) {
- if (!(fp->daccess & (FILE_READ_DATA_LE | FILE_EXECUTE_LE))) {
- pr_err("no right to read(%pD)\n", fp->filp);
- return -EACCES;
- }
- }
-
+ ret = ksmbd_vfs_check_read_access(work, fp);
+ if (ret)
+ return ret;
if (ksmbd_stream_fd(fp))
return ksmbd_vfs_stream_read(fp, rbuf, pos, count);
-
- if (!work->tcon->posix_extensions) {
- int ret;
-
- ret = check_lock_range(filp, *pos, *pos + count - 1, READ);
- if (ret) {
- pr_err("unable to read due to lock\n");
- return -EAGAIN;
- }
- }
+ ret = ksmbd_vfs_check_read_range(work, fp, *pos, count);
+ if (ret)
+ return ret;
nbytes = kernel_read(filp, rbuf, count, pos);
if (nbytes < 0) {
pr_err("smb read failed, err = %zd\n", nbytes);
return nbytes;
}
+ filp->f_pos = *pos;
+ ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes);
+ return nbytes;
+}
+
+static int ksmbd_read_payload_reserve(struct ksmbd_read_payload *payload,
+ unsigned int nr_bvecs)
+{
+ struct bio_vec *bvec;
+ unsigned int needed, nr_alloc;
+ if (check_add_overflow(payload->nr_bvecs, nr_bvecs, &needed))
+ return -EOVERFLOW;
+ if (payload->nr_alloc >= needed)
+ return 0;
+ nr_alloc = payload->nr_alloc ?: 16;
+ while (nr_alloc < needed) {
+ if (nr_alloc > UINT_MAX / 2) {
+ nr_alloc = needed;
+ break;
+ }
+ nr_alloc *= 2;
+ }
+ bvec = kvrealloc(payload->bvec,
+ array_size(nr_alloc, sizeof(*payload->bvec)),
+ KSMBD_DEFAULT_GFP | __GFP_ZERO);
+ if (!bvec)
+ return -ENOMEM;
+ payload->bvec = bvec;
+ payload->nr_alloc = nr_alloc;
+ return 0;
+}
+
+static int ksmbd_read_payload_actor(struct pipe_inode_info *pipe,
+ struct pipe_buffer *buf,
+ struct splice_desc *sd)
+{
+ struct ksmbd_read_payload *payload = sd->u.data;
+ unsigned int offset = buf->offset, len = sd->len;
+ size_t payload_len;
+ unsigned int nr_bvecs;
+ int ret;
+
+ nr_bvecs = DIV_ROUND_UP(offset_in_page(offset) + len, PAGE_SIZE);
+ ret = ksmbd_read_payload_reserve(payload, nr_bvecs);
+ if (ret)
+ return ret;
+ if (check_add_overflow(payload->len, (size_t)len, &payload_len))
+ return -EOVERFLOW;
+ while (len) {
+ struct page *page = buf->page + offset / PAGE_SIZE;
+ unsigned int page_offset = offset_in_page(offset);
+ unsigned int bytes = min_t(unsigned int, len,
+ PAGE_SIZE - page_offset);
+
+ if (!sendpage_ok(page))
+ return -EOPNOTSUPP;
+ get_page(page);
+ bvec_set_page(&payload->bvec[payload->nr_bvecs++], page,
+ bytes, page_offset);
+ offset += bytes;
+ len -= bytes;
+ }
+ payload->len = payload_len;
+ return sd->len;
+}
+
+static int ksmbd_direct_splice_actor(struct pipe_inode_info *pipe,
+ struct splice_desc *sd)
+{
+ return __splice_from_pipe(pipe, sd, ksmbd_read_payload_actor);
+}
+
+/**
+ * ksmbd_vfs_read_payload() - collect file pages for an SMB READ response
+ * @work: smb work
+ * @fp: ksmbd file pointer
+ * @count: read byte count
+ * @pos: file position, advanced only on success
+ * @payload: payload returned on success, otherwise set to NULL
+ *
+ * Return: number of bytes read, or -EOPNOTSUPP with @pos unchanged when the
+ * caller may safely retry through ksmbd_vfs_read().
+ */
+int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp,
+ size_t count, loff_t *pos,
+ struct ksmbd_read_payload **payload)
+{
+ struct file *filp = fp->filp;
+ struct ksmbd_read_payload *read_payload;
+ loff_t start = *pos;
+ struct splice_desc sd;
+ ssize_t nbytes;
+ int ret;
+
+ *payload = NULL;
+ if (S_ISDIR(file_inode(filp)->i_mode))
+ return -EISDIR;
+ if (unlikely(count == 0))
+ return 0;
+ ret = ksmbd_vfs_check_read_access(work, fp);
+ if (ret)
+ return ret;
+ if (ksmbd_stream_fd(fp) || !S_ISREG(file_inode(filp)->i_mode) ||
+ (filp->f_flags & O_DIRECT) || IS_DAX(file_inode(filp)) ||
+ !(filp->f_mode & FMODE_LSEEK) || !filp->f_op->splice_read)
+ return -EOPNOTSUPP;
+ ret = ksmbd_vfs_check_read_range(work, fp, start, count);
+ if (ret)
+ return ret;
+
+ read_payload = kzalloc_obj(struct ksmbd_read_payload,
+ KSMBD_DEFAULT_GFP);
+ if (!read_payload)
+ return -ENOMEM;
+ nbytes = rw_verify_area(READ, filp, pos, count);
+ if (nbytes)
+ goto out_free;
+ sd = (struct splice_desc) {
+ .total_len = count,
+ .pos = start,
+ .u.data = read_payload,
+ };
+ nbytes = splice_direct_to_actor(filp, &sd, ksmbd_direct_splice_actor);
+ if (nbytes <= 0)
+ goto out_free;
+ if (nbytes < count && start + nbytes < i_size_read(file_inode(filp))) {
+ nbytes = -EOPNOTSUPP;
+ goto out_free;
+ }
+ *pos = start + nbytes;
filp->f_pos = *pos;
- ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, (s64)nbytes);
+ ksmbd_counter_add(KSMBD_COUNTER_READ_BYTES, nbytes);
+ *payload = read_payload;
+ return nbytes;
+
+out_free:
+ *pos = start;
+ ksmbd_read_payload_release(read_payload);
return nbytes;
}
static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos,
size_t count)
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 7b3d2f4fd985..5afee75c4a3b 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -34,10 +34,11 @@ enum {
#define CREATE_OPTION_SPECIAL 0x20000000
struct ksmbd_work;
struct ksmbd_file;
struct ksmbd_conn;
+struct ksmbd_read_payload;
struct ksmbd_dir_info {
const char *name;
char *wptr;
char *rptr;
@@ -77,10 +78,13 @@ void ksmbd_vfs_query_maximal_access(struct mnt_idmap *idmap,
struct dentry *dentry, __le32 *daccess);
int ksmbd_vfs_create(struct ksmbd_work *work, const char *name, umode_t mode);
int ksmbd_vfs_mkdir(struct ksmbd_work *work, const char *name, umode_t mode);
int ksmbd_vfs_read(struct ksmbd_work *work, struct ksmbd_file *fp, size_t count,
loff_t *pos, char *rbuf);
+int ksmbd_vfs_read_payload(struct ksmbd_work *work, struct ksmbd_file *fp,
+ size_t count, loff_t *pos,
+ struct ksmbd_read_payload **payload);
int ksmbd_vfs_write(struct ksmbd_work *work, struct ksmbd_file *fp,
char *buf, size_t count, loff_t *pos, bool sync,
ssize_t *written);
int ksmbd_vfs_fsync(struct ksmbd_work *work, u64 fid, u64 p_id);
int ksmbd_vfs_remove_file(struct ksmbd_work *work, const struct path *path);
--
2.47.3