Linux CIFS filesystem development
 help / color / mirror / Atom feed
From: Enzo Matsumiya <ematsumiya@suse.de>
To: nspmangalore@gmail.com
Cc: linux-cifs@vger.kernel.org, smfrench@gmail.com, pc@manguebit.org,
	 bharathsm@microsoft.com, dhowells@redhat.com,
	henrique.carvalho@suse.com,
	 Shyam Prasad N <sprasad@microsoft.com>
Subject: Re: [PATCH v3 06/19] cifs: optimize readdir for larger directories
Date: Tue, 28 Apr 2026 14:00:02 -0300	[thread overview]
Message-ID: <afDnSUDhehP7fyIF@suse.de> (raw)
In-Reply-To: <20260428160804.281745-6-sprasad@microsoft.com>

On 04/28, nspmangalore@gmail.com wrote:
>From: Shyam Prasad N <sprasad@microsoft.com>
>
>Today QueryDirectory uses the compound_send_recv infrastructure
>which is limited to 16KB in size. As a result, readdir of large
>directories generally take several round trips.
>
>With this change, if the readdir needs a QueryDir after the first
>round-trip (meaning that there are more dirents to read), then the
>following QueryDirs will now switch to using larger buffers with
>MTU credits.
>
>Till now, the only command type that used this flow was SMB2_READ.
>In case of encrypted response, it becomes challenging to decide if
>the response is for SMB2_READ or SMB2_READDIR. This change reuses
>receive_encrypted_read and after decrypting the response decides
>the handling function based on the command in the resp header. That
>way, care has been taken to ensure that the read code path
>modifications on account of this change are kept to a minimum.
>
>Cc: David Howells <dhowells@redhat.com>
>Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
>---
> fs/smb/client/cifsglob.h  |  21 +-
> fs/smb/client/cifsproto.h |   3 +
> fs/smb/client/readdir.c   |   2 +-
> fs/smb/client/smb1ops.c   |   4 +-
> fs/smb/client/smb2misc.c  |   7 +-
> fs/smb/client/smb2ops.c   | 458 ++++++++++++++++++++++++++++++++++++--
> fs/smb/client/smb2pdu.c   | 185 ++++++++++++++-
> fs/smb/client/smb2pdu.h   |   3 +
> fs/smb/client/smb2proto.h |   3 +
> fs/smb/client/trace.h     |   1 +
> fs/smb/client/transport.c | 170 ++++++++++++++
> 11 files changed, 822 insertions(+), 35 deletions(-)
>
>diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
>index 8d089ba08e3e5..38d5600efe2c8 100644
>--- a/fs/smb/client/cifsglob.h
>+++ b/fs/smb/client/cifsglob.h
>@@ -504,7 +504,7 @@ struct smb_version_operations {
> 			       struct cifs_search_info *);
> 	/* continue readdir */
> 	int (*query_dir_next)(const unsigned int, struct cifs_tcon *,
>-			      struct cifs_fid *,
>+			      struct cifs_sb_info *, struct cifs_fid *,
> 			      __u16, struct cifs_search_info *srch_inf);
> 	/* close dir */
> 	int (*close_dir)(const unsigned int, struct cifs_tcon *,
>@@ -1397,6 +1397,25 @@ struct cifs_search_info {
> 	bool is_dynamic_buf:1; /* dynamically allocated buffer - can be variable size */
> };
>
>+/* Structure for QueryDirectory with multi-credit support */
>+struct cifs_query_dir_io {
>+	struct cifs_tcon *tcon;
>+	struct TCP_Server_Info *server;
>+	struct cifs_search_info *srch_inf;
>+	unsigned int xid;
>+	u64 persistent_fid;
>+	u64 volatile_fid;
>+	int index;
>+	struct kvec combined_iov;	/* Pre-allocated buffer to hold resp */
>+	struct completion done;
>+	int result;
>+	struct cifs_credits credits;
>+	bool replay;
>+	unsigned int retries;
>+	unsigned int cur_sleep;
>+	struct kvec iov[2];		/* For response handling */
>+};
>+
> #define ACL_NO_MODE	((umode_t)(-1))
> struct cifs_open_parms {
> 	struct cifs_tcon *tcon;
>diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h
>index 884bfa1cf0b42..bbbee0ef09443 100644
>--- a/fs/smb/client/cifsproto.h
>+++ b/fs/smb/client/cifsproto.h
>@@ -336,6 +336,9 @@ struct cifs_ses *cifs_get_smb_ses(struct TCP_Server_Info *server,
> int cifs_readv_receive(struct TCP_Server_Info *server,
> 		       struct mid_q_entry *mid);
>
>+int cifs_query_dir_receive(struct TCP_Server_Info *server,
>+			    struct mid_q_entry *mid);
>+
> int cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,
> 			  struct cifs_sb_info *cifs_sb,
> 			  const unsigned char *path, char *pbuf,
>diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c
>index b50efd9b9e1d2..8a444f97e0ae9 100644
>--- a/fs/smb/client/readdir.c
>+++ b/fs/smb/client/readdir.c
>@@ -760,7 +760,7 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
> 	while ((index_to_find >= cfile->srch_inf.index_of_last_entry) &&
> 	       (rc == 0) && !cfile->srch_inf.endOfSearch) {
> 		cifs_dbg(FYI, "calling findnext2\n");
>-		rc = server->ops->query_dir_next(xid, tcon, &cfile->fid,
>+		rc = server->ops->query_dir_next(xid, tcon, cifs_sb, &cfile->fid,
> 						 search_flags,
> 						 &cfile->srch_inf);
> 		if (rc)
>diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
>index 9694117050a6c..860a9b23a2f8d 100644
>--- a/fs/smb/client/smb1ops.c
>+++ b/fs/smb/client/smb1ops.c
>@@ -1135,8 +1135,8 @@ cifs_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
>
> static int
> cifs_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon,
>-		    struct cifs_fid *fid, __u16 search_flags,
>-		    struct cifs_search_info *srch_inf)
>+		    struct cifs_sb_info *cifs_sb, struct cifs_fid *fid,
>+		    __u16 search_flags, struct cifs_search_info *srch_inf)
> {
> 	return CIFSFindNext(xid, tcon, fid->netfid, search_flags, srch_inf);
> }
>diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c
>index 973fce3c959c4..b7b6ecd5fdaee 100644
>--- a/fs/smb/client/smb2misc.c
>+++ b/fs/smb/client/smb2misc.c
>@@ -12,6 +12,7 @@
> #include "cifsglob.h"
> #include "cifsproto.h"
> #include "smb2proto.h"
>+#include "smb2pdu.h"
> #include "cifs_debug.h"
> #include "cifs_unicode.h"
> #include "../common/smb2status.h"
>@@ -316,7 +317,7 @@ char *
> smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr)
> {
> 	const int max_off = 4096;
>-	const int max_len = 128 * 1024;
>+	int max_len = 128 * 1024;
>
> 	*off = 0;
> 	*len = 0;
>@@ -367,6 +368,10 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr)
> 		  ((struct smb2_query_directory_rsp *)shdr)->OutputBufferOffset);
> 		*len = le32_to_cpu(
> 		  ((struct smb2_query_directory_rsp *)shdr)->OutputBufferLength);
>+		/* Allow larger buffers for query directory (up to 2MB).
>+		 * The actual data is handled separately in cifs_query_dir_receive().
>+		 */
>+		max_len = SMB2_MAX_QD_DATABUF_SIZE;
> 		break;
> 	case SMB2_IOCTL:
> 		*off = le32_to_cpu(
>diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
>index f075330f88598..2df4d080e95f0 100644
>--- a/fs/smb/client/smb2ops.c
>+++ b/fs/smb/client/smb2ops.c
>@@ -2713,11 +2713,131 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,
>
> static int
> smb2_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon,
>-		    struct cifs_fid *fid, __u16 search_flags,
>-		    struct cifs_search_info *srch_inf)
>+		    struct cifs_sb_info *cifs_sb, struct cifs_fid *fid,
>+		    __u16 search_flags, struct cifs_search_info *srch_inf)
> {
>-	return SMB2_query_directory(xid, tcon, fid->persistent_fid,
>-				    fid->volatile_fid, 0, srch_inf);
>+	struct cifs_query_dir_io qd_io;
>+	struct TCP_Server_Info *server;
>+	struct cifs_ses *ses = tcon->ses;
>+	size_t buf_size;
>+	int rc;
>+	int resp_buftype = CIFS_DYNAMIC_BUFFER;
>+
>+	/* Pick server and determine buffer size based on negotiated rsize */
>+	server = cifs_pick_channel(ses);
>+	if (!server)
>+		return smb_EIO(smb_eio_trace_null_pointers);
>+
>+	/* Negotiate rsize if not already set */
>+	if (cifs_sb->ctx->rsize == 0)
>+		cifs_negotiate_rsize(server, cifs_sb->ctx, tcon);
>+
>+	/* Use negotiated rsize for buffer size, with reasonable limits */
>+	buf_size = cifs_sb->ctx->rsize;
>+
>+	cifs_dbg(FYI, "%s: using buffer size %zu (rsize=%u, encrypted=%d)\n",
>+		 __func__, buf_size, cifs_sb->ctx->rsize, smb3_encryption_required(tcon));
>+
>+	/* Initialize qd_io structure */
>+	memset(&qd_io, 0, sizeof(qd_io));
>+	qd_io.tcon = tcon;
>+	qd_io.server = server;
>+	qd_io.srch_inf = srch_inf;
>+	qd_io.xid = xid;
>+	qd_io.persistent_fid = fid->persistent_fid;
>+	qd_io.volatile_fid = fid->volatile_fid;
>+	qd_io.index = 0;
>+	qd_io.result = 0;
>+	qd_io.replay = false;
>+	qd_io.retries = 0;
>+	qd_io.cur_sleep = 0;
>+
>+	/* Allocate credits for the buffer size */
>+	rc = server->ops->wait_mtu_credits(server, buf_size, &buf_size,
>+					   &qd_io.credits);
>+	if (rc) {
>+		cifs_dbg(VFS, "%s: failed to get credits: %d\n", __func__, rc);
>+		return rc;
>+	}
>+
>+	cifs_dbg(FYI, "%s: allocated %u credits for %zu bytes\n",
>+		 __func__, qd_io.credits.value, buf_size);
>+
>+	/* Send query directory with large buffer and wait for completion */
>+	rc = SMB2_query_directory_large(&qd_io, buf_size);
>+	if (rc) {
>+		if (rc == -ENODATA) {
>+			const struct smb2_hdr *hdr = NULL;
>+
>+			if (qd_io.combined_iov.iov_base)
>+				hdr = (const struct smb2_hdr *)qd_io.combined_iov.iov_base;
>+			else if (qd_io.iov[0].iov_base)
>+				hdr = (const struct smb2_hdr *)qd_io.iov[0].iov_base;
>+
>+			/*
>+			 * ENODATA from QUERY_DIRECTORY generally means enumeration reached
>+			 * the end. Treat it as end-of-search even if the header buffer is
>+			 * unavailable in this async path.
>+			 */
>+			if (!hdr) {
>+				cifs_dbg(FYI, "%s: ENODATA but hdr is NULL\n", __func__);
>+			} else {
>+				cifs_dbg(FYI, "%s: ENODATA with hdr->Status=0x%x (STATUS_NO_MORE_FILES=0x%x)\n",
>+					 __func__, le32_to_cpu(hdr->Status), le32_to_cpu(STATUS_NO_MORE_FILES));
>+			}
>+
>+			if (hdr && hdr->Status == STATUS_NO_MORE_FILES) {
>+				trace_smb3_query_dir_done(xid, fid->persistent_fid,
>+					tcon->tid, tcon->ses->Suid, 0, 0);
>+				srch_inf->endOfSearch = true;
>+				rc = 0;
>+			} else {
>+				cifs_dbg(FYI, "%s: ENODATA but Status mismatch - not treating as end-of-search\n",
>+					__func__);
>+				trace_smb3_query_dir_err(xid, fid->persistent_fid,
>+					tcon->tid, tcon->ses->Suid, 0, 0, rc);
>+			}
>+		} else {
>+			trace_smb3_query_dir_err(xid, fid->persistent_fid,
>+				tcon->tid, tcon->ses->Suid, 0, 0, rc);
>+		}
>+		goto qdir_next_exit;
>+	}
>+
>+	/* Parse the response using the combined buffer built in receive handler */
>+	if (qd_io.combined_iov.iov_len > 0) {
>+		rc = smb2_parse_query_directory(tcon, &qd_io.combined_iov, resp_buftype,
>+						srch_inf);
>+		if (rc) {
>+			trace_smb3_query_dir_err(xid, fid->persistent_fid,
>+				tcon->tid, tcon->ses->Suid, 0, 0, rc);
>+			kfree(qd_io.combined_iov.iov_base);
>+			qd_io.combined_iov.iov_base = NULL;
>+			goto qdir_next_exit;
>+		}
>+
>+		/* combined_iov.iov_base ownership transferred to srch_inf->ntwrk_buf_start */
>+		qd_io.combined_iov.iov_base = NULL;
>+
>+		trace_smb3_query_dir_done(xid, fid->persistent_fid,
>+			tcon->tid, tcon->ses->Suid, 0,
>+			srch_inf->entries_in_buffer);
>+	}
>+
>+qdir_next_exit:
>+	/* Free the data buffer if not transferred to srch_inf */
>+	kfree(qd_io.combined_iov.iov_base);
>+
>+	/* Return credits if we still have them (they should have been cleared in callback) */
>+	if (qd_io.credits.value != 0) {
>+		trace_smb3_rw_credits(0, 0, 0,
>+				      server->credits, server->in_flight,
>+				      qd_io.credits.value,
>+				      cifs_trace_rw_credits_query_dir_done);
>+		add_credits(server, &qd_io.credits, 0);
>+	}
>+
>+	return rc;
> }
>
> static int
>@@ -4834,6 +4954,252 @@ cifs_copy_folioq_to_iter(struct folio_queue *folioq, size_t data_size,
> 	return 0;
> }
>
>+static int
>+cifs_copy_folioq_to_buf(struct folio_queue *folioq, size_t total_size,
>+			size_t skip, char *buf, size_t buf_len)
>+{
>+	size_t copied = 0;
>+
>+	if (buf_len > total_size - skip)
>+		buf_len = total_size - skip;
>+
>+	for (; folioq; folioq = folioq->next) {
>+		for (int s = 0; s < folioq_count(folioq); s++) {
>+			struct folio *folio = folioq_folio(folioq, s);
>+			size_t fsize = folio_size(folio);
>+			size_t len = umin(fsize - skip, buf_len);
>+
>+			if (len == 0)
>+				break;
>+
>+			memcpy_from_folio(buf + copied, folio, skip, len);
>+			copied += len;
>+			buf_len -= len;
>+			skip = 0;
>+
>+			if (buf_len == 0)
>+				return 0;
>+		}
>+	}
>+
>+	return 0;
>+}
>+
>+/*
>+ * Handle encrypted QueryDirectory response data.
>+ * Called only for encrypted responses where mid->decrypted == true.
>+ * For unencrypted responses, cifs_query_dir_receive handles everything.
>+ *
>+ * This is written in such a way that handle_read_data does not need modification.
>+ *
>+ * buf: contains read_rsp_size bytes of decrypted response
>+ * buffer: contains (total_len - read_rsp_size) bytes of decrypted data
>+ *
>+ * Since sizeof(struct smb2_query_directory_rsp) < read_rsp_size,
>+ * the response header is always fully in buf. Data may be split between
>+ * buf and buffer depending on data_offset.
>+ */
>+static int
>+handle_query_dir_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
>+		      char *buf, unsigned int buf_len, struct folio_queue *buffer,
>+		      unsigned int buffer_len, bool is_offloaded)
>+{
>+	struct cifs_query_dir_io *qd_io = mid->callback_data;
>+	struct smb2_query_directory_rsp *rsp;
>+	struct smb2_hdr *shdr = (struct smb2_hdr *)buf;
>+	unsigned int data_offset, data_len;
>+	unsigned int hdr_len;
>+
>+	cifs_dbg(FYI, "%s: processing encrypted QueryDirectory response\n", __func__);
>+
>+	if (shdr->Command != SMB2_QUERY_DIRECTORY) {
>+		cifs_server_dbg(VFS, "only QueryDirectory responses are supported\n");
>+		return -EOPNOTSUPP;
>+	}
>+
>+	if (server->ops->is_session_expired &&
>+	    server->ops->is_session_expired(buf)) {
>+		if (!is_offloaded)
>+			cifs_reconnect(server, true);
>+		return -1;
>+	}
>+
>+	if (server->ops->is_status_pending &&
>+			server->ops->is_status_pending(buf, server))
>+		return -1;
>+
>+	rsp = (struct smb2_query_directory_rsp *)buf;
>+	hdr_len = min_t(unsigned int, buf_len,
>+			 sizeof(struct smb2_query_directory_rsp));
>+
>+	/* Map error code first */
>+	qd_io->result = server->ops->map_error(buf, false);
>+
>+	/* Get data_offset early to set up iov properly */
>+	data_offset = le16_to_cpu(rsp->OutputBufferOffset);
>+	data_len = le32_to_cpu(rsp->OutputBufferLength);
>+
>+	/* Set up first iov to point to header portion (needed for credits/signature) */
>+	qd_io->iov[0].iov_base = buf;
>+	qd_io->iov[0].iov_len = qd_io->result ? hdr_len : data_offset;
>+
>+	if (qd_io->result != 0) {
>+		cifs_dbg(FYI, "%s: server returned error %d (Status=0x%x)\n",
>+			 __func__, qd_io->result, le32_to_cpu(rsp->hdr.Status));
>+
>+		/*
>+		 * Copy header to persistent combined_iov buffer so status
>+		 * remains accessible after receive handler returns. buf is temporary
>+		 * and will be freed/reused, so we can't leave iov[0] pointing to it
>+		 */
>+		if (qd_io->combined_iov.iov_base && hdr_len > 0 &&
>+		    hdr_len <= qd_io->combined_iov.iov_len) {
>+			memcpy(qd_io->combined_iov.iov_base, buf, hdr_len);
>+			qd_io->iov[0].iov_base = qd_io->combined_iov.iov_base;
>+			qd_io->iov[0].iov_len = hdr_len;
>+			cifs_dbg(FYI, "%s: copied error response header to combined_iov\n",
>+				__func__);
>+		}
>+
>+		/*
>+		 * Normal error on query_directory response - response received successfully,
>+		 * but the command failed. Store error in qd_io->result for callback
>+		 */
>+		if (is_offloaded)
>+			mid->mid_state = MID_RESPONSE_RECEIVED;
>+		else
>+			dequeue_mid(server, mid, false);
>+		return 0;
>+	}
>+
>+	/* Success - parse the response data */
>+	cifs_dbg(FYI, "%s: data_offset=%u data_len=%u buf_len=%u buffer_len=%u\n",
>+		 __func__, data_offset, data_len, buf_len, buffer_len);
>+
>+	/* Validate data_offset */
>+	if (data_offset < sizeof(struct smb2_query_directory_rsp)) {
>+		cifs_dbg(FYI, "%s: data offset (%u) inside response header\n",
>+			 __func__, data_offset);
>+		data_offset = sizeof(struct smb2_query_directory_rsp);
>+	} else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) {
>+		cifs_dbg(VFS, "%s: data offset (%u) beyond end of smallbuf\n",
>+			 __func__, data_offset);
>+		qd_io->result = -EIO;
>+		dequeue_mid(server, mid, qd_io->result);
>+		return qd_io->result;
>+	}
>+
>+	/* Validate data_offset is within buf_len + buffer_len */
>+	if (data_offset > buf_len + buffer_len) {
>+		cifs_dbg(VFS, "%s: data offset (%u) beyond response length (%u)\n",
>+			 __func__, data_offset, buf_len + buffer_len);
>+		qd_io->result = -EIO;
>+		dequeue_mid(server, mid, qd_io->result);
>+		return qd_io->result;
>+	}
>+
>+	/* Validate response fits in pre-allocated combined buffer */
>+	if ((size_t)data_offset + data_len > qd_io->combined_iov.iov_len) {
>+		cifs_dbg(VFS, "%s: response (%u + %u) exceeds buffer capacity (%zu)\n",
>+			 __func__, data_offset, data_len, qd_io->combined_iov.iov_len);
>+		qd_io->result = -EIO;
>+		dequeue_mid(server, mid, qd_io->result);
>+		return qd_io->result;
>+	}
>+
>+	/* Copy the prefix present in buf into combined_iov, preserving wire layout */
>+	memcpy(qd_io->combined_iov.iov_base, buf, min(data_offset, buf_len));
>+
>+	if (data_offset < buf_len) {
>+		/* Data starts in buf, may continue into buffer */
>+		unsigned int data_in_buf = buf_len - data_offset;
>+		unsigned int data_in_buffer;
>+
>+		if (data_len <= data_in_buf) {
>+			/* All data is in buf */
>+			memcpy(qd_io->combined_iov.iov_base + data_offset,
>+			       buf + data_offset, data_len);
>+		} else {
>+			/* Copy from buf first */
>+			memcpy(qd_io->combined_iov.iov_base + data_offset,
>+			       buf + data_offset, data_in_buf);
>+
>+			/* Copy remainder from buffer at offset 0 */
>+			data_in_buffer = data_len - data_in_buf;
>+			if (data_in_buffer > buffer_len) {
>+				cifs_dbg(VFS, "%s: data_in_buffer (%u) > buffer_len (%u)\n",
>+					 __func__, data_in_buffer, buffer_len);
>+				qd_io->result = -EIO;
>+				dequeue_mid(server, mid, qd_io->result);
>+				return qd_io->result;
>+			}
>+
>+			qd_io->result = cifs_copy_folioq_to_buf(buffer, buffer_len, 0,
>+								qd_io->combined_iov.iov_base +
>+								data_offset + data_in_buf,
>+								data_in_buffer);
>+			if (qd_io->result != 0) {
>+				cifs_dbg(VFS, "%s: failed to copy from folio_queue: %d\n",
>+					 __func__, qd_io->result);
>+				dequeue_mid(server, mid, qd_io->result);
>+				return qd_io->result;
>+			}
>+		}
>+	} else {
>+		/* Padding and data are in buffer starting at offset 0 */
>+		unsigned int bytes_in_buffer = data_offset - buf_len + data_len;
>+
>+		if (bytes_in_buffer > buffer_len) {
>+			cifs_dbg(VFS, "%s: data beyond buffer: prefix+len=%u buffer_len=%u\n",
>+				 __func__, bytes_in_buffer, buffer_len);
>+			qd_io->result = -EIO;
>+			dequeue_mid(server, mid, qd_io->result);
>+			return qd_io->result;
>+		}
>+
>+		qd_io->result = cifs_copy_folioq_to_buf(buffer, buffer_len, 0,
>+							qd_io->combined_iov.iov_base + buf_len,
>+							bytes_in_buffer);
>+		if (qd_io->result != 0) {
>+			cifs_dbg(VFS, "%s: failed to copy from folio_queue: %d\n",
>+				 __func__, qd_io->result);
>+			dequeue_mid(server, mid, qd_io->result);
>+			return qd_io->result;
>+		}
>+	}
>+
>+	/* Set up iov[1] pointing into combined buffer, finalize valid length */
>+	qd_io->iov[1].iov_base = qd_io->combined_iov.iov_base + data_offset;
>+	qd_io->iov[1].iov_len = data_len;
>+	qd_io->combined_iov.iov_len = data_offset + data_len;
>+
>+	dequeue_mid(server, mid, false);
>+	return 0;
>+}
>+
>+/*
>+ * Handle callback for async QueryDirectory with multi-credit support.
>+ * For encrypted responses, extracts decrypted data.
>+ * For unencrypted responses, cifs_query_dir_receive already processed everything.
>+ */
>+int
>+smb2_query_dir_handle_data(struct TCP_Server_Info *server, struct mid_q_entry *mid)
>+{
>+	char *buf = server->large_buf ? server->bigbuf : server->smallbuf;
>+
>+	/* For unencrypted responses, data already processed in cifs_query_dir_receive */
>+	if (!mid->decrypted)
>+		return 0;
>+
>+	/*
>+	 * For small encrypted responses (< CIFSMaxBufSize), all data is in buf.
>+	 * For large encrypted responses, this callback is not used - instead,
>+	 * receive_encrypted_read/smb2_decrypt_offload call handle_query_dir_data directly.
>+	 */
>+	return handle_query_dir_data(server, mid, buf, server->pdu_size,
>+				      NULL, 0, false);
>+}
>+
> static int
> handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
> 		 char *buf, unsigned int buf_len, struct folio_queue *buffer,
>@@ -4997,25 +5363,46 @@ static void smb2_decrypt_offload(struct work_struct *work)
> 	int rc;
> 	struct mid_q_entry *mid;
> 	struct iov_iter iter;
>+	struct smb2_hdr *shdr;
>+	unsigned int read_rsp_size = dw->server->vals->read_rsp_size;
>
>+	/* decrypt read_rsp_size in buf + remainder in folio_queue */
> 	iov_iter_folio_queue(&iter, ITER_DEST, dw->buffer, 0, 0, dw->len);
>-	rc = decrypt_raw_data(dw->server, dw->buf, dw->server->vals->read_rsp_size,
>-			      &iter, true);
>+	rc = decrypt_raw_data(dw->server, dw->buf, read_rsp_size, &iter, true);
> 	if (rc) {
> 		cifs_dbg(VFS, "error decrypting rc=%d\n", rc);
> 		goto free_pages;
> 	}
>
> 	dw->server->lstrp = jiffies;
>+
>+	shdr = (struct smb2_hdr *)(dw->buf + sizeof(struct smb2_transform_hdr));
>+
>+	/*
>+	 * buf now contains read_rsp_size bytes after transform_hdr.
>+	 * folio_queue contains (total_len - read_rsp_size) bytes.
>+	 * The handle functions will determine where data actually starts based on data_offset.
>+	 */
>+
> 	mid = smb2_find_dequeue_mid(dw->server, dw->buf);
> 	if (mid == NULL)
> 		cifs_dbg(FYI, "mid not found\n");
> 	else {
> 		mid->decrypted = true;
>-		rc = handle_read_data(dw->server, mid, dw->buf,
>-				      dw->server->vals->read_rsp_size,
>-				      dw->buffer, dw->len,
>-				      true);
>+
>+		/* Handle based on command type */
>+		if (le16_to_cpu(shdr->Command) == SMB2_READ) {
>+			rc = handle_read_data(dw->server, mid, dw->buf, read_rsp_size,
>+					      dw->buffer, dw->len, true);
>+		} else if (le16_to_cpu(shdr->Command) == SMB2_QUERY_DIRECTORY) {
>+			rc = handle_query_dir_data(dw->server, mid, dw->buf, read_rsp_size,
>+						   dw->buffer, dw->len, true);
>+		} else {
>+			cifs_dbg(VFS, "Unexpected command %u in decrypt offload\n",
>+				 le16_to_cpu(shdr->Command));
>+			rc = -EOPNOTSUPP;
>+		}
>+
> 		if (rc >= 0) {
> #ifdef CONFIG_CIFS_STATS2
> 			mid->when_received = jiffies;
>@@ -5059,9 +5446,10 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
> {
> 	char *buf = server->smallbuf;
> 	struct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf;
>+	struct smb2_hdr *shdr;
> 	struct iov_iter iter;
>-	unsigned int len;
>-	unsigned int buflen = server->pdu_size;
>+	unsigned int len, total_len, buflen = server->pdu_size;
>+	unsigned int read_rsp_size = server->vals->read_rsp_size;
> 	int rc;
> 	struct smb2_decrypt_work *dw;
>
>@@ -5072,7 +5460,10 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
> 	dw->server = server;
>
> 	*num_mids = 1;
>-	len = min_t(unsigned int, buflen, server->vals->read_rsp_size +
>+	total_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
>+
>+	/* Read transform_hdr + read_rsp_size into buf */
>+	len = min_t(unsigned int, buflen, read_rsp_size +
> 		sizeof(struct smb2_transform_hdr)) - HEADER_SIZE(server) + 1;
>
> 	rc = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1, len);
>@@ -5080,9 +5471,8 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
> 		goto free_dw;
> 	server->total_read += rc;
>
>-	len = le32_to_cpu(tr_hdr->OriginalMessageSize) -
>-		server->vals->read_rsp_size;
>-	dw->len = len;
>+	/* Read remaining data into folio_queue */
>+	dw->len = total_len - read_rsp_size;
> 	len = round_up(dw->len, PAGE_SIZE);
>
> 	size_t cur_size = 0;
>@@ -5111,7 +5501,7 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
> 		goto free_pages;
>
> 	/*
>-	 * For large reads, offload to different thread for better performance,
>+	 * For large responses, offload to different thread for better performance,
> 	 * use more cores decrypting which can be expensive
> 	 */
>
>@@ -5125,20 +5515,41 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,
> 		return -1;
> 	}
>
>-	rc = decrypt_raw_data(server, buf, server->vals->read_rsp_size,
>-			      &iter, false);
>+	/* Decrypt: read_rsp_size in buf + remainder in folio_queue */
>+	rc = decrypt_raw_data(server, buf, read_rsp_size, &iter, false);
> 	if (rc)
> 		goto free_pages;
>
>+	shdr = (struct smb2_hdr *) buf;
>+
>+	/*
>+	 * buf now contains the complete response header (read_rsp_size bytes).
>+	 * folio_queue contains (total_len - read_rsp_size) bytes.
>+	 * The handle functions will determine where data actually starts based on data_offset.
>+	 */
>+
> 	*mid = smb2_find_mid(server, buf);
> 	if (*mid == NULL) {
> 		cifs_dbg(FYI, "mid not found\n");
> 	} else {
>-		cifs_dbg(FYI, "mid found\n");
>+		cifs_dbg(FYI, "mid found, command=%u\n", le16_to_cpu(shdr->Command));
> 		(*mid)->decrypted = true;
>-		rc = handle_read_data(server, *mid, buf,
>-				      server->vals->read_rsp_size,
>-				      dw->buffer, dw->len, false);
>+
>+		/* Handle based on command type */
>+		if (le16_to_cpu(shdr->Command) == SMB2_READ) {
>+			rc = handle_read_data(server, *mid, buf, read_rsp_size,
>+					      dw->buffer, dw->len, false);
>+		} else if (le16_to_cpu(shdr->Command) == SMB2_QUERY_DIRECTORY) {
>+			rc = handle_query_dir_data(server, *mid, buf, read_rsp_size,
>+						   dw->buffer, dw->len, false);
>+		} else {
>+			/* For now, other commands not supported in large encrypted path */
>+			cifs_server_dbg(VFS,
>+					"Large encrypted responses only supported for SMB2_READ and SMB2_QUERY_DIRECTORY (got %u)\n",
>+					le16_to_cpu(shdr->Command));
>+			rc = -EOPNOTSUPP;
>+		}
>+
> 		if (rc >= 0) {
> 			if (server->ops->is_network_name_deleted) {
> 				server->ops->is_network_name_deleted(buf,
>@@ -5279,7 +5690,6 @@ smb3_receive_transform(struct TCP_Server_Info *server,
> 		return -ECONNABORTED;
> 	}
>
>-	/* TODO: add support for compounds containing READ. */
> 	if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) {
> 		return receive_encrypted_read(server, &mids[0], num_mids);
> 	}
>diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
>index 2d55246d2851b..92724cfb5b3f5 100644
>--- a/fs/smb/client/smb2pdu.c
>+++ b/fs/smb/client/smb2pdu.c
>@@ -5496,6 +5496,185 @@ num_entries(int infotype, char *bufstart, char *end_of_buf, char **lastentry,
> 	return entrycount;
> }
>
>+/*
>+ * Callback for async QueryDirectory with multi-credit support
>+ */
>+static void
>+smb2_query_dir_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid)
>+{
>+	struct cifs_query_dir_io *qd_io = mid->callback_data;
>+	struct cifs_tcon *tcon = qd_io->tcon;
>+	struct smb2_hdr *shdr = (struct smb2_hdr *)qd_io->iov[0].iov_base;
>+	struct cifs_credits credits = {
>+		.value = 0,
>+		.instance = 0,
>+	};
>+
>+	WARN_ONCE(qd_io->server != server,
>+		  "qd_io server %p != mid server %p",
>+		  qd_io->server, server);
>+
>+	cifs_dbg(FYI, "%s: mid=%llu state=%d result=%d\n",
>+		 __func__, mid->mid, mid->mid_state, qd_io->result);
>+
>+	switch (mid->mid_state) {
>+	case MID_RESPONSE_RECEIVED:
>+		credits.value = le16_to_cpu(shdr->CreditRequest);
>+		credits.instance = server->reconnect_instance;
>+		/* result already set, check signature if needed */
>+		if (server->sign && !mid->decrypted) {
>+			int rc;
>+			struct smb_rqst rqst = {
>+				.rq_iov = &qd_io->iov[0],
>+				.rq_nvec = qd_io->iov[1].iov_len ? 2 : 1,
>+			};
>+
>+			rc = smb2_verify_signature(&rqst, server);
>+			if (rc) {
>+				cifs_tcon_dbg(VFS, "QueryDir signature verification returned error = %d\n",
>+					      rc);
>+				qd_io->result = rc;
>+			}
>+		}
>+		break;
>+	case MID_REQUEST_SUBMITTED:
>+	case MID_RETRY_NEEDED:
>+		qd_io->result = -EAGAIN;
>+		break;
>+	case MID_RESPONSE_MALFORMED:
>+		credits.value = le16_to_cpu(shdr->CreditRequest);
>+		credits.instance = server->reconnect_instance;
>+		qd_io->result = smb_EIO(smb_eio_trace_read_rsp_malformed);
>+		break;
>+	default:
>+		qd_io->result = smb_EIO1(smb_eio_trace_read_mid_state_unknown,
>+					 mid->mid_state);
>+		break;
>+	}
>+
>+	if (qd_io->result && qd_io->result != -ENODATA)
>+		cifs_stats_fail_inc(tcon, SMB2_QUERY_DIRECTORY_HE);
>+
>+	trace_smb3_rw_credits(0, 0, qd_io->credits.value,
>+			      server->credits, server->in_flight,
>+			      0, cifs_trace_rw_credits_read_response_clear);
>+	qd_io->credits.value = 0;
>+	release_mid(server, mid);
>+	trace_smb3_rw_credits(0, 0, 0,
>+			      server->credits, server->in_flight,
>+			      credits.value, cifs_trace_rw_credits_read_response_add);
>+	add_credits(server, &credits, 0);
>+
>+	complete(&qd_io->done);
>+}
>+
>+/*
>+ * QueryDirectory with large buffer and multi-credit support.
>+ * Uses async infrastructure but waits for completion synchronously.
>+ */
>+int
>+SMB2_query_directory_large(struct cifs_query_dir_io *qd_io, unsigned int buf_size)
>+{
>+	int rc, flags = 0;
>+	char *buf;
>+	struct smb2_hdr *shdr;
>+	struct smb_rqst rqst = { .rq_iov = &qd_io->iov[0],
>+				 .rq_nvec = SMB2_QUERY_DIRECTORY_IOV_SIZE };
>+	struct TCP_Server_Info *server = qd_io->server;
>+	struct cifs_tcon *tcon = qd_io->tcon;
>+	unsigned int total_len;
>+	int credit_request;
>+
>+	cifs_dbg(FYI, "%s: buf_size=%u\n", __func__, buf_size);
>+
>+	/* Cap buffer size to avoid kmalloc failures for very large allocations.
>+	 * SMB2_MAX_QD_DATABUF_SIZE is a safe limit that stays well below typical
>+	 * kmalloc constraints while still allowing large directory listings.
>+	 */
>+	if (buf_size > SMB2_MAX_QD_DATABUF_SIZE)
>+		buf_size = SMB2_MAX_QD_DATABUF_SIZE;
>+
>+	/* Allocate response buffer. Since we'll build a combined header+data buffer,
>+	 * we need space for both. We'll request a slightly smaller OutputBufferLength
>+	 * from the server to ensure the total response fits.
>+	 */
>+	qd_io->combined_iov.iov_base = kmalloc(buf_size, GFP_KERNEL);
>+	if (!qd_io->combined_iov.iov_base)
>+		return -ENOMEM;
>+	/* Store total capacity in iov_len; updated to actual data length by receive handler */
>+	qd_io->combined_iov.iov_len = buf_size;
>+
>+	/* Initialize completion */
>+	init_completion(&qd_io->done);
>+
>+	/* Request less data from server to leave room for the response header.
>+	 * Use MAX_CIFS_SMALL_BUFFER_SIZE as a safety margin.
>+	 */
>+	rc = SMB2_query_directory_init(qd_io->xid, tcon, server,
>+				       &rqst, qd_io->persistent_fid,
>+				       qd_io->volatile_fid, qd_io->index,
>+				       qd_io->srch_inf->info_level,
>+				       buf_size - MAX_CIFS_SMALL_BUFFER_SIZE);
>+	if (rc) {
>+		kfree(qd_io->combined_iov.iov_base);
>+		qd_io->combined_iov.iov_base = NULL;
>+		return rc;
>+	}
>+
>+	if (smb3_encryption_required(tcon))
>+		flags |= CIFS_TRANSFORM_REQ;
>+
>+	buf = rqst.rq_iov[0].iov_base;
>+	total_len = rqst.rq_iov[0].iov_len;
>+
>+	shdr = (struct smb2_hdr *)buf;
>+
>+	if (qd_io->replay) {
>+		/* Back-off before retry */
>+		if (qd_io->cur_sleep)
>+			msleep(qd_io->cur_sleep);
>+		smb2_set_replay(server, &rqst);
>+	}
>+
>+	/* Set credit charge based on buffer size */
>+	if (qd_io->credits.value > 0) {
>+		shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(buf_size,
>+						SMB2_MAX_BUFFER_SIZE));
>+		credit_request = le16_to_cpu(shdr->CreditCharge) + 8;
>+		if (server->credits >= server->max_credits)
>+			shdr->CreditRequest = cpu_to_le16(0);
>+		else
>+			shdr->CreditRequest = cpu_to_le16(
>+				min_t(int, server->max_credits -
>+						server->credits, credit_request));
>+
>+		flags |= CIFS_HAS_CREDITS;
>+	}
>+
>+	rc = cifs_call_async(server, &rqst,
>+			     cifs_query_dir_receive, smb2_query_dir_callback,
>+			     smb2_query_dir_handle_data, qd_io, flags,
>+			     &qd_io->credits);
>+	if (rc) {
>+		cifs_stats_fail_inc(tcon, SMB2_QUERY_DIRECTORY_HE);
>+		trace_smb3_query_dir_err(qd_io->xid, qd_io->persistent_fid,
>+					 tcon->tid, tcon->ses->Suid,
>+					 qd_io->index, 0, rc);
>+		kfree(qd_io->combined_iov.iov_base);
>+		qd_io->combined_iov.iov_base = NULL;
>+	}
>+
>+	/* Free request buffer immediately after async call */
>+	cifs_small_buf_release(buf);
>+
>+	if (rc)
>+		return rc;
>+
>+	/* Wait for the async operation to complete */
>+	wait_for_completion(&qd_io->done);
>+	return qd_io->result;
>+}
>+
> /*
>  * Readdir/FindFirst
>  */
>@@ -5560,12 +5739,6 @@ int SMB2_query_directory_init(const unsigned int xid,
> 	req->FileNameOffset =
> 		cpu_to_le16(sizeof(struct smb2_query_directory_req));
> 	req->FileNameLength = cpu_to_le16(len);
>-	/*
>-	 * BB could be 30 bytes or so longer if we used SMB2 specific
>-	 * buffer lengths, but this is safe and close enough.
>-	 */
>-	output_size = min_t(unsigned int, output_size, server->maxBuf);
>-	output_size = min_t(unsigned int, output_size, 2 << 15);
> 	req->OutputBufferLength = cpu_to_le32(output_size);
>
> 	iov[0].iov_base = (char *)req;
>diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h
>index 7b7a864520c68..843e30c1ecc61 100644
>--- a/fs/smb/client/smb2pdu.h
>+++ b/fs/smb/client/smb2pdu.h
>@@ -132,6 +132,9 @@ struct share_redirect_error_context_rsp {
> /* Size of the minimal QueryDir response for checking if more data exists */
> #define SMB2_QD2_RESPONSE_SIZE 4096
>
>+/* max query directory data buffer size */
>+#define SMB2_MAX_QD_DATABUF_SIZE (2 * 1024 * 1024)
>+
> /*
>  * Output buffer size for first QueryDir in Create+QD1+QD2 compound.
>  * Accounts for shared buffer space needed for all three responses.
>diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h
>index 9de7d2fe8d466..9607e8899f7ff 100644
>--- a/fs/smb/client/smb2proto.h
>+++ b/fs/smb/client/smb2proto.h
>@@ -46,6 +46,8 @@ __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock);
> bool smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server);
> int smb3_handle_read_data(struct TCP_Server_Info *server,
> 			  struct mid_q_entry *mid);
>+int smb2_query_dir_handle_data(struct TCP_Server_Info *server,
>+			       struct mid_q_entry *mid);
> struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,
> 					struct super_block *sb,
> 					const unsigned int xid,
>@@ -197,6 +199,7 @@ int SMB2_query_directory_init(const unsigned int xid, struct cifs_tcon *tcon,
> 			      u64 volatile_fid, int index, int info_level,
> 			      unsigned int output_size);
> void SMB2_query_directory_free(struct smb_rqst *rqst);
>+int SMB2_query_directory_large(struct cifs_query_dir_io *qd_io, unsigned int buf_size);
> int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,
> 		 u64 persistent_fid, u64 volatile_fid, u32 pid,
> 		 loff_t new_eof);
>diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
>index acfbb63086ea2..54ee1317c5b12 100644
>--- a/fs/smb/client/trace.h
>+++ b/fs/smb/client/trace.h
>@@ -165,6 +165,7 @@
> 	EM(cifs_trace_rw_credits_write_prepare,		"wr-prepare ") \
> 	EM(cifs_trace_rw_credits_write_response_add,	"wr-resp-add") \
> 	EM(cifs_trace_rw_credits_write_response_clear,	"wr-resp-clr") \
>+	EM(cifs_trace_rw_credits_query_dir_done,	"qd-done    ") \
> 	E_(cifs_trace_rw_credits_zero_in_flight,	"ZERO-IN-FLT")
>
> #define smb3_tcon_ref_traces					      \
>diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c
>index 05f8099047e1a..1e9e6f3f9a06f 100644
>--- a/fs/smb/client/transport.c
>+++ b/fs/smb/client/transport.c
>@@ -1154,6 +1154,176 @@ cifs_readv_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid)
> 	return  __cifs_readv_discard(server, mid, rdata->result);
> }
>
>+static int
>+cifs_query_dir_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid,
>+		      bool malformed)
>+{
>+	int length;
>+
>+	length = cifs_discard_remaining_data(server);
>+	dequeue_mid(server, mid, malformed);
>+	mid->resp_buf = server->smallbuf;
>+	server->smallbuf = NULL;
>+	return length;
>+}

This is exactly the same as __cifs_readv_discard(), maybe just rename
that one to something like __cifs_discard() and reuse it?

>+
>+/*
>+ * Receive handler for async QueryDirectory with multi-credit support
>+ */
>+int
>+cifs_query_dir_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
>+{
>+	int length, len;
>+	unsigned int data_offset, data_len, resp_size;
>+	struct cifs_query_dir_io *qd_io = mid->callback_data;
>+	char *buf = server->smallbuf;
>+	unsigned int buflen = server->pdu_size;
>+	struct smb2_query_directory_rsp *rsp;
>+
>+	cifs_dbg(FYI, "%s: mid=%llu buf_capacity=%zu\n",
>+		 __func__, mid->mid, qd_io->combined_iov.iov_len);
>+
>+	/*
>+	 * Read the rest of QUERY_DIRECTORY_RSP header (sans Data array).
>+	 * QueryDirectory response structure is 64 bytes (SMB2 header) + 8 bytes (fixed part).
>+	 */
>+	resp_size = sizeof(struct smb2_query_directory_rsp);
>+	len = min_t(unsigned int, buflen, resp_size) - HEADER_SIZE(server) + 1;
>+
>+	length = cifs_read_from_socket(server,
>+				       buf + HEADER_SIZE(server) - 1, len);
>+	if (length < 0) {
>+		qd_io->result = length;
>+		return cifs_query_dir_discard(server, mid, false);
>+	}
>+	server->total_read += length;
>+
>+	if (server->ops->is_session_expired &&
>+	    server->ops->is_session_expired(buf)) {
>+		cifs_reconnect(server, true);
>+		return -1;
>+	}
>+
>+	if (server->ops->is_status_pending &&
>+	    server->ops->is_status_pending(buf, server)) {
>+		cifs_discard_remaining_data(server);
>+		return -1;
>+	}
>+
>+	/* Is there enough to get to the rest of the QUERY_DIRECTORY_RSP header? */
>+	if (server->total_read < resp_size) {
>+		cifs_dbg(FYI, "%s: server returned short header. got=%u expected=%u\n",
>+			 __func__, server->total_read, resp_size);
>+		qd_io->result = smb_EIO2(smb_eio_trace_read_rsp_short,
>+					 server->total_read, resp_size);
>+		return cifs_query_dir_discard(server, mid, true);
>+	}
>+
>+	/* Set up first iov for signature check and to get credits */
>+	qd_io->iov[0].iov_base = buf;
>+	qd_io->iov[0].iov_len = server->total_read;
>+	qd_io->iov[1].iov_base = NULL;
>+	qd_io->iov[1].iov_len = 0;
>+	cifs_dbg(FYI, "0: iov_base=%p iov_len=%zu\n",
>+		 qd_io->iov[0].iov_base, qd_io->iov[0].iov_len);
>+
>+	/* Parse header early to access status before map_error converts it */
>+	rsp = (struct smb2_query_directory_rsp *)buf;
>+
>+	/* Was the SMB query_directory successful? */
>+	qd_io->result = server->ops->map_error(buf, false);
>+	if (qd_io->result != 0) {
>+		if (qd_io->combined_iov.iov_base && qd_io->iov[0].iov_len > 0 &&
>+		    qd_io->iov[0].iov_len <= qd_io->combined_iov.iov_len) {
>+			memcpy(qd_io->combined_iov.iov_base, qd_io->iov[0].iov_base,
>+			       qd_io->iov[0].iov_len);
>+			qd_io->iov[0].iov_base = qd_io->combined_iov.iov_base;
>+		}
>+		cifs_dbg(FYI, "%s: server returned error %d (Status was 0x%x)\n",
>+			 __func__, qd_io->result, le32_to_cpu(rsp->hdr.Status));
>+		return cifs_query_dir_discard(server, mid, false);
>+	}
>+
>+	data_offset = le16_to_cpu(rsp->OutputBufferOffset);
>+	data_len = le32_to_cpu(rsp->OutputBufferLength);
>+
>+	cifs_dbg(FYI, "%s: total_read=%u data_offset=%u data_len=%u\n",
>+		 __func__, server->total_read, data_offset, data_len);
>+
>+	/* Validate data_offset and data_len */
>+	if (data_offset < server->total_read) {
>+		cifs_dbg(FYI, "%s: data offset (%u) inside response header\n",
>+			 __func__, data_offset);
>+		data_offset = server->total_read;
>+		data_len = 0;
>+	} else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) {
>+		cifs_dbg(FYI, "%s: data offset (%u) beyond end of smallbuf\n",
>+			 __func__, data_offset);
>+		qd_io->result = smb_EIO1(smb_eio_trace_read_overlarge,
>+					 data_offset);
>+		return cifs_query_dir_discard(server, mid, true);
>+	}
>+
>+	/* Read any padding between header and data */
>+	len = data_offset - server->total_read;
>+	if (len > 0) {
>+		length = cifs_read_from_socket(server,
>+					       buf + server->total_read, len);
>+		if (length < 0) {
>+			qd_io->result = length;
>+			return cifs_query_dir_discard(server, mid, false);
>+		}
>+		server->total_read += length;
>+		qd_io->iov[0].iov_len = server->total_read;
>+	}
>+
>+	/* Check if data fits in the pre-allocated combined buffer */
>+	if (qd_io->iov[0].iov_len + data_len > qd_io->combined_iov.iov_len) {
>+		cifs_dbg(VFS, "%s: response (%zu + %u) exceeds buffer capacity (%zu)\n",
>+			 __func__, qd_io->iov[0].iov_len, data_len, qd_io->combined_iov.iov_len);
>+		qd_io->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,
>+					 data_len, (unsigned int)qd_io->combined_iov.iov_len);
>+		return cifs_query_dir_discard(server, mid, true);
>+	}
>+
>+	/*
>+	 * Build combined header+data in qd_io->combined_iov.iov_base,
>+	 * preserving the wire layout so SMB2 signing can be verified against
>+	 * the exact on-the-wire bytes.  hdr_len already accounts for the
>+	 * header and any padding up to data_offset; data is read at that
>+	 * same offset.  OutputBufferOffset/Length are left untouched.
>+	 */
>+	if (qd_io->iov[0].iov_len > 0 && qd_io->result == 0) {
>+		size_t hdr_len = qd_io->iov[0].iov_len;
>+
>+		/* Copy header+padding to combined buffer, preserving wire layout */
>+		memcpy(qd_io->combined_iov.iov_base, qd_io->iov[0].iov_base, hdr_len);
>+
>+		/* Read directory entries at the wire data_offset position */
>+		if (data_len > 0) {
>+			length = cifs_read_from_socket(server,
>+						       qd_io->combined_iov.iov_base + hdr_len,
>+						       data_len);
>+			if (length < 0) {
>+				qd_io->result = length;
>+				return cifs_query_dir_discard(server, mid, false);
>+			}
>+			server->total_read += length;
>+
>+			/* Set up second iov pointing to the directory data within combined buffer */
>+			qd_io->iov[1].iov_base = qd_io->combined_iov.iov_base + hdr_len;
>+			qd_io->iov[1].iov_len = length;
>+		}
>+
>+		qd_io->combined_iov.iov_len = hdr_len + (data_len > 0 ? length : 0);
>+
>+		cifs_dbg(FYI, "total_read=%u buflen=%u data_len=%u hdr_len=%zu combined_len=%zu\n",
>+			 server->total_read, buflen, data_len, hdr_len, qd_io->combined_iov.iov_len);
>+	}
>+
>+	return cifs_query_dir_discard(server, mid, false);
>+}
>+
> int
> cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
> {
>-- 
>2.43.0
>
>

  reply	other threads:[~2026-04-28 17:00 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28 16:07 [PATCH v3 01/19] cifs: change_conf needs to be called for session setup nspmangalore
2026-04-28 16:07 ` [PATCH v3 02/19] cifs: abort open_cached_dir if we don't request leases nspmangalore
2026-04-30 19:16   ` Steve French
2026-04-28 16:07 ` [PATCH v3 03/19] cifs: invalidate cfid on unlink/rename/rmdir nspmangalore
2026-04-28 17:28   ` Paulo Alcantara
2026-05-01  9:00     ` Shyam Prasad N
2026-04-28 16:07 ` [PATCH v3 04/19] cifs: define variable sized buffer for querydir responses nspmangalore
2026-04-28 16:07 ` [PATCH v3 05/19] cifs: optimize readdir for small directories nspmangalore
2026-04-28 16:07 ` [PATCH v3 06/19] cifs: optimize readdir for larger directories nspmangalore
2026-04-28 17:00   ` Enzo Matsumiya [this message]
2026-04-28 16:07 ` [PATCH v3 07/19] cifs: reorganize cached dir helpers nspmangalore
2026-04-28 16:07 ` [PATCH v3 08/19] cifs: make cfid locks more granular nspmangalore
2026-04-28 17:18   ` Enzo Matsumiya
2026-05-01 10:20     ` Shyam Prasad N
2026-04-28 16:07 ` [PATCH v3 09/19] cifs: query dir should reuse cfid even if not fully cached nspmangalore
2026-04-28 16:07 ` [PATCH v3 10/19] cifs: back cached_dirents with page cache nspmangalore
2026-04-28 16:07 ` [PATCH v3 11/19] cifs: in place changes to cached_dirents when dir lease is held nspmangalore
2026-04-28 16:07 ` [PATCH v3 12/19] cifs: register a shrinker to manage cached_dirents nspmangalore
2026-04-28 16:07 ` [PATCH v3 13/19] cifs: option to disable time-based eviction of cache nspmangalore
2026-04-28 16:07 ` [PATCH v3 14/19] cifs: option to set unlimited number of cached dirs nspmangalore
2026-04-28 16:08 ` [PATCH v3 15/19] cifs: allow dcache population to happen asynchronously nspmangalore
2026-04-28 16:08 ` [PATCH v3 16/19] cifs: trace points for cached_dir operations nspmangalore
2026-04-28 16:08 ` [PATCH v3 17/19] cifs: discard functions should not return failure nspmangalore
2026-04-28 17:27   ` Enzo Matsumiya
2026-05-01  8:19     ` Shyam Prasad N
2026-04-28 16:08 ` [PATCH v3 18/19] cifs: keep cfids in rbtree for efficient lookups nspmangalore
2026-04-28 16:08 ` [PATCH v3 19/19] cifs: invalidate cached_dirents if population aborted nspmangalore

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=afDnSUDhehP7fyIF@suse.de \
    --to=ematsumiya@suse.de \
    --cc=bharathsm@microsoft.com \
    --cc=dhowells@redhat.com \
    --cc=henrique.carvalho@suse.com \
    --cc=linux-cifs@vger.kernel.org \
    --cc=nspmangalore@gmail.com \
    --cc=pc@manguebit.org \
    --cc=smfrench@gmail.com \
    --cc=sprasad@microsoft.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox