[PATCH 3/5] ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR negotiated

"Gaël Blivet-Bailly" <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
From: Gael Blivet <[email protected]>

Without READDIR_ATTR, macOS Finder resolves type/creator/icon for
every file in a directory listing by opening its AFP_AfpInfo stream
individually -- one extra CREATE+QUERY_INFO+CLOSE round trip per file,
which is the dominant cost of browsing a large directory over SMB from
a Mac.

When the client negotiates READDIR_ATTR (conn->aapl_readdir_attr, set
during CREATE's AAPL context exchange), inline the same information
directly into each FILEID_BOTH_DIRECTORY_INFORMATION FIND entry:
  EaSize           = max_access, expanded specific rights
                      (GENERIC_ALL_FLAGS), not the raw FILE_GENERIC_ALL_LE
                      "generic" meta-bit -- that bit has none of the
                      specific FILE_* rights macOS's smbfs.kext checks
                      bit-by-bit, so reporting it directly would fail
                      every access check and show Finder's "no entry"
                      badge on every file/folder.
  ShortNameLength  = 24 (fixed; the spec says 0 when there's no short
                      name, but real macOS clients expect 24 here)
  ShortName[0..7]  = resource fork size (0 -- no resource forks)
  ShortName[8..23] = compressed FinderInfo (all zero: type/creator
                      unset, client falls back to extension-based
                      icon/type detection, consistent with the
                      AFP_AfpInfo synthesis this mirrors)
  Reserved2        = Unix mode bits
Reparse-point status is still carried via ExtFileAttributes rather
than EaSize once READDIR_ATTR is active, since EaSize is repurposed
for max_access.

This wire format was reverse-engineered from macOS smbfs.kext network
behavior (Apple has not published it) and cross-checked against
Samba's vfs_fruit marshalling (source3/smbd/smb2_trans2.c,
SMB_FIND_ID_BOTH_DIRECTORY_INFO), which implements the same extension
for the same reason and confirms both the max_access expansion and the
ShortNameLength=24 behavior.

Signed-off-by: Gael Blivet <[email protected]>
---
 fs/smb/server/smb2pdu.c | 57 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 52 insertions(+), 5 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 8b81c712e..b1be4df04 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4649,17 +4649,64 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
 
 		fibdinfo = (struct file_id_both_directory_info *)kstat;
 		fibdinfo->FileNameLength = cpu_to_le32(conv_len);
-		fibdinfo->EaSize =
-			smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
-		if (fibdinfo->EaSize)
-			fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
 		if (conn->is_aapl)
 			fibdinfo->UniqueId = 0;
 		else
 			fibdinfo->UniqueId = cpu_to_le64(ksmbd_kstat->kstat->ino);
 		fibdinfo->ShortNameLength = 0;
 		fibdinfo->Reserved = 0;
-		fibdinfo->Reserved2 = cpu_to_le16(0);
+		if (conn->aapl_readdir_attr) {
+			/*
+			 * READDIR_ATTR wire format, confirmed against Samba's
+			 * vfs_fruit marshalling (source3/smbd/smb2_trans2.c):
+			 *   EaSize           = max_access (expanded specific
+			 *                      rights, simplified to "grant all")
+			 *   ShortNameLength  = 24 (fixed; not 0, despite the spec)
+			 *   ShortName[0..7]  = resource fork size (uint64 LE, 0 = no rfork)
+			 *   ShortName[8..23] = compressed FinderInfo (type+creator+flags+
+			 *                      ext_flags+date_added, 16 bytes LE; all
+			 *                      zeros means type=0/creator=0, i.e. use
+			 *                      the file extension for icon lookup)
+			 *   Reserved2        = Unix mode bits (uint16 LE)
+			 * Reparse-point tag is indicated via ExtFileAttributes, not EaSize.
+			 */
+			__le32 reparse_tag =
+				smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+
+			if (reparse_tag)
+				fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
+			/*
+			 * FILE_GENERIC_ALL_LE (0x10000000) is the raw
+			 * "generic all" meta-bit -- valid only in a
+			 * client's requested access mask, for the server
+			 * to expand. It has none of the specific FILE_*
+			 * rights bits set (FILE_LIST_DIRECTORY, FILE_TRAVERSE,
+			 * etc.), so reporting it here as max_access would make
+			 * macOS's bit-by-bit access checks fail on every
+			 * entry -> permanent "no entry" badges in Finder.
+			 * Report the actual expanded rights instead, same
+			 * as smb_map_generic_desired_access() does when
+			 * translating a client's GENERIC_ALL request.
+			 */
+			fibdinfo->EaSize = cpu_to_le32(GENERIC_ALL_FLAGS);
+			/*
+			 * The spec says ShortNameLength should be 0 when
+			 * there's no short name, but real macOS clients
+			 * expect it set to 24 for READDIR_ATTR entries --
+			 * confirmed in Samba's vfs_fruit marshalling
+			 * (smb2_trans2.c): "on the wire behaviour shows
+			 * its set to 24 by clients."
+			 */
+			fibdinfo->ShortNameLength = 24;
+			memset(fibdinfo->ShortName, 0, sizeof(fibdinfo->ShortName));
+			fibdinfo->Reserved2 = cpu_to_le16(ksmbd_kstat->kstat->mode & 0xffff);
+		} else {
+			fibdinfo->EaSize =
+				smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
+			if (fibdinfo->EaSize)
+				fibdinfo->ExtFileAttributes = FILE_ATTRIBUTE_REPARSE_POINT_LE;
+			fibdinfo->Reserved2 = cpu_to_le16(0);
+		}
 		if (d_info->hide_dot_file && d_info->name[0] == '.')
 			fibdinfo->ExtFileAttributes |= FILE_ATTRIBUTE_HIDDEN_LE;
 		memcpy(fibdinfo->FileName, conv_name, conv_len);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.