[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