Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.1/patches/2223135/?format=api
{ "id": 2223135, "url": "http://patchwork.ozlabs.org/api/1.1/patches/2223135/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/20260414135918.279802-6-sprasad@microsoft.com/", "project": { "id": 12, "url": "http://patchwork.ozlabs.org/api/1.1/projects/12/?format=api", "name": "Linux CIFS Client", "link_name": "linux-cifs-client", "list_id": "linux-cifs.vger.kernel.org", "list_email": "linux-cifs@vger.kernel.org", "web_url": "", "scm_url": "", "webscm_url": "" }, "msgid": "<20260414135918.279802-6-sprasad@microsoft.com>", "date": "2026-04-14T13:59:17", "name": "[6/7] cifs: optimize readdir for larger directories", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "51463af473050a62e3d8f09015b111f93ab7a435", "submitter": { "id": 79368, "url": "http://patchwork.ozlabs.org/api/1.1/people/79368/?format=api", "name": "Shyam Prasad N", "email": "nspmangalore@gmail.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linux-cifs-client/patch/20260414135918.279802-6-sprasad@microsoft.com/mbox/", "series": [ { "id": 499852, "url": "http://patchwork.ozlabs.org/api/1.1/series/499852/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linux-cifs-client/list/?series=499852", "date": "2026-04-14T13:59:12", "name": "[1/7] cifs: change_conf needs to be called for session setup", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/499852/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2223135/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2223135/checks/", "tags": {}, "headers": { "Return-Path": "\n <linux-cifs+bounces-10809-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 (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=eD3/s3K7;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=vger.kernel.org\n (client-ip=172.234.253.10; helo=sea.lore.kernel.org;\n envelope-from=linux-cifs+bounces-10809-incoming=patchwork.ozlabs.org@vger.kernel.org;\n receiver=patchwork.ozlabs.org)", "smtp.subspace.kernel.org;\n\tdkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=\"eD3/s3K7\"", "smtp.subspace.kernel.org;\n arc=none smtp.client-ip=209.85.214.170", "smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com", "smtp.subspace.kernel.org;\n spf=pass smtp.mailfrom=gmail.com" ], "Received": [ "from sea.lore.kernel.org (sea.lore.kernel.org [172.234.253.10])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fw5cH3JMbz1yDF\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 15 Apr 2026 00:04:15 +1000 (AEST)", "from smtp.subspace.kernel.org (conduit.subspace.kernel.org\n [100.90.174.1])\n\tby sea.lore.kernel.org (Postfix) with ESMTP id 5E33F3078481\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 14 Apr 2026 13:59:41 +0000 (UTC)", "from localhost.localdomain (localhost.localdomain [127.0.0.1])\n\tby smtp.subspace.kernel.org (Postfix) with ESMTP id 3B4A823D297;\n\tTue, 14 Apr 2026 13:59:41 +0000 (UTC)", "from mail-pl1-f170.google.com (mail-pl1-f170.google.com\n [209.85.214.170])\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 8C1763E63BE\n\tfor <linux-cifs@vger.kernel.org>; Tue, 14 Apr 2026 13:59:36 +0000 (UTC)", "by mail-pl1-f170.google.com with SMTP id\n d9443c01a7336-2aae146b604so40156745ad.3\n for <linux-cifs@vger.kernel.org>;\n Tue, 14 Apr 2026 06:59:36 -0700 (PDT)", "from sprasad-dev1.corp.microsoft.com ([167.220.110.184])\n by smtp.gmail.com with ESMTPSA id\n d9443c01a7336-2b4612e60dasm59779895ad.38.2026.04.14.06.59.34\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Tue, 14 Apr 2026 06:59:34 -0700 (PDT)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;\n\tt=1776175181; cv=none;\n b=ljVJ3+CiRCLP16FYXZ6vsjjcYKgXjfKzRtefFL/YjHm/RHeL7qW3VvjixXNmy6SsxXF8+mnEIVZXSctpM+Ena1bTB4CTngWtXHgz5M4PxkDPvPmnZ03dkXreeFGN8/kOkX7uODl1HqXk4L1m644lNoGTGvE7Z7tUAs2TlrwAp3U=", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=subspace.kernel.org;\n\ts=arc-20240116; t=1776175181; c=relaxed/simple;\n\tbh=g3YXL5FcmZFpCfk3kiuvyTCmHs42eYpGg/8tqgksBH4=;\n\th=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:\n\t MIME-Version;\n b=bKJiFN6VBBJtdEydzcf1epwzsKftPSuXEAvJAx8bKbDjUt/rGSqJ/5vcc+sr+6Xo3RJfyWRQUQ5KM9c9ROFU8FqcYIRzHl1UCuspg4HYbN1mWHDGnLXvO2Megz5TlmKWzahzV8VDjORar9TyIiWFawAmWs9HnhGA8ghn2dGKbjE=", "ARC-Authentication-Results": "i=1; smtp.subspace.kernel.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com;\n spf=pass smtp.mailfrom=gmail.com;\n dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com\n header.b=eD3/s3K7; arc=none smtp.client-ip=209.85.214.170", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1776175175; x=1776779975;\n darn=vger.kernel.org;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=KsASeMOZ5yiQGBruk1f55BCUx00Dy0wjtngHst/RMag=;\n b=eD3/s3K7hnAA834OIZ07RGIWKqZSuCgG3VLZL4D+IXyYxxqdxdAY+SY1mmznZZgEqh\n OusK15lnz6+Em9PycGvvFwkoci6okr/YvvB5W3UUgOxbWpB2EJP7Iq7mmkQSS865mDfo\n UUg9MULpwaOi85RfNigukwdp7rjWm/bOF86xIynecXx6ia2eW6PLpgQABAb99BAjGVW6\n rlky9mYlxJ1lq95cmgCAC7nQ1jNZH6ILkh7GsBZePqmhJt2YHx8j4FECoe+Tt4gUCls7\n AmrxkyddS1pCL/76ksUTxTT2QJWOtBelM4oC2zWkMg4DWK2Ct8h47yRE2opOwyHlf/QT\n 1HpQ==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776175175; x=1776779975;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n :to:cc:subject:date:message-id:reply-to;\n bh=KsASeMOZ5yiQGBruk1f55BCUx00Dy0wjtngHst/RMag=;\n b=lkYlHWWhJN8/9YeVilP6h+QGsncMGzUrXqZ8u050FAWvvM1pgZFdyUi8vIpRNfDqsM\n GTmWZ+ZUg5swpLbjk/gsdS5ICCZ6eIHbT6Z43Pfx1ktooyeD8LO1JwsfjN90k4nYPyTQ\n 90eaEf9gq2Sg4TB5vL66Q5vGKpH63dDgaJOETVYlYIWjAd222ImArjGB2KkRPOszfTWH\n 9Y8e+VSe7iEPt7UArLm8LDz3kiiSYmt7xFN0UgbYK+bVjQxxpDBtYRtIfHwAFf/p3xXU\n SuB6gpEt5SPUtzgCIgkudk0efePc29oH6ru9PEtKzfuM8P2EuOaQS1D5htMlKbfrd896\n Kx7g==", "X-Gm-Message-State": "AOJu0YzX31FtyAUdBOjKXh6OtVirVRI9Amgsb4E3v8G3zBWU3qd+cHrP\n\t1ep5P8bza19FlBgzWaOtv/NZLQJzpLXu9FqmFh+OsyHi/ICp7UYYbongxg43fumMpBQ=", "X-Gm-Gg": "AeBDievQA9CQhqO2B4O39V/RO1jIweBDMrFcZH/qxr4b6OAOAv5o9nxdzBRhBsOs7Ia\n\tUM86V9sGu43ZzdVy0ng3Ok/IJt0ygcnWoNi/jcQFvPPs0TXfwK6vVq4b4kAG3gLqSF3pkUL0LUa\n\tzRDAyRfRXIq657O79Jmi1Vd7VSJcY0QTDIiYQZd1Jv56mo0ZHNRcLzSoAsYVciPLGJMn0mNN8N2\n\t4D0MbeyH8K86m3YuauFmiQrachOAc9llheTZej5rhXkDCeGfjTlq3lbH7ervp5BiQHu4nxNe8qm\n\t6UuTWk/JnQfIdvy8xuYYYEzrk/SFQhAdFM9f1iMGzp2miNnFRIzWBLOkElBURp0iFL+K+vlyhPp\n\tS+4c09efgExJ2qVY+zmUORXmye+7tUH6QjwE9r/UUzZGcbftl4H9ISu3Tat34wDgK2aADA3+sib\n\t3VPyDqti+Z4RQHu+OMyxNGeqe4OuAdLQOnC+wxPke0UdyuVqa89Y1H", "X-Received": "by 2002:a17:902:b612:b0:2b0:6475:73d0 with SMTP id\n d9443c01a7336-2b2d5976825mr140931695ad.12.1776175175042;\n Tue, 14 Apr 2026 06:59:35 -0700 (PDT)", "From": "nspmangalore@gmail.com", "X-Google-Original-From": "sprasad@microsoft.com", "To": "linux-cifs@vger.kernel.org,\n\tsmfrench@gmail.com,\n\tpc@manguebit.com,\n\tbharathsm@microsoft.com,\n\tdhowells@redhat.com,\n\thenrique.carvalho@suse.com,\n\tematsumiya@suse.de", "Cc": "Shyam Prasad N <sprasad@microsoft.com>", "Subject": "[PATCH 6/7] cifs: optimize readdir for larger directories", "Date": "Tue, 14 Apr 2026 19:29:17 +0530", "Message-ID": "<20260414135918.279802-6-sprasad@microsoft.com>", "X-Mailer": "git-send-email 2.43.0", "In-Reply-To": "<20260414135918.279802-1-sprasad@microsoft.com>", "References": "<20260414135918.279802-1-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-Transfer-Encoding": "8bit" }, "content": "From: Shyam Prasad N <sprasad@microsoft.com>\n\nToday QueryDirectory uses the compound_send_recv infrastructure\nwhich is limited to 16KB in size. As a result, readdir of large\ndirectories generally take several round trips.\n\nWith this change, if the readdir needs a QueryDir after the first\nround-trip (meaning that there are more dirents to read), then the\nfollowing QueryDirs will now switch to using larger buffers with\nMTU credits.\n\nTill now, the only command type that used this flow was SMB2_READ.\nIn case of encrypted response, it becomes challenging to decide if\nthe response is for SMB2_READ or SMB2_READDIR. This change reuses\nreceive_encrypted_read and after decrypting the response decides\nthe handling function based on the command in the resp header. That\nway, care has been taken to ensure that the read code path\nmodifications on account of this change are kept to a minimum.\n\nCc: David Howells <dhowells@redhat.com>\nSigned-off-by: Shyam Prasad N <sprasad@microsoft.com>\n---\n fs/smb/client/cifsglob.h | 23 +-\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 | 452 ++++++++++++++++++++++++++++++++++++--\n fs/smb/client/smb2pdu.c | 182 ++++++++++++++-\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 | 184 ++++++++++++++++\n 11 files changed, 829 insertions(+), 35 deletions(-)", "diff": "diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h\nindex 8d089ba08e3e5..45de35ecc0bd2 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,27 @@ 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 combined header+data buffer;\n+\t\t\t\t\t * iov_len holds total capacity until the receive\n+\t\t\t\t\t * handler fills it in, then actual valid length */\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;\ndiff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h\nindex 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,\ndiff --git a/fs/smb/client/readdir.c b/fs/smb/client/readdir.c\nindex 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)\ndiff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c\nindex 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 }\ndiff --git a/fs/smb/client/smb2misc.c b/fs/smb/client/smb2misc.c\nindex 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(\ndiff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c\nindex 5c6fe25204d26..ecbd717f5a5ea 100644\n--- a/fs/smb/client/smb2ops.c\n+++ b/fs/smb/client/smb2ops.c\n@@ -2703,11 +2703,130 @@ 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\", __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@@ -4824,6 +4943,247 @@ 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/* 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\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\", __func__);\n+\t\t}\n+\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\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@@ -4987,25 +5347,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@@ -5049,9 +5430,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@@ -5062,7 +5444,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@@ -5070,9 +5455,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@@ -5101,7 +5485,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@@ -5115,20 +5499,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@@ -5269,7 +5674,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}\ndiff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c\nindex 2d55246d2851b..b59d99d5e193a 100644\n--- a/fs/smb/client/smb2pdu.c\n+++ b/fs/smb/client/smb2pdu.c\n@@ -5496,6 +5496,182 @@ 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 = { .rq_iov = &qd_io->iov[0], .rq_nvec = 1 };\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 +5736,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;\ndiff --git a/fs/smb/client/smb2pdu.h b/fs/smb/client/smb2pdu.h\nindex 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.\ndiff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h\nindex 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);\ndiff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h\nindex 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 \\\ndiff --git a/fs/smb/client/transport.c b/fs/smb/client/transport.c\nindex 05f8099047e1a..4457b98b9365d 100644\n--- a/fs/smb/client/transport.c\n+++ b/fs/smb/client/transport.c\n@@ -1154,6 +1154,190 @@ 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+{\n+\tint length;\n+\tstruct cifs_query_dir_io *qd_io = mid->callback_data;\n+\n+\tlength = cifs_discard_remaining_data(server);\n+\tdequeue_mid(server, mid, qd_io->result != 0);\n+\tmid->resp_buf = server->smallbuf;\n+\tserver->smallbuf = NULL;\n+\treturn length;\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\tgoto discard_and_queue;\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);\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);\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);\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\tgoto discard_and_queue;\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);\n+\t}\n+\n+\t/*\n+\t * Build combined header+data in qd_io->combined_iov.iov_base.\n+\t * Layout: [header][data] with no padding between them.\n+\t * We update OutputBufferOffset in the header to reflect the new layout.\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+\t\tstruct smb2_query_directory_rsp *combined_rsp;\n+\n+\t\t/* Copy header to beginning of combined buffer */\n+\t\tmemcpy(qd_io->combined_iov.iov_base, qd_io->iov[0].iov_base, hdr_len);\n+\n+\t\t/* Read directory entries directly after the header, removing any padding */\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\tgoto discard_and_queue;\n+\t\t\t}\n+\t\t\tserver->total_read += length;\n+\n+\t\t\t/* Update OutputBufferOffset and OutputBufferLength to reflect\n+\t\t\t * the new compact layout (header followed immediately by data).\n+\t\t\t */\n+\t\t\tcombined_rsp = (struct smb2_query_directory_rsp *)qd_io->combined_iov.iov_base;\n+\t\t\tcombined_rsp->OutputBufferOffset = cpu_to_le16(hdr_len);\n+\t\t\tcombined_rsp->OutputBufferLength = cpu_to_le32(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+discard_and_queue:\n+\t/* Discard any remaining data (shouldn't be any) */\n+\tif (server->total_read < buflen)\n+\t\tcifs_discard_remaining_data(server);\n+\n+\tdequeue_mid(server, mid, false);\n+\tmid->resp_buf = server->smallbuf;\n+\tserver->smallbuf = NULL;\n+\treturn length;\n+}\n+\n int\n cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)\n {\n", "prefixes": [ "6/7" ] }