[RFC 1/3] ksmbd: add read payload infrastructure

"wang zhaolong" <[email protected]>
Newsgroups org.kernel.vger.linux-cifs,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
From: Wang Zhaolong <[email protected]>

ksmbd currently copies SMB READ data into an allocated auxiliary buffer
before sending the response. A splice-based path needs to keep page-backed
file data separate from the SMB header while still accounting for it in the
RFC1002 length.

Add a work-owned READ payload made of bio_vecs and an ownership-transfer
helper for response pinning. Add a transport operation and a TCP
implementation that sends the header followed by the payload with
MSG_SPLICE_PAGES while the connection lock prevents interleaving.

Handle positive short sends and shut down the socket if a partially emitted
PDU fails. The existing READ path remains unchanged until the payload
collector is wired up.

Signed-off-by: Wang Zhaolong <[email protected]>
---
 fs/smb/server/connection.c    | 25 ++++++++++++---
 fs/smb/server/connection.h    |  4 +++
 fs/smb/server/ksmbd_work.c    | 45 +++++++++++++++++++++++++++
 fs/smb/server/ksmbd_work.h    | 12 ++++++++
 fs/smb/server/transport_tcp.c | 58 +++++++++++++++++++++++++++++++++++
 5 files changed, 139 insertions(+), 5 deletions(-)

diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index 9e8fdb39e5a2..97ad736bf87a 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -351,10 +351,12 @@ int ksmbd_conn_wait_idle_sess_id(struct ksmbd_conn *curr_conn, u64 sess_id)
 }
 
 int ksmbd_conn_write(struct ksmbd_work *work)
 {
 	struct ksmbd_conn *conn = work->conn;
+	unsigned int head_len;
+	unsigned int pdu_len;
 	int sent;
 
 	if (!work->response_buf) {
 		pr_err("NULL response header\n");
 		return -EINVAL;
@@ -364,16 +366,29 @@ int ksmbd_conn_write(struct ksmbd_work *work)
 		return 0;
 
 	if (!work->iov_idx)
 		return -EINVAL;
 
+	pdu_len = get_rfc1002_len(work->iov[0].iov_base) + 4;
 	ksmbd_conn_lock(conn);
-	sent = conn->transport->ops->writev(conn->transport, work->iov,
-			work->iov_cnt,
-			get_rfc1002_len(work->iov[0].iov_base) + 4,
-			work->need_invalidate_rkey,
-			work->remote_key);
+	if (work->read_payload) {
+		if (WARN_ON_ONCE(work->read_payload->len > pdu_len)) {
+			sent = -EINVAL;
+		} else if (WARN_ON_ONCE(!conn->transport->ops->write_read_payload)) {
+			sent = -EOPNOTSUPP;
+		} else {
+			head_len = pdu_len - work->read_payload->len;
+			sent = conn->transport->ops->write_read_payload(conn->transport,
+					work->iov, work->iov_cnt, head_len,
+					work->read_payload);
+		}
+	} else {
+		sent = conn->transport->ops->writev(conn->transport, work->iov,
+				work->iov_cnt, pdu_len,
+				work->need_invalidate_rkey,
+				work->remote_key);
+	}
 	ksmbd_conn_unlock(conn);
 
 	if (sent < 0) {
 		pr_err("Failed to send message: %d\n", sent);
 		return sent;
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index ec75633b7da0..a44e382138a7 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -138,10 +138,14 @@ struct ksmbd_transport_ops {
 	int (*read)(struct ksmbd_transport *t, char *buf,
 		    unsigned int size, int max_retries);
 	int (*writev)(struct ksmbd_transport *t, struct kvec *iovs, int niov,
 		      int size, bool need_invalidate_rkey,
 		      unsigned int remote_key);
+	int (*write_read_payload)(struct ksmbd_transport *t,
+				  struct kvec *iovs, int niov,
+				  unsigned int head_len,
+				  struct ksmbd_read_payload *payload);
 	int (*rdma_read)(struct ksmbd_transport *t,
 			 void *buf, unsigned int len,
 			 struct smbdirect_buffer_descriptor_v1 *desc,
 			 unsigned int desc_len);
 	int (*rdma_write)(struct ksmbd_transport *t,
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index e2c2f45264be..05361f6aa8fb 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -1,10 +1,11 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 /*
  *   Copyright (C) 2019 Samsung Electronics Co., Ltd.
  */
 
+#include <linux/bvec.h>
 #include <linux/list.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
 #include <linux/workqueue.h>
 
@@ -14,10 +15,23 @@
 #include "mgmt/ksmbd_ida.h"
 
 static struct kmem_cache *work_cache;
 static struct workqueue_struct *ksmbd_wq;
 
+void ksmbd_read_payload_release(struct ksmbd_read_payload *payload)
+{
+	unsigned int i;
+
+	if (!payload)
+		return;
+
+	for (i = 0; i < payload->nr_bvecs; i++)
+		put_page(payload->bvec[i].bv_page);
+	kvfree(payload->bvec);
+	kfree(payload);
+}
+
 static int ksmbd_reserve_iov(struct ksmbd_work *work, int need_iov_cnt)
 {
 	struct kvec *new;
 	int new_alloc_cnt = work->iov_alloc_cnt;
 
@@ -74,10 +88,11 @@ void ksmbd_free_work_struct(struct ksmbd_work *work)
 	list_for_each_entry_safe(ar, tmp, &work->aux_read_list, entry) {
 		kvfree(ar->buf);
 		list_del(&ar->entry);
 		kfree(ar);
 	}
+	ksmbd_read_payload_release(work->read_payload);
 
 	kfree(work->tr_buf);
 	kvfree(work->compress_buf);
 	kvfree(work->request_buf);
 	if (work->iov != work->iov_inline)
@@ -179,10 +194,40 @@ int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
 			   void *aux_buf, unsigned int aux_size)
 {
 	return __ksmbd_iov_pin_rsp(work, ib, len, aux_buf, aux_size);
 }
 
+/**
+ * ksmbd_iov_pin_rsp_read_payload() - attach a page-backed READ payload
+ * @work: smb work that owns the response
+ * @ib: response header
+ * @len: response header length
+ * @payload: payload whose ownership transfers to @work only on success
+ *
+ * Return: 0 on success, otherwise a negative error with ownership retained by
+ * the caller.
+ */
+int ksmbd_iov_pin_rsp_read_payload(struct ksmbd_work *work, void *ib, int len,
+				   struct ksmbd_read_payload *payload)
+{
+	int ret;
+
+	if (!payload || !payload->bvec || !payload->nr_bvecs || !payload->len)
+		return -EINVAL;
+	if (WARN_ON_ONCE(work->read_payload) ||
+	    WARN_ON_ONCE(payload->len > INT_MAX))
+		return -EINVAL;
+
+	ret = __ksmbd_iov_pin_rsp(work, ib, len, NULL, 0);
+	if (ret)
+		return ret;
+
+	inc_rfc1001_len(work->iov[0].iov_base, payload->len);
+	work->read_payload = payload;
+	return 0;
+}
+
 int allocate_interim_rsp_buf(struct ksmbd_work *work)
 {
 	work->response_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, KSMBD_DEFAULT_GFP);
 	if (!work->response_buf)
 		return -ENOMEM;
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 88104f0cf363..c0e1370e2599 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -10,10 +10,11 @@
 #include <linux/workqueue.h>
 
 struct ksmbd_conn;
 struct ksmbd_session;
 struct ksmbd_tree_connect;
+struct bio_vec;
 
 #define KSMBD_WORK_INLINE_IOVS	4
 
 enum {
 	KSMBD_WORK_ACTIVE = 0,
@@ -24,10 +25,17 @@ enum {
 struct aux_read {
 	void *buf;
 	struct list_head entry;
 };
 
+struct ksmbd_read_payload {
+	struct bio_vec		*bvec;
+	unsigned int		nr_bvecs;
+	unsigned int		nr_alloc;
+	size_t			len;
+};
+
 /* one of these for every pending CIFS request at the connection */
 struct ksmbd_work {
 	/* Server corresponding to this mid */
 	struct ksmbd_conn               *conn;
 	struct ksmbd_session            *sess;
@@ -37,10 +45,11 @@ struct ksmbd_work {
 	void                            *request_buf;
 	/* Response buffer */
 	void                            *response_buf;
 
 	struct list_head		aux_read_list;
+	struct ksmbd_read_payload *read_payload;
 
 	struct kvec			*iov;
 	int				iov_alloc_cnt;
 	int				iov_cnt;
 	int				iov_idx;
@@ -142,8 +151,11 @@ int ksmbd_work_pool_init(void);
 int ksmbd_workqueue_init(void);
 void ksmbd_workqueue_destroy(void);
 bool ksmbd_queue_work(struct ksmbd_work *work);
 int ksmbd_iov_pin_rsp_read(struct ksmbd_work *work, void *ib, int len,
 			   void *aux_buf, unsigned int aux_size);
+int ksmbd_iov_pin_rsp_read_payload(struct ksmbd_work *work, void *ib, int len,
+				   struct ksmbd_read_payload *payload);
+void ksmbd_read_payload_release(struct ksmbd_read_payload *payload);
 int ksmbd_iov_pin_rsp(struct ksmbd_work *work, void *ib, int len);
 int allocate_interim_rsp_buf(struct ksmbd_work *work);
 #endif /* __KSMBD_WORK_H__ */
diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
index 13b711ea575d..3095898d74d1 100644
--- a/fs/smb/server/transport_tcp.c
+++ b/fs/smb/server/transport_tcp.c
@@ -425,10 +425,67 @@ static int ksmbd_tcp_writev(struct ksmbd_transport *t, struct kvec *iov,
 	struct msghdr smb_msg = {.msg_flags = MSG_NOSIGNAL};
 
 	return kernel_sendmsg(TCP_TRANS(t)->sock, &smb_msg, iov, nvecs, size);
 }
 
+static int ksmbd_tcp_send_iter(struct socket *sock, struct iov_iter *iter,
+			       unsigned int flags, bool *sent_any)
+{
+	struct msghdr msg = { .msg_flags = MSG_NOSIGNAL | flags };
+	int sent = 0;
+
+	while (iov_iter_count(iter)) {
+		int ret;
+
+		msg.msg_iter = *iter;
+		ret = sock_sendmsg(sock, &msg);
+		if (ret <= 0)
+			return ret ?: -EPIPE;
+		*sent_any = true;
+		iov_iter_advance(iter, ret);
+		sent += ret;
+	}
+	return sent;
+}
+
+static int ksmbd_tcp_write_read_payload(struct ksmbd_transport *t,
+					struct kvec *iov, int nvecs,
+					unsigned int head_len,
+					struct ksmbd_read_payload *payload)
+{
+	struct tcp_transport *tcp = TCP_TRANS(t);
+	struct iov_iter iter;
+	bool sent_any = false;
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < payload->nr_bvecs; i++) {
+		if (!sendpages_ok(payload->bvec[i].bv_page,
+				  payload->bvec[i].bv_len,
+				  payload->bvec[i].bv_offset))
+			return -EOPNOTSUPP;
+	}
+
+	iov_iter_kvec(&iter, ITER_SOURCE, iov, nvecs, head_len);
+	ret = ksmbd_tcp_send_iter(tcp->sock, &iter, MSG_MORE, &sent_any);
+	if (ret < 0)
+		goto out;
+	iov_iter_bvec(&iter, ITER_SOURCE, payload->bvec, payload->nr_bvecs,
+		      payload->len);
+	ret = ksmbd_tcp_send_iter(tcp->sock, &iter, MSG_SPLICE_PAGES,
+				  &sent_any);
+	if (ret >= 0)
+		return head_len + ret;
+out:
+	/* A partially emitted RFC1002 PDU makes this TCP stream unusable. */
+	if (sent_any) {
+		ksmbd_conn_set_exiting(t->conn);
+		kernel_sock_shutdown(tcp->sock, SHUT_RDWR);
+	}
+	return ret;
+}
+
 static void ksmbd_tcp_disconnect(struct ksmbd_transport *t)
 {
 	free_transport(TCP_TRANS(t));
 	if (server_conf.max_connections)
 		atomic_dec(&active_num_conn);
@@ -677,8 +734,9 @@ int ksmbd_tcp_set_interfaces(char *ifc_list, int ifc_list_sz)
 }
 
 static const struct ksmbd_transport_ops ksmbd_tcp_transport_ops = {
 	.read		= ksmbd_tcp_read,
 	.writev		= ksmbd_tcp_writev,
+	.write_read_payload = ksmbd_tcp_write_read_payload,
 	.disconnect	= ksmbd_tcp_disconnect,
 	.free_transport = ksmbd_tcp_free_transport,
 };
-- 
2.47.3
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.