Linux CIFS filesystem development
 help / color / mirror / Atom feed
From: "Gaël Blivet-Bailly" <gael.blivet@gmail.com>
To: Namjae Jeon <linkinjeon@kernel.org>
Cc: Gael Blivet <gael.blivet@gmail.com>, linux-cifs@vger.kernel.org
Subject: [PATCH v2 3/6] ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR negotiated
Date: Thu,  9 Jul 2026 02:06:09 +0200	[thread overview]
Message-ID: <20260709000618.66534-4-g.blivet@me.com> (raw)
In-Reply-To: <20260709000618.66534-1-g.blivet@me.com>

From: Gael Blivet <gael.blivet@gmail.com>

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; kept for wire parity with Samba, see below)
  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.

Reverse-engineered from macOS smbfs.kext network behavior and
cross-checked against Samba's vfs_fruit marshalling
(source3/smbd/smb2_trans2.c, SMB_FIND_ID_BOTH_DIRECTORY_INFO). Also
confirmed against Apple's published SMBClient kernel source
(apple-oss-distributions/SMBClient, smb_smb_2.c) -- every field here
matches exactly, except ShortNameLength=24: real V1 clients read but
never examine that field, so it's kept for wire parity with Samba, not
because macOS requires it.

Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
v1 -> v2: Cross-checked wire format against Apple's published
SMBClient kernel source -- confirmed every field matches, and
corrected an inaccurate claim about ShortNameLength=24 being
something real clients require (they don't; a comment-only fix).

 fs/smb/server/smb2pdu.c | 60 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 55 insertions(+), 5 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 8fed5fd6d..120c2c80d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4733,17 +4733,67 @@ 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; 24 here instead matches
+			 * Samba's vfs_fruit marshalling (smb2_trans2.c) for
+			 * server-to-server wire parity. Apple's own SMBClient
+			 * source (smb_smb_2.c) confirms real macOS clients
+			 * read this field but never examine its value for a
+			 * V1 (non-READDIR_ATTR_V2) connection -- V2 repurposes
+			 * it as a flags field that is interpreted; V1 doesn't.
+			 * Either value is safe here, so keep 24 for parity.
+			 */
+			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


  parent reply	other threads:[~2026-07-09  0:07 UTC|newest]

Thread overview: 16+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-09  0:06 [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Gaël Blivet-Bailly
2026-07-09  0:06 ` [PATCH v2 1/6] ksmbd: add Apple AAPL kAAPL_SERVER_QUERY create context support Gaël Blivet-Bailly
2026-07-09  0:06 ` [PATCH v2 2/6] ksmbd: synthesize empty AFP_AfpInfo xattr on first probe Gaël Blivet-Bailly
2026-07-09  0:06 ` Gaël Blivet-Bailly [this message]
2026-07-09  0:06 ` [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED Gaël Blivet-Bailly
2026-07-09  7:03   ` ChenXiaoSong
2026-07-09  9:24   ` ChenXiaoSong
2026-07-10  5:59     ` Gaël Blivet
2026-07-10  6:48       ` ChenXiaoSong
2026-07-10  7:04         ` ChenXiaoSong
2026-07-10  8:54           ` Gaël Blivet
2026-07-10  9:15             ` ChenXiaoSong
2026-07-10 13:28               ` Gaël Blivet
2026-07-09  0:06 ` [PATCH v2 5/6] ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK Gaël Blivet-Bailly
2026-07-09  0:06 ` [PATCH v2 6/6] ksmbd: add AAPL READDIR_ATTR V2 support Gaël Blivet-Bailly
2026-07-09 13:15 ` [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Namjae Jeon

Reply instructions:

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

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

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

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

  git send-email \
    --in-reply-to=20260709000618.66534-4-g.blivet@me.com \
    --to=gael.blivet@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-cifs@vger.kernel.org \
    /path/to/YOUR_REPLY

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

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