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 6/6] ksmbd: add AAPL READDIR_ATTR V2 support
Date: Thu,  9 Jul 2026 02:06:12 +0200	[thread overview]
Message-ID: <20260709000618.66534-7-g.blivet@me.com> (raw)
In-Reply-To: <20260709000618.66534-1-g.blivet@me.com>

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

Extends the existing V1 inline-FinderInfo mechanism
(SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) with the V2 variant:
byte-identical layout otherwise, confirmed against Apple's actual
SMBClient kernel source
(apple-oss-distributions/SMBClient/kernel/netsmb/smb_smb_2.c), except
the ShortNameLength+Reserved bytes (ignored outright by V1 clients)
become a single flags field that V2 clients actually interpret.

Negotiation: when a client's own client_caps requests V2
(SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2), advertise V2 instead of
V1 in the server's own server_caps reply -- they're mutually exclusive
on the wire, not both set together.

Wire format: the only currently-defined V2 flag,
AAPL_READDIR_ATTR_V2_NO_XATTR, signals that an item has no
xattrs/streams so the client can skip a separate query. Compute this
per-entry in ksmbd_vfs_fill_dentry_attrs() by checking for any xattr
under the XATTR_NAME_STREAM ("user.DosStream.") prefix -- a reliable,
distinct marker for genuine ADS/stream xattrs, unlike DOSATTRIB or ACL
xattrs which live under different prefixes, so this can't
false-positive into telling Finder a file has no extra data when it
actually does. Only computed when a V2 connection is active, to avoid
the extra listxattr() call otherwise.

V1's fixed ShortNameLength=24 convention (real macOS clients ignore
the value outright per the same client source, so it's cosmetic
parity with other real servers, not a functional requirement) is kept
V1-only rather than reused as a V2 base value -- V2 clients do
interpret this field, so it needs a clean 0-or-flag value, not a
leftover V1 constant that happens not to collide with the one defined
flag bit today.

Confirmed via live diagnostics that macOS actually negotiates and
uses V2 (client_caps bit 0x10 set) rather than falling back to V1 or
ignoring the capability.

Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
New for v2.

 fs/smb/common/smb2pdu.h    |  8 ++++++++
 fs/smb/server/connection.h |  1 +
 fs/smb/server/oplock.c     | 12 ++++++++++--
 fs/smb/server/oplock.h     |  3 ++-
 fs/smb/server/smb2pdu.c    | 33 +++++++++++++++++++++++++++++++--
 fs/smb/server/smb2pdu.h    | 16 ++++++++++++++++
 fs/smb/server/vfs.c        | 29 +++++++++++++++++++++++++++++
 fs/smb/server/vfs.h        |  1 +
 8 files changed, 98 insertions(+), 5 deletions(-)

diff --git a/fs/smb/common/smb2pdu.h b/fs/smb/common/smb2pdu.h
index 653cb3579..cc1edd833 100644
--- a/fs/smb/common/smb2pdu.h
+++ b/fs/smb/common/smb2pdu.h
@@ -1261,6 +1261,14 @@ struct create_mxac_req {
 #define SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE  2
 #define SMB2_CRTCTX_AAPL_UNIX_BASED             4
 #define SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE       8
+/*
+ * V2 extends the same inline-FinderInfo mechanism as
+ * SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR with an added flags field,
+ * confirmed byte-identical to V1 otherwise against Apple's actual
+ * SMBClient kernel source (apple-oss-distributions/SMBClient). Mutually
+ * exclusive with the V1 bit on the wire, not both set together.
+ */
+#define SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2 16
 
 /* "AAPL" Volume Capabilities bitmap */
 #define SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID 1
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index fe64dd24b..3ecfe4a7e 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -125,6 +125,7 @@ struct ksmbd_conn {
 	atomic_t			refcnt;
 	bool				is_aapl;
 	bool				aapl_readdir_attr; /* READDIR_ATTR negotiated */
+	bool				aapl_readdir_attr_v2; /* V2 specifically */
 	struct work_struct		release_work;
 };
 
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 693db52cd..f12f4e052 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -2156,11 +2156,15 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp)
  *
  * Sending reply_bitmap with MODEL_INFO set but no model string causes
  * smbfs.kext to enter a broken disconnect path requiring a macOS reboot.
+ * @readdir_attr_v2: advertise SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2
+ *                    instead of the V1 bit
  */
-void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap)
+void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap,
+			 bool readdir_attr_v2)
 {
 	struct create_aapl_rsp *buf;
 	u64 reply_bitmap;
+	u64 server_caps;
 	u32 data_len;
 
 	buf = (struct create_aapl_rsp *)cc;
@@ -2186,8 +2190,12 @@ void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap)
 
 	buf->cmd = cpu_to_le32(SMB2_CRTCTX_AAPL_SERVER_QUERY);
 	buf->reply_bitmap = cpu_to_le64(reply_bitmap);
+	server_caps = AAPL_SERVER_CAPS_KSMBD;
+	if (readdir_attr_v2)
+		server_caps = (server_caps & ~SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) |
+			      SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2;
 	buf->server_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) ?
-			   cpu_to_le64(AAPL_SERVER_CAPS_KSMBD) : 0;
+			   cpu_to_le64(server_caps) : 0;
 	buf->vol_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) ?
 			cpu_to_le64(vol_caps) : 0;
 
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index f7f6afcc5..aef296b21 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -125,7 +125,8 @@ void create_durable_v2_rsp_buf(char *cc, struct ksmbd_file *fp);
 void create_mxac_rsp_buf(char *cc, int maximal_access);
 void create_disk_id_rsp_buf(char *cc, __u64 file_id, __u64 vol_id);
 void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp);
-void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap);
+void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap,
+			 bool readdir_attr_v2);
 struct create_context *smb2_find_context_vals(void *open_req, const char *tag, int tag_len);
 struct oplock_info *lookup_lease_in_table(struct ksmbd_conn *conn,
 					  char *lease_key);
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 612f8c8ec..bfaa9909e 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -4404,12 +4404,23 @@ int smb2_open(struct ksmbd_work *work)
 	if (aapl_ctxt) {
 		if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
 			conn->aapl_readdir_attr = true;
+		/*
+		 * V2 extends the same inline-FinderInfo mechanism (see
+		 * smb2pdu.h), so a V2-requesting client also gets
+		 * aapl_readdir_attr treatment -- the reply just advertises
+		 * the V2 bit instead of the V1 one (create_aapl_rsp_buf).
+		 */
+		if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2) {
+			conn->aapl_readdir_attr = true;
+			conn->aapl_readdir_attr_v2 = true;
+		}
 
 		contxt_cnt++;
 		create_aapl_rsp_buf(rsp->Buffer +
 				le32_to_cpu(rsp->CreateContextsLength),
 				SMB2_CRTCTX_AAPL_FULL_SYNC,
-				aapl_req_bitmap);
+				aapl_req_bitmap,
+				conn->aapl_readdir_attr_v2);
 		le32_add_cpu(&rsp->CreateContextsLength,
 			     conn->vals->create_aapl_size);
 		iov_len += conn->vals->create_aapl_size;
@@ -4753,6 +4764,11 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
 			 *                      the file extension for icon lookup)
 			 *   Reserved2        = Unix mode bits (uint16 LE)
 			 * Reparse-point tag is indicated via ExtFileAttributes, not EaSize.
+			 *
+			 * V2 (conn->aapl_readdir_attr_v2): ShortNameLength+Reserved
+			 * are read as a single flags field instead of being ignored
+			 * -- see smb2pdu.h for the wire-format confirmation and
+			 * AAPL_READDIR_ATTR_V2_NO_XATTR's meaning.
 			 */
 			__le32 reparse_tag =
 				smb2_get_reparse_tag_special_file(ksmbd_kstat->kstat->mode);
@@ -4784,7 +4800,20 @@ static int smb2_populate_readdir_entry(struct ksmbd_conn *conn, int info_level,
 			 * it as a flags field that is interpreted; V1 doesn't.
 			 * Either value is safe here, so keep 24 for parity.
 			 */
-			fibdinfo->ShortNameLength = 24;
+			if (conn->aapl_readdir_attr_v2) {
+				/*
+				 * V2 repurposes this field as flags (see comment
+				 * above) -- 24 is a V1-only convention that real
+				 * macOS clients ignore outright, so don't reuse it
+				 * here as a base value for a field V2 clients
+				 * actually interpret.
+				 */
+				fibdinfo->ShortNameLength = 0;
+				if (!ksmbd_kstat->has_ads_stream)
+					fibdinfo->ShortNameLength = AAPL_READDIR_ATTR_V2_NO_XATTR;
+			} else {
+				fibdinfo->ShortNameLength = 24;
+			}
 			memset(fibdinfo->ShortName, 0, sizeof(fibdinfo->ShortName));
 			fibdinfo->Reserved2 = cpu_to_le16(ksmbd_kstat->kstat->mode & 0xffff);
 		} else {
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 740358a2b..737ee488f 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -96,6 +96,22 @@ struct preauth_integrity_info {
 				 SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE | \
 				 SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
 
+/*
+ * READDIR_ATTR_V2 (SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR_V2, see
+ * fs/smb/common/smb2pdu.h) extends the same inline-FinderInfo mechanism
+ * above with a flags field, confirmed byte-identical to V1 otherwise
+ * against Apple's actual SMBClient kernel source
+ * (apple-oss-distributions/SMBClient). When the client's own client_caps
+ * requests V2, the server advertises V2 instead of V1 in its own
+ * server_caps reply; V1 and V2 are mutually exclusive on the wire, not
+ * both set together. The wire format's ShortNameLength+Reserved
+ * (ignored in V1) become a single flags field in V2 --
+ * AAPL_READDIR_ATTR_V2_NO_XATTR is the only flag bit currently defined,
+ * signaling the item has no xattrs/streams so the client can skip a
+ * separate query.
+ */
+#define AAPL_READDIR_ATTR_V2_NO_XATTR	0x01
+
 /* Model string: up to 31 ASCII chars */
 #define AAPL_MODEL_MAX_CHARS	31
 #define AAPL_MODEL_UTF16_BYTES	(AAPL_MODEL_MAX_CHARS * 2)
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 61d02322c..d00438d43 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1684,6 +1684,35 @@ int ksmbd_vfs_fill_dentry_attrs(struct ksmbd_work *work,
 		}
 	}
 
+	/*
+	 * Only pay for this when it'll actually be used: AAPL
+	 * READDIR_ATTR_V2's flags field (AAPL_READDIR_ATTR_V2_NO_XATTR) is
+	 * the only consumer. XATTR_NAME_STREAM ("user.DosStream.") is a
+	 * reliable, distinct prefix for genuine ADS/stream xattrs -- unlike
+	 * DOSATTRIB or ACL xattrs, which live under different prefixes, so
+	 * this can't false-positive into telling Finder a file has no extra
+	 * data when it actually does.
+	 */
+	ksmbd_kstat->has_ads_stream = false;
+	if (work->conn->aapl_readdir_attr_v2) {
+		char *xattr_list = NULL, *name;
+		ssize_t xattr_list_len;
+
+		xattr_list_len = ksmbd_vfs_listxattr(dentry, &xattr_list);
+		if (xattr_list_len > 0) {
+			for (name = xattr_list;
+			     name - xattr_list < xattr_list_len;
+			     name += strlen(name) + 1) {
+				if (!strncmp(name, XATTR_NAME_STREAM,
+					     XATTR_NAME_STREAM_LEN)) {
+					ksmbd_kstat->has_ads_stream = true;
+					break;
+				}
+			}
+		}
+		kvfree(xattr_list);
+	}
+
 	return 0;
 }
 
diff --git a/fs/smb/server/vfs.h b/fs/smb/server/vfs.h
index 8eab9392f..2e8f9f2d9 100644
--- a/fs/smb/server/vfs.h
+++ b/fs/smb/server/vfs.h
@@ -70,6 +70,7 @@ struct ksmbd_kstat {
 	struct kstat		*kstat;
 	unsigned long long	create_time;
 	__le32			file_attributes;
+	bool			has_ads_stream; /* AAPL READDIR_ATTR V2 xattr-presence flag */
 };
 
 int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child);
-- 
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 ` [PATCH v2 3/6] ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR negotiated Gaël Blivet-Bailly
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 ` Gaël Blivet-Bailly [this message]
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-7-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