* [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support
@ 2026-07-09 0:06 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
` (6 more replies)
0 siblings, 7 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
From: Gael Blivet <gael.blivet@gmail.com>
Link to v1:
https://lore.kernel.org/linux-cifs/20260707100129.47563-1-g.blivet@me.com/
v2 addresses the CHANGE_NOTIFY CANCEL concern raised on v1:
smb2_cancel() calls each async request's cancel_fn() while holding
conn->request_lock, so cancel_fn must not sleep. Patch 4 now splits
this into inline lock-protected list/state manipulation (matching
every other cancel_fn in this file) plus a workqueue-deferred send of
the actual STATUS_CANCELLED response, which is the one part that
sleeps.
Separately: the corresponding ksmbd-tools userspace patch (aapl_model
field, "time machine"/"aapl model" config options) has been sent to
that project directly, since it's a different repository from this
kernel series.
New since v1: patch 6, AAPL READDIR_ATTR V2 support -- real macOS
clients negotiate V2 over V1 when both are offered, so v1-only left a
gap for current macOS versions.
Also new: patches 1, 3, and 6 are now additionally cross-checked
against Apple's published SMBClient kernel source
(apple-oss-distributions/SMBClient, smb_smb_2.c) -- everything matched
the network-derived understanding v1 was built on, except one
inaccurate assumption in patch 3, now corrected.
Patch 5 also gets a correctness fix: the synthesized ChunkCount=0
full-copy response was reporting ChunksWritten=1/ChunkBytesWritten=
<file size>, describing a chunk the client never actually sent (it
sent zero). Now reports 0/0, matching what's actually been validated
against a real client; only TotalBytesWritten carries a value.
Patches 1-4 are otherwise unchanged in substance from v1, re-derived
against the current tree (this branch has moved since v1).
Gael Blivet (6):
ksmbd: add Apple AAPL kAAPL_SERVER_QUERY create context support
ksmbd: synthesize empty AFP_AfpInfo xattr on first probe
ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR
negotiated
ksmbd: defer CHANGE_NOTIFY completion instead of
STATUS_NOT_IMPLEMENTED
ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK
ksmbd: add AAPL READDIR_ATTR V2 support
fs/smb/common/smb2pdu.h | 8 +
fs/smb/common/smbglob.h | 1 +
fs/smb/server/connection.h | 2 +
fs/smb/server/ksmbd_netlink.h | 4 +-
fs/smb/server/ksmbd_work.c | 1 +
fs/smb/server/ksmbd_work.h | 2 +
fs/smb/server/oplock.c | 85 +++++++
fs/smb/server/oplock.h | 2 +
fs/smb/server/server.h | 2 +
fs/smb/server/smb2ops.c | 4 +
fs/smb/server/smb2pdu.c | 442 ++++++++++++++++++++++++++++++++--
fs/smb/server/smb2pdu.h | 76 ++++++
fs/smb/server/transport_ipc.c | 9 +
fs/smb/server/vfs.c | 88 +++++++
fs/smb/server/vfs.h | 1 +
fs/smb/server/vfs_cache.c | 48 ++++
fs/smb/server/vfs_cache.h | 6 +
17 files changed, 758 insertions(+), 23 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 1/6] ksmbd: add Apple AAPL kAAPL_SERVER_QUERY create context support
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 ` 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
` (5 subsequent siblings)
6 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
From: Gael Blivet <gael.blivet@gmail.com>
macOS clients (Finder, and specifically Time Machine's backupd) send
an "AAPL" SMB2 create context on CREATE to negotiate Apple-specific
server capabilities (server_caps/vol_caps/model string). Without a
response to this context, macOS Time Machine over SMB does not work
at all.
Add the AAPL create context structs (create_aapl_rsp,
aapl_server_query_req) and create_aapl_rsp_buf(), which builds the
kAAPL_SERVER_QUERY response mirroring the layout observed from macOS's
own smbd, including the model string workaround: omitting the model
string when the client requested it causes smbfs.kext to enter a
broken disconnect path requiring a full macOS reboot to recover from.
Command codes and bitmap values reuse the existing SMB2_CRTCTX_AAPL_*
constants in fs/smb/common/smb2pdu.h.
Wire format confirmed against Apple's published SMBClient kernel
source (apple-oss-distributions/SMBClient, smb_smb_2.c) -- every field
here and every SMB2_CRTCTX_AAPL_* constant matches exactly.
Hook the request parsing and response into smb2_open()'s existing
create-context handling, following the same DataOffset+DataLength
bounds-checking convention already used by every other context parser
in this file. The AAPL model string is configurable via the existing
netlink startup path (server_conf.aapl_model, default "Xserve").
This is scoped to shares with the new KSMBD_SHARE_FLAG_TIME_MACHINE
flag only, not enabled globally -- Apple's AAPL extension is
undocumented, so containing its blast radius to shares that explicitly
opt in limits risk to ordinary SMB shares.
conn->aapl_readdir_attr is set here when the client also advertises
READDIR_ATTR support, but the actual inline-FinderInfo wire format
(the feature that flag gates) is not implemented yet -- follow-up
commit.
Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
v1 -> v2: Cross-checked wire format against Apple's published
SMBClient kernel source (apple-oss-distributions/SMBClient,
smb_smb_2.c) -- confirmed every field and constant matches; no
functional change.
fs/smb/common/smbglob.h | 1 +
fs/smb/server/connection.h | 1 +
fs/smb/server/ksmbd_netlink.h | 4 +-
fs/smb/server/oplock.c | 77 +++++++++++++++++++++++++++++++++++
fs/smb/server/oplock.h | 1 +
fs/smb/server/server.h | 2 +
fs/smb/server/smb2ops.c | 4 ++
fs/smb/server/smb2pdu.c | 56 ++++++++++++++++++++++++-
fs/smb/server/smb2pdu.h | 60 +++++++++++++++++++++++++++
fs/smb/server/transport_ipc.c | 9 ++++
10 files changed, 213 insertions(+), 2 deletions(-)
diff --git a/fs/smb/common/smbglob.h b/fs/smb/common/smbglob.h
index 4e33d91cd..d9c7e6e7a 100644
--- a/fs/smb/common/smbglob.h
+++ b/fs/smb/common/smbglob.h
@@ -39,6 +39,7 @@ struct smb_version_values {
size_t create_mxac_size;
size_t create_disk_id_size;
size_t create_posix_size;
+ size_t create_aapl_size;
};
static inline unsigned int get_rfc1002_len(void *buf)
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 04621cb11..fe64dd24b 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -124,6 +124,7 @@ struct ksmbd_conn {
bool binding;
atomic_t refcnt;
bool is_aapl;
+ bool aapl_readdir_attr; /* READDIR_ATTR negotiated */
struct work_struct release_work;
};
diff --git a/fs/smb/server/ksmbd_netlink.h b/fs/smb/server/ksmbd_netlink.h
index c9e1b0b68..1ea0a367b 100644
--- a/fs/smb/server/ksmbd_netlink.h
+++ b/fs/smb/server/ksmbd_netlink.h
@@ -113,7 +113,8 @@ struct ksmbd_startup_request {
__u32 max_connections; /* Number of maximum simultaneous connections */
__s8 bind_interfaces_only;
__u32 max_ip_connections; /* Number of maximum connection per ip address */
- __s8 reserved[499]; /* Reserved room */
+ __s8 aapl_model[32]; /* AAPL model string for Finder icon, e.g. "Xserve" */
+ __s8 reserved[467]; /* Reserved room */
__u32 ifc_list_sz; /* interfaces list size */
__s8 ____payload[];
} __packed;
@@ -378,6 +379,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_SHARE_FLAG_CROSSMNT BIT(15)
#define KSMBD_SHARE_FLAG_CONTINUOUS_AVAILABILITY BIT(16)
#define KSMBD_SHARE_FLAG_HIDE_UNREADABLE BIT(17)
+#define KSMBD_SHARE_FLAG_TIME_MACHINE BIT(18)
/*
* Tree connect request flags.
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 3c55ae5d6..693db52cd 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -16,6 +16,7 @@
#include "mgmt/user_session.h"
#include "mgmt/share_config.h"
#include "mgmt/tree_connect.h"
+#include "server.h"
static LIST_HEAD(lease_table_list);
static DEFINE_RWLOCK(lease_list_lock);
@@ -2136,6 +2137,82 @@ void create_posix_rsp_buf(char *cc, struct ksmbd_file *fp)
SIDUNIX_GROUP, (struct smb_sid *)&buf->SidBuffer[28]);
}
+/**
+ * create_aapl_rsp_buf() - build Apple AAPL kAAPL_SERVER_QUERY response
+ * @cc: buffer to write the create context into (AAPL_RSP_MAX_SIZE bytes)
+ * @vol_caps: volume capability flags (SMB2_CRTCTX_AAPL_* volume bits)
+ * @req_bitmap: the client's request bitmap, echoed back in reply_bitmap
+ *
+ * Response format follows the layout observed from macOS's own smbd, and
+ * matches the client-side parsing in Apple's published SMBClient kernel
+ * source (apple-oss-distributions/SMBClient, smb_smb_2.c, kAAPL_SERVER_QUERY
+ * case): reply_bitmap, then server_caps/vol_caps/model-info fields present
+ * only when their reply_bitmap bit is set:
+ * reply_bitmap = req_bitmap masked to the fields we support
+ * server_caps = AAPL_SERVER_CAPS_KSMBD when requested
+ * vol_caps = caller-supplied
+ * model string = server_conf.aapl_model (default "Xserve") in UTF-16LE,
+ * when SMB2_CRTCTX_AAPL_MODEL_INFO requested
+ *
+ * Sending reply_bitmap with MODEL_INFO set but no model string causes
+ * smbfs.kext to enter a broken disconnect path requiring a macOS reboot.
+ */
+void create_aapl_rsp_buf(char *cc, __u64 vol_caps, __u64 req_bitmap)
+{
+ struct create_aapl_rsp *buf;
+ u64 reply_bitmap;
+ u32 data_len;
+
+ buf = (struct create_aapl_rsp *)cc;
+ memset(buf, 0, AAPL_RSP_MAX_SIZE);
+
+ reply_bitmap = req_bitmap & (SMB2_CRTCTX_AAPL_SERVER_CAPS |
+ SMB2_CRTCTX_AAPL_VOLUME_CAPS |
+ SMB2_CRTCTX_AAPL_MODEL_INFO);
+
+ /* base data: cmd(4)+reserved(4)+reply_bitmap(8)+server_caps(8)+vol_caps(8) */
+ data_len = 32;
+ if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO)
+ data_len += 4 + 4 + AAPL_MODEL_UTF16_BYTES; /* pad2+model_bytes+string */
+
+ buf->ccontext.DataOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, cmd));
+ buf->ccontext.DataLength = cpu_to_le32(data_len);
+ buf->ccontext.NameOffset = cpu_to_le16(offsetof(struct create_aapl_rsp, Name));
+ buf->ccontext.NameLength = cpu_to_le16(SMB2_CREATE_AAPL_LEN);
+ buf->Name[0] = 'A';
+ buf->Name[1] = 'A';
+ buf->Name[2] = 'P';
+ buf->Name[3] = 'L';
+
+ buf->cmd = cpu_to_le32(SMB2_CRTCTX_AAPL_SERVER_QUERY);
+ buf->reply_bitmap = cpu_to_le64(reply_bitmap);
+ buf->server_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) ?
+ cpu_to_le64(AAPL_SERVER_CAPS_KSMBD) : 0;
+ buf->vol_caps = (reply_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) ?
+ cpu_to_le64(vol_caps) : 0;
+
+ if (reply_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) {
+ __le32 *p = (__le32 *)((u8 *)buf + sizeof(*buf));
+ __le16 *model_str = (__le16 *)(p + 2);
+ const char *src = server_conf.aapl_model[0] ?
+ server_conf.aapl_model : "Xserve";
+ int i, model_bytes = 0;
+
+ /* Convert ASCII model string to UTF-16LE in-place */
+ for (i = 0; src[i] && i < AAPL_MODEL_MAX_CHARS; i++) {
+ model_str[i] = cpu_to_le16((unsigned char)src[i]);
+ model_bytes += 2;
+ }
+
+ p[0] = 0; /* pad2 */
+ p[1] = cpu_to_le32(model_bytes);
+
+ /* Update DataLength to reflect actual model string size */
+ buf->ccontext.DataLength =
+ cpu_to_le32(data_len - AAPL_MODEL_UTF16_BYTES + model_bytes);
+ }
+}
+
/*
* Find lease object(opinfo) for given lease key/fid from lease
* break/file close path.
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index 3f581d22b..f7f6afcc5 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -125,6 +125,7 @@ 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);
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/server.h b/fs/smb/server/server.h
index b8a7317be..4d4d268b5 100644
--- a/fs/smb/server/server.h
+++ b/fs/smb/server/server.h
@@ -48,6 +48,8 @@ struct ksmbd_server_config {
char *conf[SERVER_CONF_WORK_GROUP + 1];
struct task_struct *dh_task;
bool bind_interfaces_only;
+ /* AAPL model string for Finder icon, e.g. "Xserve" */
+ char aapl_model[32];
};
extern struct ksmbd_server_config server_conf;
diff --git a/fs/smb/server/smb2ops.c b/fs/smb/server/smb2ops.c
index c9a32ee09..97938150d 100644
--- a/fs/smb/server/smb2ops.c
+++ b/fs/smb/server/smb2ops.c
@@ -37,6 +37,7 @@ static struct smb_version_values smb21_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb30_server_values = {
@@ -64,6 +65,7 @@ static struct smb_version_values smb30_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb302_server_values = {
@@ -91,6 +93,7 @@ static struct smb_version_values smb302_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_values smb311_server_values = {
@@ -118,6 +121,7 @@ static struct smb_version_values smb311_server_values = {
.create_mxac_size = sizeof(struct create_mxac_rsp),
.create_disk_id_size = sizeof(struct create_disk_id_rsp),
.create_posix_size = sizeof(struct create_posix_rsp),
+ .create_aapl_size = AAPL_RSP_MAX_SIZE,
};
static struct smb_version_ops smb2_0_server_ops = {
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 81f4af614..9c70e97d8 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -3358,6 +3358,8 @@ int smb2_open(struct ksmbd_work *work)
int rc = 0;
int contxt_cnt = 0, query_disk_id = 0;
bool maximal_access_ctxt = false, posix_ctxt = false;
+ bool aapl_ctxt = false;
+ __u64 aapl_req_bitmap = 0, aapl_client_caps = 0;
int s_type = 0;
int next_off = 0;
char *name = NULL;
@@ -4122,7 +4124,32 @@ int smb2_open(struct ksmbd_work *work)
query_disk_id = 1;
}
- if (conn->is_aapl == false) {
+ if (test_share_config_flag(share, KSMBD_SHARE_FLAG_TIME_MACHINE)) {
+ context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
+ if (IS_ERR(context)) {
+ rc = PTR_ERR(context);
+ goto err_out1;
+ } else if (context) {
+ struct aapl_server_query_req *aapl_req;
+
+ if (le32_to_cpu(context->DataLength) <
+ sizeof(struct aapl_server_query_req)) {
+ rc = -EINVAL;
+ goto err_out1;
+ }
+
+ aapl_req = (struct aapl_server_query_req *)
+ ((char *)context +
+ le16_to_cpu(context->DataOffset));
+ if (le32_to_cpu(aapl_req->cmd) ==
+ SMB2_CRTCTX_AAPL_SERVER_QUERY) {
+ conn->is_aapl = true;
+ aapl_ctxt = true;
+ aapl_req_bitmap = le64_to_cpu(aapl_req->req_bitmap);
+ aapl_client_caps = le64_to_cpu(aapl_req->client_caps);
+ }
+ }
+ } else if (conn->is_aapl == false) {
context = smb2_find_context_vals(req, SMB2_CREATE_AAPL, 4);
if (IS_ERR(context)) {
rc = PTR_ERR(context);
@@ -4329,6 +4356,10 @@ int smb2_open(struct ksmbd_work *work)
}
if (posix_ctxt) {
+ struct create_context *posix_ccontext;
+
+ posix_ccontext = (struct create_context *)(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength));
contxt_cnt++;
create_posix_rsp_buf(rsp->Buffer +
le32_to_cpu(rsp->CreateContextsLength),
@@ -4338,6 +4369,29 @@ int smb2_open(struct ksmbd_work *work)
iov_len += conn->vals->create_posix_size;
if (next_ptr)
*next_ptr = cpu_to_le32(next_off);
+ next_ptr = &posix_ccontext->Next;
+ next_off = conn->vals->create_posix_size;
+ }
+
+ /*
+ * AAPL create context response: see smb2pdu.h for the capability
+ * rationale. Scoped to TIME_MACHINE shares only.
+ */
+ if (aapl_ctxt) {
+ if (aapl_client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
+ conn->aapl_readdir_attr = true;
+
+ contxt_cnt++;
+ create_aapl_rsp_buf(rsp->Buffer +
+ le32_to_cpu(rsp->CreateContextsLength),
+ SMB2_CRTCTX_AAPL_FULL_SYNC,
+ aapl_req_bitmap);
+ le32_add_cpu(&rsp->CreateContextsLength,
+ conn->vals->create_aapl_size);
+ iov_len += conn->vals->create_aapl_size;
+ if (next_ptr)
+ *next_ptr = cpu_to_le32(next_off);
+ /* AAPL is last; next_ptr need not be updated */
}
if (contxt_cnt > 0) {
diff --git a/fs/smb/server/smb2pdu.h b/fs/smb/server/smb2pdu.h
index 70b1f630e..740358a2b 100644
--- a/fs/smb/server/smb2pdu.h
+++ b/fs/smb/server/smb2pdu.h
@@ -66,6 +66,66 @@ struct preauth_integrity_info {
/* Apple Defined Contexts */
#define SMB2_CREATE_AAPL "AAPL"
+/*
+ * Apple AAPL SMB2 extension -- kAAPL_SERVER_QUERY create context.
+ *
+ * Command code and bitmap values are the existing
+ * SMB2_CRTCTX_AAPL_* constants in fs/smb/common/smb2pdu.h (see Samba's
+ * libcli/smb/smb2_create_ctx.h, cited there).
+ *
+ * Omitting the model string when reply_bitmap includes
+ * SMB2_CRTCTX_AAPL_MODEL_INFO causes smbfs.kext to enter a broken
+ * disconnect path requiring a reboot.
+ *
+ * Layout: ccontext(16) + Name[4] + Pad[4] + cmd(4) + reserved(4) +
+ * reply_bitmap(8) + server_caps(8) + vol_caps(8)
+ * When MODEL_INFO requested, appended: pad2(4) + model_bytes(4) + UTF-16LE
+ */
+#define SMB2_CREATE_AAPL_LEN 4
+
+/*
+ * Server capability flags (server_caps field) -- SMB2_CRTCTX_AAPL_UNIX_BASED:
+ * prevents macOS Windows-compat mode (question-mark icons).
+ * SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE: enables server-side file copy via
+ * FSCTL_SRV_COPYCHUNK. SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR: inline
+ * FinderInfo per FIND entry, set when client also advertises the bit;
+ * format: EaSize=max_access, ShortName[0..7]=rfork_size,
+ * ShortName[8..23]=FinderInfo(16B), Reserved2=unix_mode.
+ */
+#define AAPL_SERVER_CAPS_KSMBD (SMB2_CRTCTX_AAPL_UNIX_BASED | \
+ SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE | \
+ SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR)
+
+/* Model string: up to 31 ASCII chars */
+#define AAPL_MODEL_MAX_CHARS 31
+#define AAPL_MODEL_UTF16_BYTES (AAPL_MODEL_MAX_CHARS * 2)
+
+/*
+ * Max AAPL response: header(24) + base data(32) + pad2(4) + model_bytes(4)
+ * + model(62), 8-byte aligned: ALIGN(126, 8) = 128 bytes.
+ */
+#define AAPL_RSP_MAX_SIZE 128
+
+/* AAPL server query request (client->server) */
+struct aapl_server_query_req {
+ __le32 cmd;
+ __le32 reserved;
+ __le64 req_bitmap;
+ __le64 client_caps;
+} __packed;
+
+struct create_aapl_rsp {
+ struct create_context_hdr ccontext;
+ __u8 Name[4];
+ __u8 Pad[4];
+ __le32 cmd;
+ __le32 reserved;
+ __le64 reply_bitmap;
+ __le64 server_caps;
+ __le64 vol_caps;
+ /* when MODEL_INFO requested: __le32 pad2; __le32 model_bytes; __le16 model[] */
+} __packed;
+
#define DURABLE_HANDLE_MAX_TIMEOUT 300000
struct create_alloc_size_req {
diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
index 0c581b962..bd58d3d0b 100644
--- a/fs/smb/server/transport_ipc.c
+++ b/fs/smb/server/transport_ipc.c
@@ -322,6 +322,15 @@ static int ipc_server_config_on_startup(struct ksmbd_startup_request *req)
goto out;
}
server_conf.share_fake_fscaps = req->share_fake_fscaps;
+
+ /* AAPL model string for Finder icon */
+ if (req->aapl_model[0])
+ strscpy(server_conf.aapl_model, req->aapl_model,
+ sizeof(server_conf.aapl_model));
+ else
+ strscpy(server_conf.aapl_model, "Xserve",
+ sizeof(server_conf.aapl_model));
+
ksmbd_init_domain(req->sub_auth);
if (req->smb2_max_read)
base-commit: 545c40084bb831530033265b654290ff7be5b090
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 2/6] ksmbd: synthesize empty AFP_AfpInfo xattr on first probe
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 ` 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
` (4 subsequent siblings)
6 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
From: Gael Blivet <gael.blivet@gmail.com>
Once a server advertises the AAPL COPYFILE capability, macOS requires
an AFP_AfpInfo stream on every file it looks at for Finder type/
creator/icon resolution. smb2_set_stream_name_xattr() currently
returns -EBADF (STATUS_OBJECT_NAME_NOT_FOUND) when a client opens
AFP_AfpInfo with FILE_OPEN disposition and the xattr doesn't exist
yet, which macOS treats as fatal for that file: Finder falls back to
showing a generic icon, and file operations that depend on succeeding
against this stream (e.g. Cmd+D duplication) fail.
Synthesize a 60-byte zeroed AFP_AfpInfo xattr (magic 0x00051607,
version 0x00020000, both big-endian per the AFP_AfpInfo wire format)
on first FILE_OPEN probe instead, matching Samba's vfs_fruit module.
type=0/creator=0 tells macOS to fall back to extension-based type
detection, which is correct for files with no explicit Finder
metadata. The synthesized xattr persists on disk, so this only pays
the extra write once per file; a later genuine write from macOS (e.g.
after the user assigns a custom icon) overwrites it normally.
Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
fs/smb/server/smb2pdu.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 9c70e97d8..8fed5fd6d 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -2864,6 +2864,30 @@ static noinline int smb2_set_stream_name_xattr(const struct path *path,
return 0;
if (fp->cdoption == FILE_OPEN_LE) {
+ if (!strcmp(stream_name, "AFP_AfpInfo") &&
+ test_share_config_flag(fp->tcon->share_conf,
+ KSMBD_SHARE_FLAG_TIME_MACHINE)) {
+ /*
+ * Synthesize an empty AFP_AfpInfo xattr on first access.
+ * type=0/creator=0 tells macOS to use the file extension
+ * for icon and type detection. Matches Samba vfs_fruit.
+ *
+ * Scoped to TIME_MACHINE shares, matching the rest of
+ * the AAPL series -- conn->is_aapl alone isn't a safe
+ * gate here, since the pre-existing narrow UniqueId=0
+ * path can also set it on ordinary, non-Time-Machine
+ * shares whenever a Mac client happens to negotiate
+ * AAPL there too.
+ */
+ static const u8 afpinfo_empty[60] = {
+ 0x00, 0x05, 0x16, 0x07, /* magic 0x00051607 BE */
+ 0x00, 0x02, 0x00, 0x00, /* version 0x00020000 BE */
+ };
+ rc = ksmbd_vfs_setxattr(idmap, path, xattr_stream_name,
+ (void *)afpinfo_empty,
+ sizeof(afpinfo_empty), 0, false);
+ return rc < 0 ? rc : 0;
+ }
ksmbd_debug(SMB, "XATTR stream name lookup failed: %d\n", rc);
return -EBADF;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 3/6] ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR negotiated
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
2026-07-09 0:06 ` [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED Gaël Blivet-Bailly
` (3 subsequent siblings)
6 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
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
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-09 0:06 [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Gaël Blivet-Bailly
` (2 preceding siblings ...)
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 ` Gaël Blivet-Bailly
2026-07-09 7:03 ` ChenXiaoSong
2026-07-09 9:24 ` ChenXiaoSong
2026-07-09 0:06 ` [PATCH v2 5/6] ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK Gaël Blivet-Bailly
` (2 subsequent siblings)
6 siblings, 2 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
From: Gael Blivet <gael.blivet@gmail.com>
smb2_notify() currently returns STATUS_NOT_IMPLEMENTED synchronously
for every CHANGE_NOTIFY request. Genuine SMB2 servers never complete a
CHANGE_NOTIFY spontaneously -- it's satisfied only by a real directory
change or with STATUS_NOTIFY_CLEANUP when the watched handle is
closed. macOS smbfs.kext depends on this deferred-completion contract:
receiving STATUS_NOT_IMPLEMENTED instead makes it hard-freeze on
unmount, since it never sees the cleanup it's waiting for.
Add a notify_pendings list on struct ksmbd_file (protected by the
existing f_lock) and a notify_entry list_head on struct ksmbd_work to
link onto it. smb2_notify() now replies STATUS_PENDING immediately and
queues a deferred STATUS_NOTIFY_CLEANUP response on the watched
handle; __ksmbd_close_fd() drains and sends any pending notifications
when the handle is actually closed. The drain splices the list out
under fp->f_lock first, then processes the detached copy without the
lock -- smb2_notify() on another connection can be adding to the same
list at the same time a close happens on this one, and
ksmbd_conn_write() can sleep (it takes the connection's write mutex),
so it must not be called while the spinlock is held.
Also handle the FileId=FFFF...FFFF share-root sentinel that macOS
backupd sends to watch for changes without holding an open handle --
without an immediate STATUS_PENDING/STATUS_NOTIFY_CLEANUP reply here,
backupd aborts Time Machine setup with STATUS_FILE_CLOSED.
Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
v1 -> v2: Addressed the CANCEL deadlock concern raised in review:
smb2_notify_cancel_fn() now does only lock-protected list/state
manipulation inline under conn->request_lock (matching every other
cancel_fn in this file), deferring the one sleeping operation
(sending STATUS_CANCELLED via ksmbd_conn_write()) to a workqueue.
The close-time drain in __ksmbd_inode_close() changed to match: it
pops one entry at a time via list_del_init() under fp->f_lock rather
than a bulk list_splice_init(), so a racing cancel_fn can tell if it
lost the race the same way, and both sides can never free the same
ksmbd_work.
fs/smb/server/ksmbd_work.c | 1 +
fs/smb/server/ksmbd_work.h | 2 +
fs/smb/server/smb2pdu.c | 226 ++++++++++++++++++++++++++++++++++++-
fs/smb/server/vfs_cache.c | 48 ++++++++
fs/smb/server/vfs_cache.h | 6 +
5 files changed, 280 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/ksmbd_work.c b/fs/smb/server/ksmbd_work.c
index e2c2f4526..97502273a 100644
--- a/fs/smb/server/ksmbd_work.c
+++ b/fs/smb/server/ksmbd_work.c
@@ -56,6 +56,7 @@ struct ksmbd_work *ksmbd_alloc_work_struct(void)
INIT_LIST_HEAD(&work->request_entry);
INIT_LIST_HEAD(&work->async_request_entry);
INIT_LIST_HEAD(&work->fp_entry);
+ INIT_LIST_HEAD(&work->notify_entry);
INIT_LIST_HEAD(&work->aux_read_list);
work->iov_alloc_cnt = ARRAY_SIZE(work->iov_inline);
work->iov = work->iov_inline;
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 88104f0cf..50c4aa779 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -104,6 +104,8 @@ struct ksmbd_work {
/* List head at conn->async_requests */
struct list_head async_request_entry;
struct list_head fp_entry;
+ /* List head at ksmbd_file->notify_pendings */
+ struct list_head notify_entry;
};
/**
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 120c2c80d..ed5b132b8 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -10070,6 +10070,88 @@ int smb2_oplock_break(struct ksmbd_work *work)
return 0;
}
+/*
+ * Cancel handler for a deferred CHANGE_NOTIFY. Races against
+ * __ksmbd_close_fd()'s notify_pendings drain (vfs_cache.c), which can run
+ * concurrently on a different connection closing the same handle -- only
+ * one of the two may claim and free in_work, so both sides check
+ * list_empty() under fp->f_lock before touching it (list_del_init()
+ * leaves a node empty, so whichever side removes it first is the owner;
+ * the loser must not touch in_work again, since the winner may already be
+ * freeing it).
+ *
+ * smb2_cancel() holds conn->request_lock (a spinlock) for the entire
+ * time it walks conn->async_requests and calls this function -- so this
+ * runs with preemption disabled and must not sleep or re-acquire that
+ * same lock. release_async_work() does both (it takes conn->request_lock
+ * itself, and frees things that can involve sleeping paths), so calling
+ * it from here would self-deadlock the very thread processing the
+ * client's CANCEL command. ksmbd_conn_write() can also sleep (it takes
+ * conn's write mutex). So: do only the non-sleeping, no-relock cleanup
+ * inline here (the async_requests removal itself is safe without
+ * re-locking, since the caller already holds that lock), and defer the
+ * actual response send + work-struct free to a workqueue, matching the
+ * minimal, non-blocking style of the existing smb2_remove_blocked_lock()
+ * cancel_fn (which only wakes a waiter, never sends network data itself).
+ */
+struct notify_cancel_ctx {
+ struct work_struct work;
+ struct ksmbd_work *in_work;
+};
+
+static void smb2_notify_cancel_deferred(struct work_struct *w)
+{
+ struct notify_cancel_ctx *ctx =
+ container_of(w, struct notify_cancel_ctx, work);
+ struct ksmbd_work *in_work = ctx->in_work;
+ struct smb2_hdr *in_hdr;
+
+ in_hdr = smb_get_msg(in_work->response_buf);
+ in_hdr->Status = STATUS_CANCELLED;
+ ksmbd_conn_write(in_work);
+ ksmbd_free_work_struct(in_work);
+ kfree(ctx);
+}
+
+static void smb2_notify_cancel_fn(void **argv)
+{
+ struct ksmbd_work *in_work = (struct ksmbd_work *)argv[0];
+ struct ksmbd_file *fp = (struct ksmbd_file *)argv[1];
+ struct ksmbd_conn *conn = in_work->conn;
+ struct notify_cancel_ctx *ctx;
+ bool claimed;
+
+ spin_lock(&fp->f_lock);
+ claimed = !list_empty(&in_work->notify_entry);
+ if (claimed)
+ list_del_init(&in_work->notify_entry);
+ spin_unlock(&fp->f_lock);
+
+ if (!claimed)
+ return;
+
+ /* conn->request_lock is already held by the caller (smb2_cancel()). */
+ list_del_init(&in_work->async_request_entry);
+ in_work->asynchronous = false;
+ in_work->cancel_fn = NULL;
+ kfree(in_work->cancel_argv);
+ in_work->cancel_argv = NULL;
+ if (in_work->async_id) {
+ ksmbd_release_id(&conn->async_ida, in_work->async_id);
+ in_work->async_id = 0;
+ }
+
+ ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC);
+ if (!ctx) {
+ /* Can't defer the response -- free without sending one. */
+ ksmbd_free_work_struct(in_work);
+ return;
+ }
+ ctx->in_work = in_work;
+ INIT_WORK(&ctx->work, smb2_notify_cancel_deferred);
+ schedule_work(&ctx->work);
+}
+
/**
* smb2_notify() - handler for smb2 notify request
* @work: smb work containing notify command buffer
@@ -10080,6 +10162,9 @@ int smb2_notify(struct ksmbd_work *work)
{
struct smb2_change_notify_req *req;
struct smb2_change_notify_rsp *rsp;
+ struct ksmbd_work *in_work;
+ struct smb2_hdr *in_hdr;
+ struct ksmbd_file *fp;
ksmbd_debug(SMB, "Received smb2 notify\n");
@@ -10094,9 +10179,144 @@ int smb2_notify(struct ksmbd_work *work)
return -EIO;
}
- smb2_set_err_rsp(work);
- rsp->hdr.Status = STATUS_NOT_IMPLEMENTED;
- return -EOPNOTSUPP;
+ /*
+ * macOS backupd sends CHANGE_NOTIFY with FileId=FFFF...FFFF (share-root
+ * sentinel) to watch for changes on the share root without holding an
+ * open handle. Respond STATUS_PENDING + STATUS_NOTIFY_CLEANUP immediately;
+ * without this, backupd aborts Time Machine setup on STATUS_FILE_CLOSED.
+ */
+ if (req->VolatileFileId == SMB2_NO_FID &&
+ req->PersistentFileId == SMB2_NO_FID) {
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work || allocate_interim_rsp_buf(in_work)) {
+ if (in_work)
+ ksmbd_free_work_struct(in_work);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ if (setup_async_work(work, NULL, NULL)) {
+ ksmbd_free_work_struct(in_work);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ smb2_send_interim_resp(work, STATUS_PENDING);
+ in_work->conn = work->conn;
+ in_hdr = smb_get_msg(in_work->response_buf);
+ memcpy(in_hdr, ksmbd_resp_buf_next(work),
+ __SMB2_HEADER_STRUCTURE_SIZE);
+ in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+ in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
+ smb2_set_err_rsp(in_work);
+ in_hdr->Status = STATUS_NOTIFY_CLEANUP;
+ in_work->async_id = work->async_id;
+ work->async_id = 0;
+ release_async_work(work);
+ ksmbd_conn_write(in_work);
+ ksmbd_free_work_struct(in_work);
+ work->send_no_response = 1;
+ return 0;
+ }
+
+ /*
+ * KSMBD does not implement a real change-notification backend.
+ * Genuine SMB2 servers (and macOS smbfs) never complete a
+ * CHANGE_NOTIFY spontaneously: it is satisfied only by a real
+ * directory change, or with STATUS_NOTIFY_CLEANUP when the watched
+ * handle is closed. Completing it early (e.g. on a timer) makes
+ * Finder treat the cleanup as "directory changed" and re-enumerate
+ * the directory forever, leaving items unopenable. Returning
+ * STATUS_NOT_IMPLEMENTED here (like stock ksmbd) makes macOS smbfs
+ * hard-freeze on unmount, so this must stay deferred.
+ */
+ fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
+ if (!fp) {
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ in_work = ksmbd_alloc_work_struct();
+ if (!in_work || allocate_interim_rsp_buf(in_work)) {
+ if (in_work)
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+ /*
+ * in_work is synthetic (not from the normal request-receiving
+ * pipeline), so it has no request_buf of its own. It gets registered
+ * into conn->async_requests below, and smb2_cancel() unconditionally
+ * computes smb_get_msg(iter->request_buf) for every entry in that
+ * list while searching for a match -- give it its own small buffer
+ * (not an alias of response_buf: ksmbd_free_work_struct() kvfree()s
+ * both separately, so aliasing them would double-free) so that stays
+ * a harmless read instead of a near-NULL dereference.
+ */
+ in_work->request_buf = kzalloc(MAX_CIFS_SMALL_BUFFER_SIZE, KSMBD_DEFAULT_GFP);
+ if (!in_work->request_buf) {
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ if (setup_async_work(work, NULL, NULL)) {
+ ksmbd_free_work_struct(in_work);
+ ksmbd_fd_put(work, fp);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ smb2_set_err_rsp(work);
+ return 0;
+ }
+
+ smb2_send_interim_resp(work, STATUS_PENDING);
+
+ in_work->conn = work->conn;
+ in_hdr = smb_get_msg(in_work->response_buf);
+ memcpy(in_hdr, ksmbd_resp_buf_next(work), __SMB2_HEADER_STRUCTURE_SIZE);
+ in_hdr->Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+ in_hdr->Id.AsyncId = cpu_to_le64(work->async_id);
+ smb2_set_err_rsp(in_work);
+ in_hdr->Status = STATUS_NOTIFY_CLEANUP;
+
+ /*
+ * Transfer ownership of the async id to in_work; it stays reserved
+ * until in_work is freed after the deferred response is sent on
+ * close, so it can't be reused for an unrelated async response.
+ */
+ in_work->async_id = work->async_id;
+ work->async_id = 0;
+ release_async_work(work);
+
+ /*
+ * work itself is about to be recycled by the normal request-processing
+ * pipeline, so it can't stay the target of a future CANCEL -- register
+ * in_work instead, reusing the same async_id, so a client-sent CANCEL
+ * for this notify actually finds something to cancel instead of
+ * silently doing nothing until the handle eventually closes.
+ */
+ in_work->asynchronous = true;
+ in_work->cancel_argv = kmalloc_array(2, sizeof(void *), KSMBD_DEFAULT_GFP);
+ if (in_work->cancel_argv) {
+ in_work->cancel_argv[0] = in_work;
+ in_work->cancel_argv[1] = fp;
+ in_work->cancel_fn = smb2_notify_cancel_fn;
+ }
+ spin_lock(&work->conn->request_lock);
+ list_add_tail(&in_work->async_request_entry, &work->conn->async_requests);
+ spin_unlock(&work->conn->request_lock);
+
+ spin_lock(&fp->f_lock);
+ list_add_tail(&in_work->notify_entry, &fp->notify_pendings);
+ spin_unlock(&fp->f_lock);
+
+ ksmbd_fd_put(work, fp);
+ work->send_no_response = 1;
+ return 0;
}
/**
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 2543dd7e8..89a43ca18 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -529,6 +529,7 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
{
struct file *filp;
struct ksmbd_lock *smb_lock, *tmp_lock;
+ struct ksmbd_work *cn_work;
fd_limit_close();
ksmbd_remove_durable_fd(fp);
@@ -561,6 +562,52 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
kfree(smb_lock);
}
+ /*
+ * Complete any CHANGE_NOTIFY left pending on this handle now that
+ * it is closed. KSMBD never completes CHANGE_NOTIFY spontaneously
+ * (no real change-notification backend), only on close -- matching
+ * genuine SMB2/macOS smbfs semantics and avoiding the Finder
+ * "directory changed, re-enumerate everything" loop.
+ *
+ * smb2_notify() on another connection can be adding to
+ * notify_pendings under fp->f_lock at the same time this handle is
+ * closed, and a client-sent CANCEL can concurrently be racing to
+ * claim the same entry via smb2_notify_cancel_fn() (smb2pdu.c).
+ * Pop one entry at a time under the lock via list_del_init() rather
+ * than a bulk list_splice_init(): list_del_init() leaves the node
+ * self-linked ("empty"), which is what the cancel path checks under
+ * the same lock to tell whether it lost the race -- a bulk splice
+ * would instead relink every entry into a shared local list, so an
+ * entry claimed here would still read as "not empty" to a racing
+ * cancel_fn, and both sides could end up freeing the same work.
+ * ksmbd_conn_write() can sleep (it takes conn's write mutex), so it
+ * must not be called while fp->f_lock is held -- release the lock
+ * before processing each popped entry, then reacquire it for the
+ * next.
+ */
+ for (;;) {
+ spin_lock(&fp->f_lock);
+ if (list_empty(&fp->notify_pendings)) {
+ spin_unlock(&fp->f_lock);
+ break;
+ }
+ cn_work = list_first_entry(&fp->notify_pendings,
+ struct ksmbd_work, notify_entry);
+ list_del_init(&cn_work->notify_entry);
+ spin_unlock(&fp->f_lock);
+
+ ksmbd_conn_write(cn_work);
+ /*
+ * release_async_work() removes cn_work from
+ * conn->async_requests, frees cancel_argv, and releases+zeroes
+ * async_id -- all needed before ksmbd_free_work_struct(), which
+ * only releases async_id itself if still nonzero (i.e. if this
+ * hadn't already been done).
+ */
+ release_async_work(cn_work);
+ ksmbd_free_work_struct(cn_work);
+ }
+
/*
* Drop fp's strong reference on conn (taken in ksmbd_open_fd() /
* ksmbd_reopen_durable_fd()). Durable fps that reached the
@@ -1080,6 +1127,7 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)
INIT_LIST_HEAD(&fp->blocked_works);
INIT_LIST_HEAD(&fp->node);
INIT_LIST_HEAD(&fp->lock_list);
+ INIT_LIST_HEAD(&fp->notify_pendings);
spin_lock_init(&fp->f_lock);
mutex_init(&fp->readdir_lock);
atomic_set(&fp->refcount, 1);
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 111a4e315..2d722c43c 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -135,6 +135,12 @@ struct ksmbd_file {
bool is_posix_ctxt;
struct durable_owner owner;
+
+ /*
+ * Pending CHANGE_NOTIFY completions for this handle, sent with
+ * STATUS_NOTIFY_CLEANUP when the handle is closed.
+ */
+ struct list_head notify_pendings;
};
static inline void set_ctx_actor(struct dir_context *ctx,
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 5/6] ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK
2026-07-09 0:06 [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Gaël Blivet-Bailly
` (3 preceding siblings ...)
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 0:06 ` 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
6 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
From: Gael Blivet <gael.blivet@gmail.com>
fsctl_copychunk() treats FSCTL_SRV_COPYCHUNK with ChunkCount=0 as the
standard SMB2 "query my copy limits, don't copy anything" request and
returns success without ever looking up the file handles. That's
correct for compliant SMB2 clients, but macOS Finder's Cmd+D duplicate
sends ChunkCount=0 expecting the server to copy the whole file/stream
-- so duplicated files are left at their just-created 0 bytes while
the client reports success.
Scope the full-copy fallback to AAPL-negotiated connections on a
Time Machine share (conn->is_aapl && KSMBD_SHARE_FLAG_TIME_MACHINE)
only, so standard non-Apple SMB2 clients, and AAPL-negotiated clients
on ordinary shares, keep the spec-correct query-limits behavior
unchanged.
Re-derived against the copychunk rework in
commit 8afe741dbd67 ("ksmbd: support copychunk for alternate data streams")
and commit 165e5b86fd2c ("ksmbd: handle AAPL stream copy length mismatch"):
both streams and regular files now share a single chunk_count == 0
fast path added right after src_file_size is computed, reusing the
same buffered-copy helper and vfs_copy_file_range()/COPY_FILE_SPLICE
fallback the existing per-chunk loop already uses, rather than the
separate xattr-specific get/setxattr path this used before that
rework.
ChunksWritten/ChunkBytesWritten are 0 in the response: this is a
synthesized whole-file copy, not a response to any chunk descriptor
the client actually sent (it sent none), so there's no real chunk to
report the count/size of. Only TotalBytesWritten is meaningful here.
Signed-off-by: Gael Blivet <gael.blivet@gmail.com>
---
v1 -> v2: Re-derived against the copychunk rework that landed upstream
since v1 (commit 8afe741dbd67, commit 165e5b86fd2c), which replaced
the separate xattr-specific stream-copy path this used with a unified
chunk_count == 0 fast path shared by streams and regular files. Also
fixed ChunksWritten/ChunkBytesWritten in the response: v1 reported
ChunksWritten=1 and a nonzero ChunkBytesWritten, describing a chunk
the client never sent (it sent zero); now both are 0, matching a
value actually validated against a real client.
fs/smb/server/smb2pdu.c | 47 +++++++++++++++++++++++---------
fs/smb/server/vfs.c | 59 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 93 insertions(+), 13 deletions(-)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index ed5b132b8..612f8c8ec 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -8934,23 +8934,44 @@ static int fsctl_copychunk(struct ksmbd_work *work,
cpu_to_le32(ksmbd_server_side_copy_max_total_size());
chunk_count = le32_to_cpu(ci_req->ChunkCount);
- if (chunk_count == 0)
+ /*
+ * ChunkCount=0 is the standard SMB2 "query my copy limits" request
+ * (no data copied) -- but macOS Finder's Cmd+D duplicate sends
+ * FSCTL_SRV_COPYCHUNK with ChunkCount=0 meaning "copy the whole
+ * file", relying on the AAPL-negotiated server to do a full copy
+ * instead. Keep the standard no-op behavior for everyone else.
+ *
+ * Gate on the TIME_MACHINE share flag, not just conn->is_aapl:
+ * that flag alone has ambiguous provenance -- the pre-existing
+ * narrow UniqueId=0 path can also set it on ordinary,
+ * non-Time-Machine shares, and this series' stated design keeps
+ * every AAPL-driven behavior opt-in per share.
+ */
+ if (chunk_count == 0 &&
+ !(work->conn->is_aapl &&
+ test_share_config_flag(work->tcon->share_conf,
+ KSMBD_SHARE_FLAG_TIME_MACHINE)))
goto out;
total_size_written = 0;
+ i = 0;
- /* verify the SRV_COPYCHUNK_COPY packet */
- if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
- input_count < struct_size(ci_req, Chunks, chunk_count)) {
- rsp->hdr.Status = STATUS_INVALID_PARAMETER;
- return -EINVAL;
- }
+ if (chunk_count) {
+ /* verify the SRV_COPYCHUNK_COPY packet */
+ if (chunk_count > ksmbd_server_side_copy_max_chunk_count() ||
+ input_count < struct_size(ci_req, Chunks, chunk_count)) {
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ return -EINVAL;
+ }
- chunks = &ci_req->Chunks[0];
- for (i = 0; i < chunk_count; i++) {
- if (le32_to_cpu(chunks[i].Length) == 0 ||
- le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
- break;
- total_size_written += le32_to_cpu(chunks[i].Length);
+ chunks = &ci_req->Chunks[0];
+ for (i = 0; i < chunk_count; i++) {
+ if (le32_to_cpu(chunks[i].Length) == 0 ||
+ le32_to_cpu(chunks[i].Length) > ksmbd_server_side_copy_max_chunk_size())
+ break;
+ total_size_written += le32_to_cpu(chunks[i].Length);
+ }
+ } else {
+ chunks = &ci_req->Chunks[0];
}
if (i < chunk_count ||
diff --git a/fs/smb/server/vfs.c b/fs/smb/server/vfs.c
index 7f7e73915..61d02322c 100644
--- a/fs/smb/server/vfs.c
+++ b/fs/smb/server/vfs.c
@@ -1858,6 +1858,65 @@ int ksmbd_vfs_copy_file_ranges(struct ksmbd_work *work,
src_file_size = i_size_read(file_inode(src_fp->filp));
}
+ /*
+ * macOS Finder's Cmd+D duplicate sends FSCTL_SRV_COPYCHUNK with
+ * ChunkCount=0 meaning "copy the whole file/stream", not the
+ * standard SMB2 "query my copy limits, no data" semantics --
+ * fsctl_copychunk() only reaches here with chunk_count == 0 for
+ * AAPL-negotiated connections, so this doesn't affect compliant
+ * non-Apple clients. Without this, the destination stays at its
+ * just-created 0 bytes / empty stream: the for loop below is a
+ * no-op when chunk_count is 0, since it never has an iteration to
+ * treat as "copy everything".
+ */
+ if (chunk_count == 0 && work->conn->is_aapl) {
+ loff_t off = 0;
+
+ while (off < src_file_size) {
+ size_t remaining = src_file_size - off;
+ ssize_t copied;
+
+ /* Same source/destination offset here: an in-place,
+ * same-inode copy at matching offsets is a degenerate
+ * no-op range, not a real overlap, but vfs_copy_file_range
+ * still doesn't support streams -- route those (and the
+ * same-inode case defensively) through the buffered path.
+ */
+ if (ksmbd_stream_fd(src_fp) || ksmbd_stream_fd(dst_fp) ||
+ file_inode(src_fp->filp) == file_inode(dst_fp->filp)) {
+ copied = ksmbd_vfs_copy_file_range_buffered(work, src_fp, dst_fp,
+ off, off, remaining);
+ } else {
+ copied = vfs_copy_file_range(src_fp->filp, off,
+ dst_fp->filp, off,
+ remaining, 0);
+ if (copied == -EOPNOTSUPP || copied == -EXDEV)
+ copied = vfs_copy_file_range(src_fp->filp, off,
+ dst_fp->filp, off,
+ remaining,
+ COPY_FILE_SPLICE);
+ }
+ if (copied < 0)
+ return copied;
+ if (copied == 0)
+ break;
+ off += copied;
+ }
+
+ /*
+ * This is a synthesized whole-file copy, not a response to
+ * any chunk descriptor the client actually sent (it sent
+ * none -- chunk_count is 0). Report zero chunks/chunk-bytes
+ * rather than inventing a chunk that doesn't correspond to
+ * anything in the request; only total_size_written (bytes
+ * actually copied) is meaningful here.
+ */
+ *chunk_count_written = 0;
+ *chunk_size_written = 0;
+ *total_size_written = off;
+ return 0;
+ }
+
for (i = 0; i < chunk_count; i++) {
bool stream_len_mismatch = false;
size_t copy_len;
--
2.43.0
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 6/6] ksmbd: add AAPL READDIR_ATTR V2 support
2026-07-09 0:06 [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Gaël Blivet-Bailly
` (4 preceding siblings ...)
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
2026-07-09 13:15 ` [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Namjae Jeon
6 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet-Bailly @ 2026-07-09 0:06 UTC (permalink / raw)
To: Namjae Jeon; +Cc: Gael Blivet, linux-cifs
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
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
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
1 sibling, 0 replies; 16+ messages in thread
From: ChenXiaoSong @ 2026-07-09 7:03 UTC (permalink / raw)
To: Gaël Blivet-Bailly, Namjae Jeon; +Cc: linux-cifs
It looks like this patch from Gaël has already implemented notify
pending and cancel support. I will rebase on this patch.
I will send the remaining ksmbd notify patches soon.
On 7/9/26 08:06, Gaël Blivet-Bailly wrote:
> From: Gael Blivet<gael.blivet@gmail.com>
>
> smb2_notify() currently returns STATUS_NOT_IMPLEMENTED synchronously
> for every CHANGE_NOTIFY request. Genuine SMB2 servers never complete a
> CHANGE_NOTIFY spontaneously -- it's satisfied only by a real directory
> change or with STATUS_NOTIFY_CLEANUP when the watched handle is
> closed. macOS smbfs.kext depends on this deferred-completion contract:
> receiving STATUS_NOT_IMPLEMENTED instead makes it hard-freeze on
> unmount, since it never sees the cleanup it's waiting for.
>
> Add a notify_pendings list on struct ksmbd_file (protected by the
> existing f_lock) and a notify_entry list_head on struct ksmbd_work to
> link onto it. smb2_notify() now replies STATUS_PENDING immediately and
> queues a deferred STATUS_NOTIFY_CLEANUP response on the watched
> handle; __ksmbd_close_fd() drains and sends any pending notifications
> when the handle is actually closed. The drain splices the list out
> under fp->f_lock first, then processes the detached copy without the
> lock -- smb2_notify() on another connection can be adding to the same
> list at the same time a close happens on this one, and
> ksmbd_conn_write() can sleep (it takes the connection's write mutex),
> so it must not be called while the spinlock is held.
>
> Also handle the FileId=FFFF...FFFF share-root sentinel that macOS
> backupd sends to watch for changes without holding an open handle --
> without an immediate STATUS_PENDING/STATUS_NOTIFY_CLEANUP reply here,
> backupd aborts Time Machine setup with STATUS_FILE_CLOSED.
>
> Signed-off-by: Gael Blivet<gael.blivet@gmail.com>
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
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
1 sibling, 1 reply; 16+ messages in thread
From: ChenXiaoSong @ 2026-07-09 9:24 UTC (permalink / raw)
To: Gaël Blivet-Bailly, Namjae Jeon; +Cc: linux-cifs
Hi Gaël and Namjae,
Do you think it would be better to send STATUS_CANCELLED in smb2_notify()?
On 7/9/26 08:06, Gaël Blivet-Bailly wrote:
> +static void smb2_notify_cancel_deferred(struct work_struct *w)
> +{
> + struct notify_cancel_ctx *ctx =
> + container_of(w, struct notify_cancel_ctx, work);
> + struct ksmbd_work *in_work = ctx->in_work;
> + struct smb2_hdr *in_hdr;
> +
> + in_hdr = smb_get_msg(in_work->response_buf);
> + in_hdr->Status = STATUS_CANCELLED;
> + ksmbd_conn_write(in_work);
> + ksmbd_free_work_struct(in_work);
> + kfree(ctx);
> +}
> +
> +static void smb2_notify_cancel_fn(void **argv)
> +{
> + ...
> + INIT_WORK(&ctx->work, smb2_notify_cancel_deferred);
> + schedule_work(&ctx->work);
> +}
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support
2026-07-09 0:06 [PATCH v2 0/6] ksmbd: add macOS Time Machine / AAPL SMB2 extension support Gaël Blivet-Bailly
` (5 preceding siblings ...)
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 ` Namjae Jeon
6 siblings, 0 replies; 16+ messages in thread
From: Namjae Jeon @ 2026-07-09 13:15 UTC (permalink / raw)
To: Gaël Blivet-Bailly; +Cc: linux-cifs
On Thu, Jul 9, 2026 at 9:07 AM Gaël Blivet-Bailly <gael.blivet@gmail.com> wrote:
>
> From: Gael Blivet <gael.blivet@gmail.com>
>
> Link to v1:
> https://lore.kernel.org/linux-cifs/20260707100129.47563-1-g.blivet@me.com/
>
> v2 addresses the CHANGE_NOTIFY CANCEL concern raised on v1:
> smb2_cancel() calls each async request's cancel_fn() while holding
> conn->request_lock, so cancel_fn must not sleep. Patch 4 now splits
> this into inline lock-protected list/state manipulation (matching
> every other cancel_fn in this file) plus a workqueue-deferred send of
> the actual STATUS_CANCELLED response, which is the one part that
> sleeps.
>
> Separately: the corresponding ksmbd-tools userspace patch (aapl_model
> field, "time machine"/"aapl model" config options) has been sent to
> that project directly, since it's a different repository from this
> kernel series.
>
> New since v1: patch 6, AAPL READDIR_ATTR V2 support -- real macOS
> clients negotiate V2 over V1 when both are offered, so v1-only left a
> gap for current macOS versions.
>
> Also new: patches 1, 3, and 6 are now additionally cross-checked
> against Apple's published SMBClient kernel source
> (apple-oss-distributions/SMBClient, smb_smb_2.c) -- everything matched
> the network-derived understanding v1 was built on, except one
> inaccurate assumption in patch 3, now corrected.
>
> Patch 5 also gets a correctness fix: the synthesized ChunkCount=0
> full-copy response was reporting ChunksWritten=1/ChunkBytesWritten=
> <file size>, describing a chunk the client never actually sent (it
> sent zero). Now reports 0/0, matching what's actually been validated
> against a real client; only TotalBytesWritten carries a value.
>
> Patches 1-4 are otherwise unchanged in substance from v1, re-derived
> against the current tree (this branch has moved since v1).
>
> Gael Blivet (6):
> ksmbd: add Apple AAPL kAAPL_SERVER_QUERY create context support
> ksmbd: synthesize empty AFP_AfpInfo xattr on first probe
> ksmbd: send inline FinderInfo in FIND responses when READDIR_ATTR
> negotiated
> ksmbd: defer CHANGE_NOTIFY completion instead of
> STATUS_NOT_IMPLEMENTED
> ksmbd: implement full-file copy for AAPL ChunkCount=0 COPYCHUNK
> ksmbd: add AAPL READDIR_ATTR V2 support
Applied them to #ksmbd-for-next-next.
Thanks!
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-09 9:24 ` ChenXiaoSong
@ 2026-07-10 5:59 ` Gaël Blivet
2026-07-10 6:48 ` ChenXiaoSong
0 siblings, 1 reply; 16+ messages in thread
From: Gaël Blivet @ 2026-07-10 5:59 UTC (permalink / raw)
To: ChenXiaoSong; +Cc: Namjae Jeon, linux-cifs
Hi ChenXiaoSong,
Because cancel_fn runs under conn->request_lock, smb2_cancel() holds it as a spinlock while walking async_requests, so cancel_fn can’t sleep. ksmbd_conn_write() takes conn->srv_mutex and can block on the actual socket write too, so calling it there directly would mean sleeping under a spinlock.
smb2_notify_cancel_fn() only does the non-sleeping parts inline (list removal, freeing cancel_argv, releasing the async id), same as smb2_remove_blocked_lock() elsewhere in this file, and pushes the actual response send to a workqueue.
> Le 9 juil. 2026 à 11:24, ChenXiaoSong <chenxiaosong@chenxiaosong.com> a écrit :
>
> Hi Gaël and Namjae,
>
> Do you think it would be better to send STATUS_CANCELLED in smb2_notify()?
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-10 5:59 ` Gaël Blivet
@ 2026-07-10 6:48 ` ChenXiaoSong
2026-07-10 7:04 ` ChenXiaoSong
0 siblings, 1 reply; 16+ messages in thread
From: ChenXiaoSong @ 2026-07-10 6:48 UTC (permalink / raw)
To: Gaël Blivet; +Cc: Namjae Jeon, linux-cifs
Hi Gaël,
Thanks for your patches.
I mean sending STATUS_CANCELLED in smb2_notify() (not in
smb2_notify_cancel_fn()). The event notification and STATUS_CANCELLED
response should be handled in smb2_notify().
You can refer to the implementation of smb2_lock():
```
int smb2_lock()
{
smb2_send_interim_resp(work, STATUS_PENDING);
ksmbd_vfs_posix_lock_wait(flock); // woken up by
smb2_remove_blocked_lock()
smb2_send_interim_resp(work, STATUS_CANCELLED);
work->send_no_response = 1
}
```
I have already implemented the complete notify feature, but since my
patch series is quite large, I still need to make some changes and split
it into smaller patches that are easier to review.
On 7/10/26 13:59, Gaël Blivet wrote:
> Because cancel_fn runs under conn->request_lock, smb2_cancel() holds it as a spinlock while walking async_requests, so cancel_fn can’t sleep. ksmbd_conn_write() takes conn->srv_mutex and can block on the actual socket write too, so calling it there directly would mean sleeping under a spinlock.
>
> smb2_notify_cancel_fn() only does the non-sleeping parts inline (list removal, freeing cancel_argv, releasing the async id), same as smb2_remove_blocked_lock() elsewhere in this file, and pushes the actual response send to a workqueue.
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-10 6:48 ` ChenXiaoSong
@ 2026-07-10 7:04 ` ChenXiaoSong
2026-07-10 8:54 ` Gaël Blivet
0 siblings, 1 reply; 16+ messages in thread
From: ChenXiaoSong @ 2026-07-10 7:04 UTC (permalink / raw)
To: Gaël Blivet; +Cc: Namjae Jeon, linux-cifs
Would it be better to do it like the following?
```
int smb2_notify()
{
setup_async_work(..., smb2_notify_cancel_fn, ...);
smb2_send_interim_resp(work, STATUS_PENDING);
wait_for_completion_interruptible(); // woken up by
smb2_notify_cancel_fn()
if (work->state == KSMBD_WORK_CANCELLED) {
smb2_send_interim_resp(work, STATUS_CANCELLED);
work->send_no_response = 1;
goto out;
}
// Add my implementation of notification events here.
}
```
On 7/10/26 14:48, ChenXiaoSong wrote:
> Hi Gaël,
>
> Thanks for your patches.
>
> I mean sending STATUS_CANCELLED in smb2_notify() (not in
> smb2_notify_cancel_fn()). The event notification and STATUS_CANCELLED
> response should be handled in smb2_notify().
>
> You can refer to the implementation of smb2_lock():
>
> ```
> int smb2_lock()
> {
> smb2_send_interim_resp(work, STATUS_PENDING);
> ksmbd_vfs_posix_lock_wait(flock); // woken up by
> smb2_remove_blocked_lock()
>
> smb2_send_interim_resp(work, STATUS_CANCELLED);
> work->send_no_response = 1
> }
> ```
>
> I have already implemented the complete notify feature, but since my
> patch series is quite large, I still need to make some changes and split
> it into smaller patches that are easier to review.
>
> On 7/10/26 13:59, Gaël Blivet wrote:
>> Because cancel_fn runs under conn->request_lock, smb2_cancel() holds
>> it as a spinlock while walking async_requests, so cancel_fn can’t
>> sleep. ksmbd_conn_write() takes conn->srv_mutex and can block on the
>> actual socket write too, so calling it there directly would mean
>> sleeping under a spinlock.
>>
>> smb2_notify_cancel_fn() only does the non-sleeping parts inline (list
>> removal, freeing cancel_argv, releasing the async id), same as
>> smb2_remove_blocked_lock() elsewhere in this file, and pushes the
>> actual response send to a workqueue.
>
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-10 7:04 ` ChenXiaoSong
@ 2026-07-10 8:54 ` Gaël Blivet
2026-07-10 9:15 ` ChenXiaoSong
0 siblings, 1 reply; 16+ messages in thread
From: Gaël Blivet @ 2026-07-10 8:54 UTC (permalink / raw)
To: ChenXiaoSong; +Cc: Namjae Jeon, linux-cifs
Hi ChenXiaoSong,
Makes sense, sorry for the misunderstanding. Could be cleaner, but doesn't wait_for_completion_interruptible() still block a worker thread from ksmbd_wq for as long as the watch stays open?
Happy to help align the merged patch with your series once it's up.
> Le 10 juil. 2026 à 09:04, ChenXiaoSong <chenxiaosong@chenxiaosong.com> a écrit :
>
> Would it be better to do it like the following?
>
> ```
> int smb2_notify()
> {
> setup_async_work(..., smb2_notify_cancel_fn, ...);
>
> smb2_send_interim_resp(work, STATUS_PENDING);
> wait_for_completion_interruptible(); // woken up by smb2_notify_cancel_fn()
>
> if (work->state == KSMBD_WORK_CANCELLED) {
> smb2_send_interim_resp(work, STATUS_CANCELLED);
> work->send_no_response = 1;
> goto out;
> }
>
> // Add my implementation of notification events here.
> }
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-10 8:54 ` Gaël Blivet
@ 2026-07-10 9:15 ` ChenXiaoSong
2026-07-10 13:28 ` Gaël Blivet
0 siblings, 1 reply; 16+ messages in thread
From: ChenXiaoSong @ 2026-07-10 9:15 UTC (permalink / raw)
To: Gaël Blivet; +Cc: Namjae Jeon, linux-cifs
Hi Gaël,
Yes, before the cancel response or event notification response is sent,
the notify request has not completed yet, so the worker will remain waiting.
If you find that this approach causes any issues, we can refactor it and
use a better solution.
On 7/10/26 16:54, Gaël Blivet wrote:
> Makes sense, sorry for the misunderstanding. Could be cleaner, but doesn't wait_for_completion_interruptible() still block a worker thread from ksmbd_wq for as long as the watch stays open?
>
> Happy to help align the merged patch with your series once it's up.
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/6] ksmbd: defer CHANGE_NOTIFY completion instead of STATUS_NOT_IMPLEMENTED
2026-07-10 9:15 ` ChenXiaoSong
@ 2026-07-10 13:28 ` Gaël Blivet
0 siblings, 0 replies; 16+ messages in thread
From: Gaël Blivet @ 2026-07-10 13:28 UTC (permalink / raw)
To: ChenXiaoSong; +Cc: Namjae Jeon, linux-cifs
We can keep that in mind and see in practice how it performs (for example with Time Machine, that can open long-running watches).
> Le 10 juil. 2026 à 11:15, ChenXiaoSong <chenxiaosong@chenxiaosong.com> a écrit :
>
> Yes, before the cancel response or event notification response is sent, the notify request has not completed yet, so the worker will remain waiting.
>
> If you find that this approach causes any issues, we can refactor it and use a better solution.
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-07-10 13:28 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox