[{"id":3683595,"web_url":"http://patchwork.ozlabs.org/comment/3683595/","msgid":"<afDnSUDhehP7fyIF@suse.de>","list_archive_url":null,"date":"2026-04-28T17:00:02","subject":"Re: [PATCH v3 06/19] cifs: optimize readdir for larger directories","submitter":{"id":78375,"url":"http://patchwork.ozlabs.org/api/people/78375/","name":"Enzo Matsumiya","email":"ematsumiya@suse.de"},"content":"On 04/28, nspmangalore@gmail.com wrote:\n>From: Shyam Prasad N <sprasad@microsoft.com>\n>\n>Today QueryDirectory uses the compound_send_recv infrastructure\n>which is limited to 16KB in size. As a result, readdir of large\n>directories generally take several round trips.\n>\n>With this change, if the readdir needs a QueryDir after the first\n>round-trip (meaning that there are more dirents to read), then the\n>following QueryDirs will now switch to using larger buffers with\n>MTU credits.\n>\n>Till now, the only command type that used this flow was SMB2_READ.\n>In case of encrypted response, it becomes challenging to decide if\n>the response is for SMB2_READ or SMB2_READDIR. This change reuses\n>receive_encrypted_read and after decrypting the response decides\n>the handling function based on the command in the resp header. That\n>way, care has been taken to ensure that the read code path\n>modifications on account of this change are kept to a minimum.\n>\n>Cc: David Howells <dhowells@redhat.com>\n>Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>\n>---\n> fs/smb/client/cifsglob.h  |  21 +-\n> fs/smb/client/cifsproto.h |   3 +\n> fs/smb/client/readdir.c   |   2 +-\n> fs/smb/client/smb1ops.c   |   4 +-\n> fs/smb/client/smb2misc.c  |   7 +-\n> fs/smb/client/smb2ops.c   | 458 ++++++++++++++++++++++++++++++++++++--\n> fs/smb/client/smb2pdu.c   | 185 ++++++++++++++-\n> fs/smb/client/smb2pdu.h   |   3 +\n> fs/smb/client/smb2proto.h |   3 +\n> fs/smb/client/trace.h     |   1 +\n> fs/smb/client/transport.c | 170 ++++++++++++++\n> 11 files changed, 822 insertions(+), 35 deletions(-)\n>\n>diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h\n>index 8d089ba08e3e5..38d5600efe2c8 100644\n>--- a/fs/smb/client/cifsglob.h\n>+++ b/fs/smb/client/cifsglob.h\n>@@ -504,7 +504,7 @@ struct smb_version_operations {\n> \t\t\t       struct cifs_search_info *);\n> \t/* continue readdir */\n> \tint (*query_dir_next)(const unsigned int, struct cifs_tcon *,\n>-\t\t\t      struct cifs_fid *,\n>+\t\t\t      struct cifs_sb_info *, struct cifs_fid *,\n> \t\t\t      __u16, struct cifs_search_info *srch_inf);\n> \t/* close dir */\n> \tint (*close_dir)(const unsigned int, struct cifs_tcon *,\n>@@ -1397,6 +1397,25 @@ struct cifs_search_info {\n> \tbool is_dynamic_buf:1; /* dynamically allocated buffer - can be variable size */\n> };\n>\n>+/* Structure for QueryDirectory with multi-credit support */\n>+struct cifs_query_dir_io {\n>+\tstruct cifs_tcon *tcon;\n>+\tstruct TCP_Server_Info *server;\n>+\tstruct cifs_search_info *srch_inf;\n>+\tunsigned int xid;\n>+\tu64 persistent_fid;\n>+\tu64 volatile_fid;\n>+\tint index;\n>+\tstruct kvec combined_iov;\t/* Pre-allocated buffer to hold resp */\n>+\tstruct completion done;\n>+\tint result;\n>+\tstruct cifs_credits credits;\n>+\tbool replay;\n>+\tunsigned int retries;\n>+\tunsigned int cur_sleep;\n>+\tstruct kvec iov[2];\t\t/* For response handling */\n>+};\n>+\n> #define ACL_NO_MODE\t((umode_t)(-1))\n> struct cifs_open_parms {\n> \tstruct cifs_tcon *tcon;\n>diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h\n>index 884bfa1cf0b42..bbbee0ef09443 100644\n>--- a/fs/smb/client/cifsproto.h\n>+++ b/fs/smb/client/cifsproto.h\n>@@ -336,6 +336,9 @@ struct cifs_ses *cifs_get_smb_ses(struct TCP_Server_Info *server,\n> int cifs_readv_receive(struct TCP_Server_Info *server,\n> \t\t       struct mid_q_entry *mid);\n>\n>+int cifs_query_dir_receive(struct TCP_Server_Info *server,\n>+\t\t\t    struct mid_q_entry *mid);\n>+\n> int cifs_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon,\n> \t\t\t  struct cifs_sb_info *cifs_sb,\n> \t\t\t  const unsigned char *path, char *pbuf,\n>diff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c\n>index b50efd9b9e1d2..8a444f97e0ae9 100644\n>--- a/fs/smb/client/readdir.c\n>+++ b/fs/smb/client/readdir.c\n>@@ -760,7 +760,7 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,\n> \twhile ((index_to_find >= cfile->srch_inf.index_of_last_entry) &&\n> \t       (rc == 0) && !cfile->srch_inf.endOfSearch) {\n> \t\tcifs_dbg(FYI, \"calling findnext2\\n\");\n>-\t\trc = server->ops->query_dir_next(xid, tcon, &cfile->fid,\n>+\t\trc = server->ops->query_dir_next(xid, tcon, cifs_sb, &cfile->fid,\n> \t\t\t\t\t\t search_flags,\n> \t\t\t\t\t\t &cfile->srch_inf);\n> \t\tif (rc)\n>diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c\n>index 9694117050a6c..860a9b23a2f8d 100644\n>--- a/fs/smb/client/smb1ops.c\n>+++ b/fs/smb/client/smb1ops.c\n>@@ -1135,8 +1135,8 @@ cifs_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,\n>\n> static int\n> cifs_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon,\n>-\t\t    struct cifs_fid *fid, __u16 search_flags,\n>-\t\t    struct cifs_search_info *srch_inf)\n>+\t\t    struct cifs_sb_info *cifs_sb, struct cifs_fid *fid,\n>+\t\t    __u16 search_flags, struct cifs_search_info *srch_inf)\n> {\n> \treturn CIFSFindNext(xid, tcon, fid->netfid, search_flags, srch_inf);\n> }\n>diff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c\n>index 973fce3c959c4..b7b6ecd5fdaee 100644\n>--- a/fs/smb/client/smb2misc.c\n>+++ b/fs/smb/client/smb2misc.c\n>@@ -12,6 +12,7 @@\n> #include \"cifsglob.h\"\n> #include \"cifsproto.h\"\n> #include \"smb2proto.h\"\n>+#include \"smb2pdu.h\"\n> #include \"cifs_debug.h\"\n> #include \"cifs_unicode.h\"\n> #include \"../common/smb2status.h\"\n>@@ -316,7 +317,7 @@ char *\n> smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr)\n> {\n> \tconst int max_off = 4096;\n>-\tconst int max_len = 128 * 1024;\n>+\tint max_len = 128 * 1024;\n>\n> \t*off = 0;\n> \t*len = 0;\n>@@ -367,6 +368,10 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *shdr)\n> \t\t  ((struct smb2_query_directory_rsp *)shdr)->OutputBufferOffset);\n> \t\t*len = le32_to_cpu(\n> \t\t  ((struct smb2_query_directory_rsp *)shdr)->OutputBufferLength);\n>+\t\t/* Allow larger buffers for query directory (up to 2MB).\n>+\t\t * The actual data is handled separately in cifs_query_dir_receive().\n>+\t\t */\n>+\t\tmax_len = SMB2_MAX_QD_DATABUF_SIZE;\n> \t\tbreak;\n> \tcase SMB2_IOCTL:\n> \t\t*off = le32_to_cpu(\n>diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c\n>index f075330f88598..2df4d080e95f0 100644\n>--- a/fs/smb/client/smb2ops.c\n>+++ b/fs/smb/client/smb2ops.c\n>@@ -2713,11 +2713,131 @@ smb2_query_dir_first(const unsigned int xid, struct cifs_tcon *tcon,\n>\n> static int\n> smb2_query_dir_next(const unsigned int xid, struct cifs_tcon *tcon,\n>-\t\t    struct cifs_fid *fid, __u16 search_flags,\n>-\t\t    struct cifs_search_info *srch_inf)\n>+\t\t    struct cifs_sb_info *cifs_sb, struct cifs_fid *fid,\n>+\t\t    __u16 search_flags, struct cifs_search_info *srch_inf)\n> {\n>-\treturn SMB2_query_directory(xid, tcon, fid->persistent_fid,\n>-\t\t\t\t    fid->volatile_fid, 0, srch_inf);\n>+\tstruct cifs_query_dir_io qd_io;\n>+\tstruct TCP_Server_Info *server;\n>+\tstruct cifs_ses *ses = tcon->ses;\n>+\tsize_t buf_size;\n>+\tint rc;\n>+\tint resp_buftype = CIFS_DYNAMIC_BUFFER;\n>+\n>+\t/* Pick server and determine buffer size based on negotiated rsize */\n>+\tserver = cifs_pick_channel(ses);\n>+\tif (!server)\n>+\t\treturn smb_EIO(smb_eio_trace_null_pointers);\n>+\n>+\t/* Negotiate rsize if not already set */\n>+\tif (cifs_sb->ctx->rsize == 0)\n>+\t\tcifs_negotiate_rsize(server, cifs_sb->ctx, tcon);\n>+\n>+\t/* Use negotiated rsize for buffer size, with reasonable limits */\n>+\tbuf_size = cifs_sb->ctx->rsize;\n>+\n>+\tcifs_dbg(FYI, \"%s: using buffer size %zu (rsize=%u, encrypted=%d)\\n\",\n>+\t\t __func__, buf_size, cifs_sb->ctx->rsize, smb3_encryption_required(tcon));\n>+\n>+\t/* Initialize qd_io structure */\n>+\tmemset(&qd_io, 0, sizeof(qd_io));\n>+\tqd_io.tcon = tcon;\n>+\tqd_io.server = server;\n>+\tqd_io.srch_inf = srch_inf;\n>+\tqd_io.xid = xid;\n>+\tqd_io.persistent_fid = fid->persistent_fid;\n>+\tqd_io.volatile_fid = fid->volatile_fid;\n>+\tqd_io.index = 0;\n>+\tqd_io.result = 0;\n>+\tqd_io.replay = false;\n>+\tqd_io.retries = 0;\n>+\tqd_io.cur_sleep = 0;\n>+\n>+\t/* Allocate credits for the buffer size */\n>+\trc = server->ops->wait_mtu_credits(server, buf_size, &buf_size,\n>+\t\t\t\t\t   &qd_io.credits);\n>+\tif (rc) {\n>+\t\tcifs_dbg(VFS, \"%s: failed to get credits: %d\\n\", __func__, rc);\n>+\t\treturn rc;\n>+\t}\n>+\n>+\tcifs_dbg(FYI, \"%s: allocated %u credits for %zu bytes\\n\",\n>+\t\t __func__, qd_io.credits.value, buf_size);\n>+\n>+\t/* Send query directory with large buffer and wait for completion */\n>+\trc = SMB2_query_directory_large(&qd_io, buf_size);\n>+\tif (rc) {\n>+\t\tif (rc == -ENODATA) {\n>+\t\t\tconst struct smb2_hdr *hdr = NULL;\n>+\n>+\t\t\tif (qd_io.combined_iov.iov_base)\n>+\t\t\t\thdr = (const struct smb2_hdr *)qd_io.combined_iov.iov_base;\n>+\t\t\telse if (qd_io.iov[0].iov_base)\n>+\t\t\t\thdr = (const struct smb2_hdr *)qd_io.iov[0].iov_base;\n>+\n>+\t\t\t/*\n>+\t\t\t * ENODATA from QUERY_DIRECTORY generally means enumeration reached\n>+\t\t\t * the end. Treat it as end-of-search even if the header buffer is\n>+\t\t\t * unavailable in this async path.\n>+\t\t\t */\n>+\t\t\tif (!hdr) {\n>+\t\t\t\tcifs_dbg(FYI, \"%s: ENODATA but hdr is NULL\\n\", __func__);\n>+\t\t\t} else {\n>+\t\t\t\tcifs_dbg(FYI, \"%s: ENODATA with hdr->Status=0x%x (STATUS_NO_MORE_FILES=0x%x)\\n\",\n>+\t\t\t\t\t __func__, le32_to_cpu(hdr->Status), le32_to_cpu(STATUS_NO_MORE_FILES));\n>+\t\t\t}\n>+\n>+\t\t\tif (hdr && hdr->Status == STATUS_NO_MORE_FILES) {\n>+\t\t\t\ttrace_smb3_query_dir_done(xid, fid->persistent_fid,\n>+\t\t\t\t\ttcon->tid, tcon->ses->Suid, 0, 0);\n>+\t\t\t\tsrch_inf->endOfSearch = true;\n>+\t\t\t\trc = 0;\n>+\t\t\t} else {\n>+\t\t\t\tcifs_dbg(FYI, \"%s: ENODATA but Status mismatch - not treating as end-of-search\\n\",\n>+\t\t\t\t\t__func__);\n>+\t\t\t\ttrace_smb3_query_dir_err(xid, fid->persistent_fid,\n>+\t\t\t\t\ttcon->tid, tcon->ses->Suid, 0, 0, rc);\n>+\t\t\t}\n>+\t\t} else {\n>+\t\t\ttrace_smb3_query_dir_err(xid, fid->persistent_fid,\n>+\t\t\t\ttcon->tid, tcon->ses->Suid, 0, 0, rc);\n>+\t\t}\n>+\t\tgoto qdir_next_exit;\n>+\t}\n>+\n>+\t/* Parse the response using the combined buffer built in receive handler */\n>+\tif (qd_io.combined_iov.iov_len > 0) {\n>+\t\trc = smb2_parse_query_directory(tcon, &qd_io.combined_iov, resp_buftype,\n>+\t\t\t\t\t\tsrch_inf);\n>+\t\tif (rc) {\n>+\t\t\ttrace_smb3_query_dir_err(xid, fid->persistent_fid,\n>+\t\t\t\ttcon->tid, tcon->ses->Suid, 0, 0, rc);\n>+\t\t\tkfree(qd_io.combined_iov.iov_base);\n>+\t\t\tqd_io.combined_iov.iov_base = NULL;\n>+\t\t\tgoto qdir_next_exit;\n>+\t\t}\n>+\n>+\t\t/* combined_iov.iov_base ownership transferred to srch_inf->ntwrk_buf_start */\n>+\t\tqd_io.combined_iov.iov_base = NULL;\n>+\n>+\t\ttrace_smb3_query_dir_done(xid, fid->persistent_fid,\n>+\t\t\ttcon->tid, tcon->ses->Suid, 0,\n>+\t\t\tsrch_inf->entries_in_buffer);\n>+\t}\n>+\n>+qdir_next_exit:\n>+\t/* Free the data buffer if not transferred to srch_inf */\n>+\tkfree(qd_io.combined_iov.iov_base);\n>+\n>+\t/* Return credits if we still have them (they should have been cleared in callback) */\n>+\tif (qd_io.credits.value != 0) {\n>+\t\ttrace_smb3_rw_credits(0, 0, 0,\n>+\t\t\t\t      server->credits, server->in_flight,\n>+\t\t\t\t      qd_io.credits.value,\n>+\t\t\t\t      cifs_trace_rw_credits_query_dir_done);\n>+\t\tadd_credits(server, &qd_io.credits, 0);\n>+\t}\n>+\n>+\treturn rc;\n> }\n>\n> static int\n>@@ -4834,6 +4954,252 @@ cifs_copy_folioq_to_iter(struct folio_queue *folioq, size_t data_size,\n> \treturn 0;\n> }\n>\n>+static int\n>+cifs_copy_folioq_to_buf(struct folio_queue *folioq, size_t total_size,\n>+\t\t\tsize_t skip, char *buf, size_t buf_len)\n>+{\n>+\tsize_t copied = 0;\n>+\n>+\tif (buf_len > total_size - skip)\n>+\t\tbuf_len = total_size - skip;\n>+\n>+\tfor (; folioq; folioq = folioq->next) {\n>+\t\tfor (int s = 0; s < folioq_count(folioq); s++) {\n>+\t\t\tstruct folio *folio = folioq_folio(folioq, s);\n>+\t\t\tsize_t fsize = folio_size(folio);\n>+\t\t\tsize_t len = umin(fsize - skip, buf_len);\n>+\n>+\t\t\tif (len == 0)\n>+\t\t\t\tbreak;\n>+\n>+\t\t\tmemcpy_from_folio(buf + copied, folio, skip, len);\n>+\t\t\tcopied += len;\n>+\t\t\tbuf_len -= len;\n>+\t\t\tskip = 0;\n>+\n>+\t\t\tif (buf_len == 0)\n>+\t\t\t\treturn 0;\n>+\t\t}\n>+\t}\n>+\n>+\treturn 0;\n>+}\n>+\n>+/*\n>+ * Handle encrypted QueryDirectory response data.\n>+ * Called only for encrypted responses where mid->decrypted == true.\n>+ * For unencrypted responses, cifs_query_dir_receive handles everything.\n>+ *\n>+ * This is written in such a way that handle_read_data does not need modification.\n>+ *\n>+ * buf: contains read_rsp_size bytes of decrypted response\n>+ * buffer: contains (total_len - read_rsp_size) bytes of decrypted data\n>+ *\n>+ * Since sizeof(struct smb2_query_directory_rsp) < read_rsp_size,\n>+ * the response header is always fully in buf. Data may be split between\n>+ * buf and buffer depending on data_offset.\n>+ */\n>+static int\n>+handle_query_dir_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,\n>+\t\t      char *buf, unsigned int buf_len, struct folio_queue *buffer,\n>+\t\t      unsigned int buffer_len, bool is_offloaded)\n>+{\n>+\tstruct cifs_query_dir_io *qd_io = mid->callback_data;\n>+\tstruct smb2_query_directory_rsp *rsp;\n>+\tstruct smb2_hdr *shdr = (struct smb2_hdr *)buf;\n>+\tunsigned int data_offset, data_len;\n>+\tunsigned int hdr_len;\n>+\n>+\tcifs_dbg(FYI, \"%s: processing encrypted QueryDirectory response\\n\", __func__);\n>+\n>+\tif (shdr->Command != SMB2_QUERY_DIRECTORY) {\n>+\t\tcifs_server_dbg(VFS, \"only QueryDirectory responses are supported\\n\");\n>+\t\treturn -EOPNOTSUPP;\n>+\t}\n>+\n>+\tif (server->ops->is_session_expired &&\n>+\t    server->ops->is_session_expired(buf)) {\n>+\t\tif (!is_offloaded)\n>+\t\t\tcifs_reconnect(server, true);\n>+\t\treturn -1;\n>+\t}\n>+\n>+\tif (server->ops->is_status_pending &&\n>+\t\t\tserver->ops->is_status_pending(buf, server))\n>+\t\treturn -1;\n>+\n>+\trsp = (struct smb2_query_directory_rsp *)buf;\n>+\thdr_len = min_t(unsigned int, buf_len,\n>+\t\t\t sizeof(struct smb2_query_directory_rsp));\n>+\n>+\t/* Map error code first */\n>+\tqd_io->result = server->ops->map_error(buf, false);\n>+\n>+\t/* Get data_offset early to set up iov properly */\n>+\tdata_offset = le16_to_cpu(rsp->OutputBufferOffset);\n>+\tdata_len = le32_to_cpu(rsp->OutputBufferLength);\n>+\n>+\t/* Set up first iov to point to header portion (needed for credits/signature) */\n>+\tqd_io->iov[0].iov_base = buf;\n>+\tqd_io->iov[0].iov_len = qd_io->result ? hdr_len : data_offset;\n>+\n>+\tif (qd_io->result != 0) {\n>+\t\tcifs_dbg(FYI, \"%s: server returned error %d (Status=0x%x)\\n\",\n>+\t\t\t __func__, qd_io->result, le32_to_cpu(rsp->hdr.Status));\n>+\n>+\t\t/*\n>+\t\t * Copy header to persistent combined_iov buffer so status\n>+\t\t * remains accessible after receive handler returns. buf is temporary\n>+\t\t * and will be freed/reused, so we can't leave iov[0] pointing to it\n>+\t\t */\n>+\t\tif (qd_io->combined_iov.iov_base && hdr_len > 0 &&\n>+\t\t    hdr_len <= qd_io->combined_iov.iov_len) {\n>+\t\t\tmemcpy(qd_io->combined_iov.iov_base, buf, hdr_len);\n>+\t\t\tqd_io->iov[0].iov_base = qd_io->combined_iov.iov_base;\n>+\t\t\tqd_io->iov[0].iov_len = hdr_len;\n>+\t\t\tcifs_dbg(FYI, \"%s: copied error response header to combined_iov\\n\",\n>+\t\t\t\t__func__);\n>+\t\t}\n>+\n>+\t\t/*\n>+\t\t * Normal error on query_directory response - response received successfully,\n>+\t\t * but the command failed. Store error in qd_io->result for callback\n>+\t\t */\n>+\t\tif (is_offloaded)\n>+\t\t\tmid->mid_state = MID_RESPONSE_RECEIVED;\n>+\t\telse\n>+\t\t\tdequeue_mid(server, mid, false);\n>+\t\treturn 0;\n>+\t}\n>+\n>+\t/* Success - parse the response data */\n>+\tcifs_dbg(FYI, \"%s: data_offset=%u data_len=%u buf_len=%u buffer_len=%u\\n\",\n>+\t\t __func__, data_offset, data_len, buf_len, buffer_len);\n>+\n>+\t/* Validate data_offset */\n>+\tif (data_offset < sizeof(struct smb2_query_directory_rsp)) {\n>+\t\tcifs_dbg(FYI, \"%s: data offset (%u) inside response header\\n\",\n>+\t\t\t __func__, data_offset);\n>+\t\tdata_offset = sizeof(struct smb2_query_directory_rsp);\n>+\t} else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) {\n>+\t\tcifs_dbg(VFS, \"%s: data offset (%u) beyond end of smallbuf\\n\",\n>+\t\t\t __func__, data_offset);\n>+\t\tqd_io->result = -EIO;\n>+\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\treturn qd_io->result;\n>+\t}\n>+\n>+\t/* Validate data_offset is within buf_len + buffer_len */\n>+\tif (data_offset > buf_len + buffer_len) {\n>+\t\tcifs_dbg(VFS, \"%s: data offset (%u) beyond response length (%u)\\n\",\n>+\t\t\t __func__, data_offset, buf_len + buffer_len);\n>+\t\tqd_io->result = -EIO;\n>+\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\treturn qd_io->result;\n>+\t}\n>+\n>+\t/* Validate response fits in pre-allocated combined buffer */\n>+\tif ((size_t)data_offset + data_len > qd_io->combined_iov.iov_len) {\n>+\t\tcifs_dbg(VFS, \"%s: response (%u + %u) exceeds buffer capacity (%zu)\\n\",\n>+\t\t\t __func__, data_offset, data_len, qd_io->combined_iov.iov_len);\n>+\t\tqd_io->result = -EIO;\n>+\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\treturn qd_io->result;\n>+\t}\n>+\n>+\t/* Copy the prefix present in buf into combined_iov, preserving wire layout */\n>+\tmemcpy(qd_io->combined_iov.iov_base, buf, min(data_offset, buf_len));\n>+\n>+\tif (data_offset < buf_len) {\n>+\t\t/* Data starts in buf, may continue into buffer */\n>+\t\tunsigned int data_in_buf = buf_len - data_offset;\n>+\t\tunsigned int data_in_buffer;\n>+\n>+\t\tif (data_len <= data_in_buf) {\n>+\t\t\t/* All data is in buf */\n>+\t\t\tmemcpy(qd_io->combined_iov.iov_base + data_offset,\n>+\t\t\t       buf + data_offset, data_len);\n>+\t\t} else {\n>+\t\t\t/* Copy from buf first */\n>+\t\t\tmemcpy(qd_io->combined_iov.iov_base + data_offset,\n>+\t\t\t       buf + data_offset, data_in_buf);\n>+\n>+\t\t\t/* Copy remainder from buffer at offset 0 */\n>+\t\t\tdata_in_buffer = data_len - data_in_buf;\n>+\t\t\tif (data_in_buffer > buffer_len) {\n>+\t\t\t\tcifs_dbg(VFS, \"%s: data_in_buffer (%u) > buffer_len (%u)\\n\",\n>+\t\t\t\t\t __func__, data_in_buffer, buffer_len);\n>+\t\t\t\tqd_io->result = -EIO;\n>+\t\t\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\t\t\treturn qd_io->result;\n>+\t\t\t}\n>+\n>+\t\t\tqd_io->result = cifs_copy_folioq_to_buf(buffer, buffer_len, 0,\n>+\t\t\t\t\t\t\t\tqd_io->combined_iov.iov_base +\n>+\t\t\t\t\t\t\t\tdata_offset + data_in_buf,\n>+\t\t\t\t\t\t\t\tdata_in_buffer);\n>+\t\t\tif (qd_io->result != 0) {\n>+\t\t\t\tcifs_dbg(VFS, \"%s: failed to copy from folio_queue: %d\\n\",\n>+\t\t\t\t\t __func__, qd_io->result);\n>+\t\t\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\t\t\treturn qd_io->result;\n>+\t\t\t}\n>+\t\t}\n>+\t} else {\n>+\t\t/* Padding and data are in buffer starting at offset 0 */\n>+\t\tunsigned int bytes_in_buffer = data_offset - buf_len + data_len;\n>+\n>+\t\tif (bytes_in_buffer > buffer_len) {\n>+\t\t\tcifs_dbg(VFS, \"%s: data beyond buffer: prefix+len=%u buffer_len=%u\\n\",\n>+\t\t\t\t __func__, bytes_in_buffer, buffer_len);\n>+\t\t\tqd_io->result = -EIO;\n>+\t\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\t\treturn qd_io->result;\n>+\t\t}\n>+\n>+\t\tqd_io->result = cifs_copy_folioq_to_buf(buffer, buffer_len, 0,\n>+\t\t\t\t\t\t\tqd_io->combined_iov.iov_base + buf_len,\n>+\t\t\t\t\t\t\tbytes_in_buffer);\n>+\t\tif (qd_io->result != 0) {\n>+\t\t\tcifs_dbg(VFS, \"%s: failed to copy from folio_queue: %d\\n\",\n>+\t\t\t\t __func__, qd_io->result);\n>+\t\t\tdequeue_mid(server, mid, qd_io->result);\n>+\t\t\treturn qd_io->result;\n>+\t\t}\n>+\t}\n>+\n>+\t/* Set up iov[1] pointing into combined buffer, finalize valid length */\n>+\tqd_io->iov[1].iov_base = qd_io->combined_iov.iov_base + data_offset;\n>+\tqd_io->iov[1].iov_len = data_len;\n>+\tqd_io->combined_iov.iov_len = data_offset + data_len;\n>+\n>+\tdequeue_mid(server, mid, false);\n>+\treturn 0;\n>+}\n>+\n>+/*\n>+ * Handle callback for async QueryDirectory with multi-credit support.\n>+ * For encrypted responses, extracts decrypted data.\n>+ * For unencrypted responses, cifs_query_dir_receive already processed everything.\n>+ */\n>+int\n>+smb2_query_dir_handle_data(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n>+{\n>+\tchar *buf = server->large_buf ? server->bigbuf : server->smallbuf;\n>+\n>+\t/* For unencrypted responses, data already processed in cifs_query_dir_receive */\n>+\tif (!mid->decrypted)\n>+\t\treturn 0;\n>+\n>+\t/*\n>+\t * For small encrypted responses (< CIFSMaxBufSize), all data is in buf.\n>+\t * For large encrypted responses, this callback is not used - instead,\n>+\t * receive_encrypted_read/smb2_decrypt_offload call handle_query_dir_data directly.\n>+\t */\n>+\treturn handle_query_dir_data(server, mid, buf, server->pdu_size,\n>+\t\t\t\t      NULL, 0, false);\n>+}\n>+\n> static int\n> handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,\n> \t\t char *buf, unsigned int buf_len, struct folio_queue *buffer,\n>@@ -4997,25 +5363,46 @@ static void smb2_decrypt_offload(struct work_struct *work)\n> \tint rc;\n> \tstruct mid_q_entry *mid;\n> \tstruct iov_iter iter;\n>+\tstruct smb2_hdr *shdr;\n>+\tunsigned int read_rsp_size = dw->server->vals->read_rsp_size;\n>\n>+\t/* decrypt read_rsp_size in buf + remainder in folio_queue */\n> \tiov_iter_folio_queue(&iter, ITER_DEST, dw->buffer, 0, 0, dw->len);\n>-\trc = decrypt_raw_data(dw->server, dw->buf, dw->server->vals->read_rsp_size,\n>-\t\t\t      &iter, true);\n>+\trc = decrypt_raw_data(dw->server, dw->buf, read_rsp_size, &iter, true);\n> \tif (rc) {\n> \t\tcifs_dbg(VFS, \"error decrypting rc=%d\\n\", rc);\n> \t\tgoto free_pages;\n> \t}\n>\n> \tdw->server->lstrp = jiffies;\n>+\n>+\tshdr = (struct smb2_hdr *)(dw->buf + sizeof(struct smb2_transform_hdr));\n>+\n>+\t/*\n>+\t * buf now contains read_rsp_size bytes after transform_hdr.\n>+\t * folio_queue contains (total_len - read_rsp_size) bytes.\n>+\t * The handle functions will determine where data actually starts based on data_offset.\n>+\t */\n>+\n> \tmid = smb2_find_dequeue_mid(dw->server, dw->buf);\n> \tif (mid == NULL)\n> \t\tcifs_dbg(FYI, \"mid not found\\n\");\n> \telse {\n> \t\tmid->decrypted = true;\n>-\t\trc = handle_read_data(dw->server, mid, dw->buf,\n>-\t\t\t\t      dw->server->vals->read_rsp_size,\n>-\t\t\t\t      dw->buffer, dw->len,\n>-\t\t\t\t      true);\n>+\n>+\t\t/* Handle based on command type */\n>+\t\tif (le16_to_cpu(shdr->Command) == SMB2_READ) {\n>+\t\t\trc = handle_read_data(dw->server, mid, dw->buf, read_rsp_size,\n>+\t\t\t\t\t      dw->buffer, dw->len, true);\n>+\t\t} else if (le16_to_cpu(shdr->Command) == SMB2_QUERY_DIRECTORY) {\n>+\t\t\trc = handle_query_dir_data(dw->server, mid, dw->buf, read_rsp_size,\n>+\t\t\t\t\t\t   dw->buffer, dw->len, true);\n>+\t\t} else {\n>+\t\t\tcifs_dbg(VFS, \"Unexpected command %u in decrypt offload\\n\",\n>+\t\t\t\t le16_to_cpu(shdr->Command));\n>+\t\t\trc = -EOPNOTSUPP;\n>+\t\t}\n>+\n> \t\tif (rc >= 0) {\n> #ifdef CONFIG_CIFS_STATS2\n> \t\t\tmid->when_received = jiffies;\n>@@ -5059,9 +5446,10 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,\n> {\n> \tchar *buf = server->smallbuf;\n> \tstruct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf;\n>+\tstruct smb2_hdr *shdr;\n> \tstruct iov_iter iter;\n>-\tunsigned int len;\n>-\tunsigned int buflen = server->pdu_size;\n>+\tunsigned int len, total_len, buflen = server->pdu_size;\n>+\tunsigned int read_rsp_size = server->vals->read_rsp_size;\n> \tint rc;\n> \tstruct smb2_decrypt_work *dw;\n>\n>@@ -5072,7 +5460,10 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,\n> \tdw->server = server;\n>\n> \t*num_mids = 1;\n>-\tlen = min_t(unsigned int, buflen, server->vals->read_rsp_size +\n>+\ttotal_len = le32_to_cpu(tr_hdr->OriginalMessageSize);\n>+\n>+\t/* Read transform_hdr + read_rsp_size into buf */\n>+\tlen = min_t(unsigned int, buflen, read_rsp_size +\n> \t\tsizeof(struct smb2_transform_hdr)) - HEADER_SIZE(server) + 1;\n>\n> \trc = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1, len);\n>@@ -5080,9 +5471,8 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,\n> \t\tgoto free_dw;\n> \tserver->total_read += rc;\n>\n>-\tlen = le32_to_cpu(tr_hdr->OriginalMessageSize) -\n>-\t\tserver->vals->read_rsp_size;\n>-\tdw->len = len;\n>+\t/* Read remaining data into folio_queue */\n>+\tdw->len = total_len - read_rsp_size;\n> \tlen = round_up(dw->len, PAGE_SIZE);\n>\n> \tsize_t cur_size = 0;\n>@@ -5111,7 +5501,7 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,\n> \t\tgoto free_pages;\n>\n> \t/*\n>-\t * For large reads, offload to different thread for better performance,\n>+\t * For large responses, offload to different thread for better performance,\n> \t * use more cores decrypting which can be expensive\n> \t */\n>\n>@@ -5125,20 +5515,41 @@ receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid,\n> \t\treturn -1;\n> \t}\n>\n>-\trc = decrypt_raw_data(server, buf, server->vals->read_rsp_size,\n>-\t\t\t      &iter, false);\n>+\t/* Decrypt: read_rsp_size in buf + remainder in folio_queue */\n>+\trc = decrypt_raw_data(server, buf, read_rsp_size, &iter, false);\n> \tif (rc)\n> \t\tgoto free_pages;\n>\n>+\tshdr = (struct smb2_hdr *) buf;\n>+\n>+\t/*\n>+\t * buf now contains the complete response header (read_rsp_size bytes).\n>+\t * folio_queue contains (total_len - read_rsp_size) bytes.\n>+\t * The handle functions will determine where data actually starts based on data_offset.\n>+\t */\n>+\n> \t*mid = smb2_find_mid(server, buf);\n> \tif (*mid == NULL) {\n> \t\tcifs_dbg(FYI, \"mid not found\\n\");\n> \t} else {\n>-\t\tcifs_dbg(FYI, \"mid found\\n\");\n>+\t\tcifs_dbg(FYI, \"mid found, command=%u\\n\", le16_to_cpu(shdr->Command));\n> \t\t(*mid)->decrypted = true;\n>-\t\trc = handle_read_data(server, *mid, buf,\n>-\t\t\t\t      server->vals->read_rsp_size,\n>-\t\t\t\t      dw->buffer, dw->len, false);\n>+\n>+\t\t/* Handle based on command type */\n>+\t\tif (le16_to_cpu(shdr->Command) == SMB2_READ) {\n>+\t\t\trc = handle_read_data(server, *mid, buf, read_rsp_size,\n>+\t\t\t\t\t      dw->buffer, dw->len, false);\n>+\t\t} else if (le16_to_cpu(shdr->Command) == SMB2_QUERY_DIRECTORY) {\n>+\t\t\trc = handle_query_dir_data(server, *mid, buf, read_rsp_size,\n>+\t\t\t\t\t\t   dw->buffer, dw->len, false);\n>+\t\t} else {\n>+\t\t\t/* For now, other commands not supported in large encrypted path */\n>+\t\t\tcifs_server_dbg(VFS,\n>+\t\t\t\t\t\"Large encrypted responses only supported for SMB2_READ and SMB2_QUERY_DIRECTORY (got %u)\\n\",\n>+\t\t\t\t\tle16_to_cpu(shdr->Command));\n>+\t\t\trc = -EOPNOTSUPP;\n>+\t\t}\n>+\n> \t\tif (rc >= 0) {\n> \t\t\tif (server->ops->is_network_name_deleted) {\n> \t\t\t\tserver->ops->is_network_name_deleted(buf,\n>@@ -5279,7 +5690,6 @@ smb3_receive_transform(struct TCP_Server_Info *server,\n> \t\treturn -ECONNABORTED;\n> \t}\n>\n>-\t/* TODO: add support for compounds containing READ. */\n> \tif (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) {\n> \t\treturn receive_encrypted_read(server, &mids[0], num_mids);\n> \t}\n>diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c\n>index 2d55246d2851b..92724cfb5b3f5 100644\n>--- a/fs/smb/client/smb2pdu.c\n>+++ b/fs/smb/client/smb2pdu.c\n>@@ -5496,6 +5496,185 @@ num_entries(int infotype, char *bufstart, char *end_of_buf, char **lastentry,\n> \treturn entrycount;\n> }\n>\n>+/*\n>+ * Callback for async QueryDirectory with multi-credit support\n>+ */\n>+static void\n>+smb2_query_dir_callback(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n>+{\n>+\tstruct cifs_query_dir_io *qd_io = mid->callback_data;\n>+\tstruct cifs_tcon *tcon = qd_io->tcon;\n>+\tstruct smb2_hdr *shdr = (struct smb2_hdr *)qd_io->iov[0].iov_base;\n>+\tstruct cifs_credits credits = {\n>+\t\t.value = 0,\n>+\t\t.instance = 0,\n>+\t};\n>+\n>+\tWARN_ONCE(qd_io->server != server,\n>+\t\t  \"qd_io server %p != mid server %p\",\n>+\t\t  qd_io->server, server);\n>+\n>+\tcifs_dbg(FYI, \"%s: mid=%llu state=%d result=%d\\n\",\n>+\t\t __func__, mid->mid, mid->mid_state, qd_io->result);\n>+\n>+\tswitch (mid->mid_state) {\n>+\tcase MID_RESPONSE_RECEIVED:\n>+\t\tcredits.value = le16_to_cpu(shdr->CreditRequest);\n>+\t\tcredits.instance = server->reconnect_instance;\n>+\t\t/* result already set, check signature if needed */\n>+\t\tif (server->sign && !mid->decrypted) {\n>+\t\t\tint rc;\n>+\t\t\tstruct smb_rqst rqst = {\n>+\t\t\t\t.rq_iov = &qd_io->iov[0],\n>+\t\t\t\t.rq_nvec = qd_io->iov[1].iov_len ? 2 : 1,\n>+\t\t\t};\n>+\n>+\t\t\trc = smb2_verify_signature(&rqst, server);\n>+\t\t\tif (rc) {\n>+\t\t\t\tcifs_tcon_dbg(VFS, \"QueryDir signature verification returned error = %d\\n\",\n>+\t\t\t\t\t      rc);\n>+\t\t\t\tqd_io->result = rc;\n>+\t\t\t}\n>+\t\t}\n>+\t\tbreak;\n>+\tcase MID_REQUEST_SUBMITTED:\n>+\tcase MID_RETRY_NEEDED:\n>+\t\tqd_io->result = -EAGAIN;\n>+\t\tbreak;\n>+\tcase MID_RESPONSE_MALFORMED:\n>+\t\tcredits.value = le16_to_cpu(shdr->CreditRequest);\n>+\t\tcredits.instance = server->reconnect_instance;\n>+\t\tqd_io->result = smb_EIO(smb_eio_trace_read_rsp_malformed);\n>+\t\tbreak;\n>+\tdefault:\n>+\t\tqd_io->result = smb_EIO1(smb_eio_trace_read_mid_state_unknown,\n>+\t\t\t\t\t mid->mid_state);\n>+\t\tbreak;\n>+\t}\n>+\n>+\tif (qd_io->result && qd_io->result != -ENODATA)\n>+\t\tcifs_stats_fail_inc(tcon, SMB2_QUERY_DIRECTORY_HE);\n>+\n>+\ttrace_smb3_rw_credits(0, 0, qd_io->credits.value,\n>+\t\t\t      server->credits, server->in_flight,\n>+\t\t\t      0, cifs_trace_rw_credits_read_response_clear);\n>+\tqd_io->credits.value = 0;\n>+\trelease_mid(server, mid);\n>+\ttrace_smb3_rw_credits(0, 0, 0,\n>+\t\t\t      server->credits, server->in_flight,\n>+\t\t\t      credits.value, cifs_trace_rw_credits_read_response_add);\n>+\tadd_credits(server, &credits, 0);\n>+\n>+\tcomplete(&qd_io->done);\n>+}\n>+\n>+/*\n>+ * QueryDirectory with large buffer and multi-credit support.\n>+ * Uses async infrastructure but waits for completion synchronously.\n>+ */\n>+int\n>+SMB2_query_directory_large(struct cifs_query_dir_io *qd_io, unsigned int buf_size)\n>+{\n>+\tint rc, flags = 0;\n>+\tchar *buf;\n>+\tstruct smb2_hdr *shdr;\n>+\tstruct smb_rqst rqst = { .rq_iov = &qd_io->iov[0],\n>+\t\t\t\t .rq_nvec = SMB2_QUERY_DIRECTORY_IOV_SIZE };\n>+\tstruct TCP_Server_Info *server = qd_io->server;\n>+\tstruct cifs_tcon *tcon = qd_io->tcon;\n>+\tunsigned int total_len;\n>+\tint credit_request;\n>+\n>+\tcifs_dbg(FYI, \"%s: buf_size=%u\\n\", __func__, buf_size);\n>+\n>+\t/* Cap buffer size to avoid kmalloc failures for very large allocations.\n>+\t * SMB2_MAX_QD_DATABUF_SIZE is a safe limit that stays well below typical\n>+\t * kmalloc constraints while still allowing large directory listings.\n>+\t */\n>+\tif (buf_size > SMB2_MAX_QD_DATABUF_SIZE)\n>+\t\tbuf_size = SMB2_MAX_QD_DATABUF_SIZE;\n>+\n>+\t/* Allocate response buffer. Since we'll build a combined header+data buffer,\n>+\t * we need space for both. We'll request a slightly smaller OutputBufferLength\n>+\t * from the server to ensure the total response fits.\n>+\t */\n>+\tqd_io->combined_iov.iov_base = kmalloc(buf_size, GFP_KERNEL);\n>+\tif (!qd_io->combined_iov.iov_base)\n>+\t\treturn -ENOMEM;\n>+\t/* Store total capacity in iov_len; updated to actual data length by receive handler */\n>+\tqd_io->combined_iov.iov_len = buf_size;\n>+\n>+\t/* Initialize completion */\n>+\tinit_completion(&qd_io->done);\n>+\n>+\t/* Request less data from server to leave room for the response header.\n>+\t * Use MAX_CIFS_SMALL_BUFFER_SIZE as a safety margin.\n>+\t */\n>+\trc = SMB2_query_directory_init(qd_io->xid, tcon, server,\n>+\t\t\t\t       &rqst, qd_io->persistent_fid,\n>+\t\t\t\t       qd_io->volatile_fid, qd_io->index,\n>+\t\t\t\t       qd_io->srch_inf->info_level,\n>+\t\t\t\t       buf_size - MAX_CIFS_SMALL_BUFFER_SIZE);\n>+\tif (rc) {\n>+\t\tkfree(qd_io->combined_iov.iov_base);\n>+\t\tqd_io->combined_iov.iov_base = NULL;\n>+\t\treturn rc;\n>+\t}\n>+\n>+\tif (smb3_encryption_required(tcon))\n>+\t\tflags |= CIFS_TRANSFORM_REQ;\n>+\n>+\tbuf = rqst.rq_iov[0].iov_base;\n>+\ttotal_len = rqst.rq_iov[0].iov_len;\n>+\n>+\tshdr = (struct smb2_hdr *)buf;\n>+\n>+\tif (qd_io->replay) {\n>+\t\t/* Back-off before retry */\n>+\t\tif (qd_io->cur_sleep)\n>+\t\t\tmsleep(qd_io->cur_sleep);\n>+\t\tsmb2_set_replay(server, &rqst);\n>+\t}\n>+\n>+\t/* Set credit charge based on buffer size */\n>+\tif (qd_io->credits.value > 0) {\n>+\t\tshdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(buf_size,\n>+\t\t\t\t\t\tSMB2_MAX_BUFFER_SIZE));\n>+\t\tcredit_request = le16_to_cpu(shdr->CreditCharge) + 8;\n>+\t\tif (server->credits >= server->max_credits)\n>+\t\t\tshdr->CreditRequest = cpu_to_le16(0);\n>+\t\telse\n>+\t\t\tshdr->CreditRequest = cpu_to_le16(\n>+\t\t\t\tmin_t(int, server->max_credits -\n>+\t\t\t\t\t\tserver->credits, credit_request));\n>+\n>+\t\tflags |= CIFS_HAS_CREDITS;\n>+\t}\n>+\n>+\trc = cifs_call_async(server, &rqst,\n>+\t\t\t     cifs_query_dir_receive, smb2_query_dir_callback,\n>+\t\t\t     smb2_query_dir_handle_data, qd_io, flags,\n>+\t\t\t     &qd_io->credits);\n>+\tif (rc) {\n>+\t\tcifs_stats_fail_inc(tcon, SMB2_QUERY_DIRECTORY_HE);\n>+\t\ttrace_smb3_query_dir_err(qd_io->xid, qd_io->persistent_fid,\n>+\t\t\t\t\t tcon->tid, tcon->ses->Suid,\n>+\t\t\t\t\t qd_io->index, 0, rc);\n>+\t\tkfree(qd_io->combined_iov.iov_base);\n>+\t\tqd_io->combined_iov.iov_base = NULL;\n>+\t}\n>+\n>+\t/* Free request buffer immediately after async call */\n>+\tcifs_small_buf_release(buf);\n>+\n>+\tif (rc)\n>+\t\treturn rc;\n>+\n>+\t/* Wait for the async operation to complete */\n>+\twait_for_completion(&qd_io->done);\n>+\treturn qd_io->result;\n>+}\n>+\n> /*\n>  * Readdir/FindFirst\n>  */\n>@@ -5560,12 +5739,6 @@ int SMB2_query_directory_init(const unsigned int xid,\n> \treq->FileNameOffset =\n> \t\tcpu_to_le16(sizeof(struct smb2_query_directory_req));\n> \treq->FileNameLength = cpu_to_le16(len);\n>-\t/*\n>-\t * BB could be 30 bytes or so longer if we used SMB2 specific\n>-\t * buffer lengths, but this is safe and close enough.\n>-\t */\n>-\toutput_size = min_t(unsigned int, output_size, server->maxBuf);\n>-\toutput_size = min_t(unsigned int, output_size, 2 << 15);\n> \treq->OutputBufferLength = cpu_to_le32(output_size);\n>\n> \tiov[0].iov_base = (char *)req;\n>diff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h\n>index 7b7a864520c68..843e30c1ecc61 100644\n>--- a/fs/smb/client/smb2pdu.h\n>+++ b/fs/smb/client/smb2pdu.h\n>@@ -132,6 +132,9 @@ struct share_redirect_error_context_rsp {\n> /* Size of the minimal QueryDir response for checking if more data exists */\n> #define SMB2_QD2_RESPONSE_SIZE 4096\n>\n>+/* max query directory data buffer size */\n>+#define SMB2_MAX_QD_DATABUF_SIZE (2 * 1024 * 1024)\n>+\n> /*\n>  * Output buffer size for first QueryDir in Create+QD1+QD2 compound.\n>  * Accounts for shared buffer space needed for all three responses.\n>diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h\n>index 9de7d2fe8d466..9607e8899f7ff 100644\n>--- a/fs/smb/client/smb2proto.h\n>+++ b/fs/smb/client/smb2proto.h\n>@@ -46,6 +46,8 @@ __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode, unsigned int oplock);\n> bool smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server);\n> int smb3_handle_read_data(struct TCP_Server_Info *server,\n> \t\t\t  struct mid_q_entry *mid);\n>+int smb2_query_dir_handle_data(struct TCP_Server_Info *server,\n>+\t\t\t       struct mid_q_entry *mid);\n> struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data,\n> \t\t\t\t\tstruct super_block *sb,\n> \t\t\t\t\tconst unsigned int xid,\n>@@ -197,6 +199,7 @@ int SMB2_query_directory_init(const unsigned int xid, struct cifs_tcon *tcon,\n> \t\t\t      u64 volatile_fid, int index, int info_level,\n> \t\t\t      unsigned int output_size);\n> void SMB2_query_directory_free(struct smb_rqst *rqst);\n>+int SMB2_query_directory_large(struct cifs_query_dir_io *qd_io, unsigned int buf_size);\n> int SMB2_set_eof(const unsigned int xid, struct cifs_tcon *tcon,\n> \t\t u64 persistent_fid, u64 volatile_fid, u32 pid,\n> \t\t loff_t new_eof);\n>diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h\n>index acfbb63086ea2..54ee1317c5b12 100644\n>--- a/fs/smb/client/trace.h\n>+++ b/fs/smb/client/trace.h\n>@@ -165,6 +165,7 @@\n> \tEM(cifs_trace_rw_credits_write_prepare,\t\t\"wr-prepare \") \\\n> \tEM(cifs_trace_rw_credits_write_response_add,\t\"wr-resp-add\") \\\n> \tEM(cifs_trace_rw_credits_write_response_clear,\t\"wr-resp-clr\") \\\n>+\tEM(cifs_trace_rw_credits_query_dir_done,\t\"qd-done    \") \\\n> \tE_(cifs_trace_rw_credits_zero_in_flight,\t\"ZERO-IN-FLT\")\n>\n> #define smb3_tcon_ref_traces\t\t\t\t\t      \\\n>diff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c\n>index 05f8099047e1a..1e9e6f3f9a06f 100644\n>--- a/fs/smb/client/transport.c\n>+++ b/fs/smb/client/transport.c\n>@@ -1154,6 +1154,176 @@ cifs_readv_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n> \treturn  __cifs_readv_discard(server, mid, rdata->result);\n> }\n>\n>+static int\n>+cifs_query_dir_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid,\n>+\t\t      bool malformed)\n>+{\n>+\tint length;\n>+\n>+\tlength = cifs_discard_remaining_data(server);\n>+\tdequeue_mid(server, mid, malformed);\n>+\tmid->resp_buf = server->smallbuf;\n>+\tserver->smallbuf = NULL;\n>+\treturn length;\n>+}\n\nThis is exactly the same as __cifs_readv_discard(), maybe just rename\nthat one to something like __cifs_discard() and reuse it?\n\n>+\n>+/*\n>+ * Receive handler for async QueryDirectory with multi-credit support\n>+ */\n>+int\n>+cifs_query_dir_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n>+{\n>+\tint length, len;\n>+\tunsigned int data_offset, data_len, resp_size;\n>+\tstruct cifs_query_dir_io *qd_io = mid->callback_data;\n>+\tchar *buf = server->smallbuf;\n>+\tunsigned int buflen = server->pdu_size;\n>+\tstruct smb2_query_directory_rsp *rsp;\n>+\n>+\tcifs_dbg(FYI, \"%s: mid=%llu buf_capacity=%zu\\n\",\n>+\t\t __func__, mid->mid, qd_io->combined_iov.iov_len);\n>+\n>+\t/*\n>+\t * Read the rest of QUERY_DIRECTORY_RSP header (sans Data array).\n>+\t * QueryDirectory response structure is 64 bytes (SMB2 header) + 8 bytes (fixed part).\n>+\t */\n>+\tresp_size = sizeof(struct smb2_query_directory_rsp);\n>+\tlen = min_t(unsigned int, buflen, resp_size) - HEADER_SIZE(server) + 1;\n>+\n>+\tlength = cifs_read_from_socket(server,\n>+\t\t\t\t       buf + HEADER_SIZE(server) - 1, len);\n>+\tif (length < 0) {\n>+\t\tqd_io->result = length;\n>+\t\treturn cifs_query_dir_discard(server, mid, false);\n>+\t}\n>+\tserver->total_read += length;\n>+\n>+\tif (server->ops->is_session_expired &&\n>+\t    server->ops->is_session_expired(buf)) {\n>+\t\tcifs_reconnect(server, true);\n>+\t\treturn -1;\n>+\t}\n>+\n>+\tif (server->ops->is_status_pending &&\n>+\t    server->ops->is_status_pending(buf, server)) {\n>+\t\tcifs_discard_remaining_data(server);\n>+\t\treturn -1;\n>+\t}\n>+\n>+\t/* Is there enough to get to the rest of the QUERY_DIRECTORY_RSP header? */\n>+\tif (server->total_read < resp_size) {\n>+\t\tcifs_dbg(FYI, \"%s: server returned short header. got=%u expected=%u\\n\",\n>+\t\t\t __func__, server->total_read, resp_size);\n>+\t\tqd_io->result = smb_EIO2(smb_eio_trace_read_rsp_short,\n>+\t\t\t\t\t server->total_read, resp_size);\n>+\t\treturn cifs_query_dir_discard(server, mid, true);\n>+\t}\n>+\n>+\t/* Set up first iov for signature check and to get credits */\n>+\tqd_io->iov[0].iov_base = buf;\n>+\tqd_io->iov[0].iov_len = server->total_read;\n>+\tqd_io->iov[1].iov_base = NULL;\n>+\tqd_io->iov[1].iov_len = 0;\n>+\tcifs_dbg(FYI, \"0: iov_base=%p iov_len=%zu\\n\",\n>+\t\t qd_io->iov[0].iov_base, qd_io->iov[0].iov_len);\n>+\n>+\t/* Parse header early to access status before map_error converts it */\n>+\trsp = (struct smb2_query_directory_rsp *)buf;\n>+\n>+\t/* Was the SMB query_directory successful? */\n>+\tqd_io->result = server->ops->map_error(buf, false);\n>+\tif (qd_io->result != 0) {\n>+\t\tif (qd_io->combined_iov.iov_base && qd_io->iov[0].iov_len > 0 &&\n>+\t\t    qd_io->iov[0].iov_len <= qd_io->combined_iov.iov_len) {\n>+\t\t\tmemcpy(qd_io->combined_iov.iov_base, qd_io->iov[0].iov_base,\n>+\t\t\t       qd_io->iov[0].iov_len);\n>+\t\t\tqd_io->iov[0].iov_base = qd_io->combined_iov.iov_base;\n>+\t\t}\n>+\t\tcifs_dbg(FYI, \"%s: server returned error %d (Status was 0x%x)\\n\",\n>+\t\t\t __func__, qd_io->result, le32_to_cpu(rsp->hdr.Status));\n>+\t\treturn cifs_query_dir_discard(server, mid, false);\n>+\t}\n>+\n>+\tdata_offset = le16_to_cpu(rsp->OutputBufferOffset);\n>+\tdata_len = le32_to_cpu(rsp->OutputBufferLength);\n>+\n>+\tcifs_dbg(FYI, \"%s: total_read=%u data_offset=%u data_len=%u\\n\",\n>+\t\t __func__, server->total_read, data_offset, data_len);\n>+\n>+\t/* Validate data_offset and data_len */\n>+\tif (data_offset < server->total_read) {\n>+\t\tcifs_dbg(FYI, \"%s: data offset (%u) inside response header\\n\",\n>+\t\t\t __func__, data_offset);\n>+\t\tdata_offset = server->total_read;\n>+\t\tdata_len = 0;\n>+\t} else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) {\n>+\t\tcifs_dbg(FYI, \"%s: data offset (%u) beyond end of smallbuf\\n\",\n>+\t\t\t __func__, data_offset);\n>+\t\tqd_io->result = smb_EIO1(smb_eio_trace_read_overlarge,\n>+\t\t\t\t\t data_offset);\n>+\t\treturn cifs_query_dir_discard(server, mid, true);\n>+\t}\n>+\n>+\t/* Read any padding between header and data */\n>+\tlen = data_offset - server->total_read;\n>+\tif (len > 0) {\n>+\t\tlength = cifs_read_from_socket(server,\n>+\t\t\t\t\t       buf + server->total_read, len);\n>+\t\tif (length < 0) {\n>+\t\t\tqd_io->result = length;\n>+\t\t\treturn cifs_query_dir_discard(server, mid, false);\n>+\t\t}\n>+\t\tserver->total_read += length;\n>+\t\tqd_io->iov[0].iov_len = server->total_read;\n>+\t}\n>+\n>+\t/* Check if data fits in the pre-allocated combined buffer */\n>+\tif (qd_io->iov[0].iov_len + data_len > qd_io->combined_iov.iov_len) {\n>+\t\tcifs_dbg(VFS, \"%s: response (%zu + %u) exceeds buffer capacity (%zu)\\n\",\n>+\t\t\t __func__, qd_io->iov[0].iov_len, data_len, qd_io->combined_iov.iov_len);\n>+\t\tqd_io->result = smb_EIO2(smb_eio_trace_read_rsp_malformed,\n>+\t\t\t\t\t data_len, (unsigned int)qd_io->combined_iov.iov_len);\n>+\t\treturn cifs_query_dir_discard(server, mid, true);\n>+\t}\n>+\n>+\t/*\n>+\t * Build combined header+data in qd_io->combined_iov.iov_base,\n>+\t * preserving the wire layout so SMB2 signing can be verified against\n>+\t * the exact on-the-wire bytes.  hdr_len already accounts for the\n>+\t * header and any padding up to data_offset; data is read at that\n>+\t * same offset.  OutputBufferOffset/Length are left untouched.\n>+\t */\n>+\tif (qd_io->iov[0].iov_len > 0 && qd_io->result == 0) {\n>+\t\tsize_t hdr_len = qd_io->iov[0].iov_len;\n>+\n>+\t\t/* Copy header+padding to combined buffer, preserving wire layout */\n>+\t\tmemcpy(qd_io->combined_iov.iov_base, qd_io->iov[0].iov_base, hdr_len);\n>+\n>+\t\t/* Read directory entries at the wire data_offset position */\n>+\t\tif (data_len > 0) {\n>+\t\t\tlength = cifs_read_from_socket(server,\n>+\t\t\t\t\t\t       qd_io->combined_iov.iov_base + hdr_len,\n>+\t\t\t\t\t\t       data_len);\n>+\t\t\tif (length < 0) {\n>+\t\t\t\tqd_io->result = length;\n>+\t\t\t\treturn cifs_query_dir_discard(server, mid, false);\n>+\t\t\t}\n>+\t\t\tserver->total_read += length;\n>+\n>+\t\t\t/* Set up second iov pointing to the directory data within combined buffer */\n>+\t\t\tqd_io->iov[1].iov_base = qd_io->combined_iov.iov_base + hdr_len;\n>+\t\t\tqd_io->iov[1].iov_len = length;\n>+\t\t}\n>+\n>+\t\tqd_io->combined_iov.iov_len = hdr_len + (data_len > 0 ? length : 0);\n>+\n>+\t\tcifs_dbg(FYI, \"total_read=%u buflen=%u data_len=%u hdr_len=%zu combined_len=%zu\\n\",\n>+\t\t\t server->total_read, buflen, data_len, hdr_len, qd_io->combined_iov.iov_len);\n>+\t}\n>+\n>+\treturn cifs_query_dir_discard(server, mid, false);\n>+}\n>+\n> int\n> cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n> {\n>-- \n>2.43.0\n>\n>","headers":{"Return-Path":"\n <linux-cifs+bounces-11259-incoming=patchwork.ozlabs.org@vger.kernel.org>","X-Original-To":["incoming@patchwork.ozlabs.org","linux-cifs@vger.kernel.org"],"Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=suse.de header.i=@suse.de header.a=rsa-sha256\n header.s=susede2_rsa header.b=yxmTE4zq;\n\tdkim=pass header.d=suse.de header.i=@suse.de header.a=ed25519-sha256\n header.s=susede2_ed25519 header.b=gi3Qz4UB;\n\tdkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de\n header.a=rsa-sha256 header.s=susede2_rsa header.b=x6wbJI5A;\n\tdkim=neutral header.d=suse.de header.i=@suse.de header.a=ed25519-sha256\n header.s=susede2_ed25519 header.b=k49ACK7O;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.105.105.114; helo=tor.lore.kernel.org;\n envelope-from=linux-cifs+bounces-11259-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)","smtp.subspace.kernel.org;\n\tdkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de\n header.b=\"yxmTE4zq\";\n\tdkim=permerror (0-bit key) header.d=suse.de header.i=@suse.de\n header.b=\"gi3Qz4UB\";\n\tdkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de\n header.b=\"x6wbJI5A\";\n\tdkim=permerror (0-bit key) header.d=suse.de header.i=@suse.de\n header.b=\"k49ACK7O\"","smtp.subspace.kernel.org;\n arc=none smtp.client-ip=195.135.223.131","smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=suse.de","smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=suse.de","smtp-out2.suse.de;\n\tdkim=pass header.d=suse.de header.s=susede2_rsa header.b=x6wbJI5A;\n\tdkim=pass header.d=suse.de header.s=susede2_ed25519 header.b=k49ACK7O"],"Received":["from tor.lore.kernel.org (tor.lore.kernel.org [172.105.105.114])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g4nDQ2xYcz1xvV\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 29 Apr 2026 03:17:10 +1000 (AEST)","from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby tor.lore.kernel.org (Postfix) with ESMTP id 4C2AF3106760\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 28 Apr 2026 17:00:12 +0000 (UTC)","from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id EED4133C183;\n\tTue, 28 Apr 2026 17:00:11 +0000 (UTC)","from smtp-out2.suse.de (smtp-out2.suse.de [195.135.223.131])\n\t(using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))\n\t(No client certificate requested)\n\tby smtp.subspace.kernel.org (Postfix) with ESMTPS id A750424501D\n\tfor <linux-cifs@vger.kernel.org>; Tue, 28 Apr 2026 17:00:08 +0000 (UTC)","from imap1.dmz-prg2.suse.org (imap1.dmz-prg2.suse.org\n [IPv6:2a07:de40:b281:104:10:150:64:97])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby smtp-out2.suse.de (Postfix) with ESMTPS id 741575BE03;\n\tTue, 28 Apr 2026 17:00:05 +0000 (UTC)","from imap1.dmz-prg2.suse.org (localhost [127.0.0.1])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby imap1.dmz-prg2.suse.org (Postfix) with ESMTPS id B3357593B2;\n\tTue, 28 Apr 2026 17:00:04 +0000 (UTC)","from dovecot-director2.suse.de ([2a07:de40:b281:106:10:150:64:167])\n\tby imap1.dmz-prg2.suse.org with ESMTPSA\n\tid jXW8HZTn8GlXQwAAD6G6ig\n\t(envelope-from <ematsumiya@suse.de>); Tue, 28 Apr 2026 17:00:04 +0000"],"ARC-Seal":"i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1777395611; cv=none;\n b=MkR5dEkeXSjoVGeBXukLMd7oJUs7SkmuPUCSn1sgCqQq6qPn3NyfMe4KjRV1wmHU3t06EtB5bcCbbSGrK9YO59GyvXdiGzb7DKDEw7FIKwt0aep6s+ESebPdVg0s+prhsYdNrRpnY+sOlZjkoU1JPK5U6dCTmP5rAbqFWD+lVvk=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1777395611; c=relaxed/simple;\n\tbh=rkAG8RNUO9oX/FDuhowy4iRGswQ9Jc19SHVJFYQp30g=;\n\th=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version:\n\t Content-Type:Content-Disposition:In-Reply-To;\n b=fqbXCYQf/g6Rud5MXpeH6AkITo0uM/rGJ6p4B8mOsonzCrC1btau4upk+X9i4GSt8uPACgDU1wejUuCnZiSKvVuUpC+lQDBgAtRvLvqfhnb2Xd/OWJkSGcsiGQnmVFZtk/b7JDqrHaT59scEWv2iwpNhyB2Fluquo5Fx6A9/9bc=","ARC-Authentication-Results":"i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=suse.de;\n spf=pass smtp.mailfrom=suse.de;\n dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de\n header.b=yxmTE4zq;\n dkim=permerror (0-bit key) header.d=suse.de header.i=@suse.de\n header.b=gi3Qz4UB;\n dkim=pass (1024-bit key) header.d=suse.de header.i=@suse.de\n header.b=x6wbJI5A;\n dkim=permerror (0-bit key) header.d=suse.de header.i=@suse.de\n header.b=k49ACK7O; arc=none smtp.client-ip=195.135.223.131","DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de;\n s=susede2_rsa;\n\tt=1777395606;\n h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc:\n\t mime-version:mime-version:content-type:content-type:\n\t in-reply-to:in-reply-to:references:references;\n\tbh=9mkSQFl6TrFv7s+2cLTgEwLZVaQP2PwbGjS1RQZdRyI=;\n\tb=yxmTE4zq5q7zd1GeSdymZOE0dbfBaCmeunFrWD1vV3iECM0QQtU5RGHUvVVJHMr1bbngBX\n\tfdUDPHrJV3DOIoMlxDeNIfv+OH6LHqrrnbhgLwMxAKWxOAFKwieYKs+FNfc25T2Tmo5iR3\n\tH2jxyjBfSMk68hCCiN2Z0j6bLmLUe1M=","v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de;\n\ts=susede2_ed25519; t=1777395606;\n\th=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc:\n\t mime-version:mime-version:content-type:content-type:\n\t in-reply-to:in-reply-to:references:references;\n\tbh=9mkSQFl6TrFv7s+2cLTgEwLZVaQP2PwbGjS1RQZdRyI=;\n\tb=gi3Qz4UBZKoNe/4NSr5bf5vhhAO+wKpugPbRi9tUMnN46fsdeIMZIGJQ76ZeJ79gctlxvH\n\tqTzSIQvLtmvfW1Cg==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.de;\n s=susede2_rsa;\n\tt=1777395605;\n h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc:\n\t mime-version:mime-version:content-type:content-type:\n\t in-reply-to:in-reply-to:references:references;\n\tbh=9mkSQFl6TrFv7s+2cLTgEwLZVaQP2PwbGjS1RQZdRyI=;\n\tb=x6wbJI5AwVPm57tpLCMjebna7JgqbG/lfkO6igdXssgdbZeqZQAqi/fHRKCRNxjmVGuVbl\n\tLh4vAhYj5UKyYQPSb6ApL4BDqLlrhf6+SPMXbnSS/u76BdhsJFIoYV8XOQgExERXvvdWSC\n\tXm5uW+PCOYlsTFRc4VQe13sZ5q4wPUs=","v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.de;\n\ts=susede2_ed25519; t=1777395605;\n\th=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc:\n\t mime-version:mime-version:content-type:content-type:\n\t in-reply-to:in-reply-to:references:references;\n\tbh=9mkSQFl6TrFv7s+2cLTgEwLZVaQP2PwbGjS1RQZdRyI=;\n\tb=k49ACK7O48ST0OcdIcOEdbhYrEjN0nzevPLRJOQzj5QMbWmuT5jCOXbZPGMeZomGbHw/C8\n\tP/QRw2kLcWQmGzAw=="],"Date":"Tue, 28 Apr 2026 14:00:02 -0300","From":"Enzo Matsumiya <ematsumiya@suse.de>","To":"nspmangalore@gmail.com","Cc":"linux-cifs@vger.kernel.org, smfrench@gmail.com, pc@manguebit.org,\n\tbharathsm@microsoft.com, dhowells@redhat.com, henrique.carvalho@suse.com,\n\tShyam Prasad N <sprasad@microsoft.com>","Subject":"Re: [PATCH v3 06/19] cifs: optimize readdir for larger directories","Message-ID":"<afDnSUDhehP7fyIF@suse.de>","References":"<20260428160804.281745-1-sprasad@microsoft.com>\n <20260428160804.281745-6-sprasad@microsoft.com>","Precedence":"bulk","X-Mailing-List":"linux-cifs@vger.kernel.org","List-Id":"<linux-cifs.vger.kernel.org>","List-Subscribe":"<mailto:linux-cifs+subscribe@vger.kernel.org>","List-Unsubscribe":"<mailto:linux-cifs+unsubscribe@vger.kernel.org>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii; format=flowed","Content-Disposition":"inline","In-Reply-To":"<20260428160804.281745-6-sprasad@microsoft.com>","X-Spamd-Result":"default: False [-4.51 / 50.00];\n\tBAYES_HAM(-3.00)[100.00%];\n\tNEURAL_HAM_LONG(-1.00)[-1.000];\n\tNEURAL_HAM_SHORT(-0.20)[-1.000];\n\tR_DKIM_ALLOW(-0.20)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519];\n\tMIME_GOOD(-0.10)[text/plain];\n\tMX_GOOD(-0.01)[];\n\tDKIM_SIGNED(0.00)[suse.de:s=susede2_rsa,suse.de:s=susede2_ed25519];\n\tFREEMAIL_TO(0.00)[gmail.com];\n\tFUZZY_RATELIMITED(0.00)[rspamd.com];\n\tARC_NA(0.00)[];\n\tTO_MATCH_ENVRCPT_ALL(0.00)[];\n\tTO_DN_SOME(0.00)[];\n\tMIME_TRACE(0.00)[0:+];\n\tFREEMAIL_ENVRCPT(0.00)[gmail.com];\n\tRCVD_TLS_ALL(0.00)[];\n\tSPAMHAUS_XBL(0.00)[2a07:de40:b281:104:10:150:64:97:from];\n\tRCVD_COUNT_TWO(0.00)[2];\n\tFROM_EQ_ENVFROM(0.00)[];\n\tFROM_HAS_DN(0.00)[];\n\tFREEMAIL_CC(0.00)[vger.kernel.org,gmail.com,manguebit.org,microsoft.com,redhat.com,suse.com];\n\tMID_RHS_MATCH_FROM(0.00)[];\n\tRCVD_VIA_SMTP_AUTH(0.00)[];\n\tRCPT_COUNT_SEVEN(0.00)[8];\n\tDKIM_TRACE(0.00)[suse.de:+];\n\tMISSING_XM_UA(0.00)[];\n\tDBL_BLOCKED_OPENRESOLVER(0.00)[suse.de:dkim,suse.de:mid,imap1.dmz-prg2.suse.org:helo,imap1.dmz-prg2.suse.org:rdns]","X-Rspamd-Action":"no action","X-Spam-Flag":"NO","X-Spam-Score":"-4.51","X-Spam-Level":"","X-Rspamd-Server":"rspamd1.dmz-prg2.suse.org","X-Rspamd-Queue-Id":"741575BE03"}}]