* [PATCH 01/12] smb/server: move change notify handling into notify.c
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 02/12] smb/server: add more validation for change notify requests ChenXiaoSong
` (10 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Move the SMB2 CHANGE_NOTIFY implementation out of smb2pdu.c into a
dedicated notify.c file.
Factor out two helper functions ksmbd_notify_validate_req() and
ksmbd_notify_wait().
No functional change.
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/Makefile | 3 +-
fs/smb/server/notify.c | 183 ++++++++++++++++++++++++++++++++++++++++
fs/smb/server/notify.h | 24 ++++++
fs/smb/server/smb2pdu.c | 110 +-----------------------
4 files changed, 213 insertions(+), 107 deletions(-)
create mode 100644 fs/smb/server/notify.c
create mode 100644 fs/smb/server/notify.h
diff --git a/fs/smb/server/Makefile b/fs/smb/server/Makefile
index 9bc87695a53c..5daae1727b32 100644
--- a/fs/smb/server/Makefile
+++ b/fs/smb/server/Makefile
@@ -10,7 +10,8 @@ ksmbd-y := unicode.o auth.o vfs.o vfs_cache.o server.o ndr.o \
mgmt/tree_connect.o mgmt/user_session.o smb_common.o \
transport_tcp.o transport_ipc.o smbacl.o smb2pdu.o \
smb2ops.o smb2misc.o ksmbd_spnego_negtokeninit.asn1.o \
- ksmbd_spnego_negtokentarg.asn1.o asn1.o compress.o
+ ksmbd_spnego_negtokentarg.asn1.o asn1.o compress.o \
+ notify.o
$(obj)/asn1.o: $(obj)/ksmbd_spnego_negtokeninit.asn1.h $(obj)/ksmbd_spnego_negtokentarg.asn1.h
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
new file mode 100644
index 000000000000..7e324af36b47
--- /dev/null
+++ b/fs/smb/server/notify.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * SMB2 CHANGE_NOTIFY
+ *
+ * Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+ *
+ * Author(s): ChenXiaoSong <chenxiaosong@kylinos.cn>
+ * Gael Blivet <gael.blivet@gmail.com>
+ *
+ */
+
+#include "glob.h"
+#include "../common/smb2status.h"
+#include "connection.h"
+#include "ksmbd_work.h"
+#include "notify.h"
+#include "smb_common.h"
+#include "smb2pdu.h"
+#include "vfs_cache.h"
+#include "mgmt/user_session.h"
+
+struct ksmbd_notify_req {
+ wait_queue_head_t wait;
+};
+
+/*
+ * Cancel handler for a pending CHANGE_NOTIFY. Called either by
+ * smb2_cancel() (conn->request_lock held, work->state already set to
+ * KSMBD_WORK_CANCELLED by the caller) or by
+ * set_close_state_blocked_works() (vfs_cache.c, fp->f_lock held,
+ * work->state already set to KSMBD_WORK_CLOSED by the caller) -- both
+ * callers hold a spinlock across this call, so it must not sleep.
+ * wake_up() only wakes the waiter in smb2_notify(); it does not touch
+ * fp->blocked_works itself, matching smb2_remove_blocked_lock()'s same
+ * non-mutating style for the equivalent byte-range-lock wait.
+ */
+static void smb2_notify_cancel(void **argv)
+{
+ struct ksmbd_notify_req *notify_req = argv[0];
+
+ wake_up(¬ify_req->wait);
+}
+
+static struct ksmbd_file *
+ksmbd_notify_validate_req(struct ksmbd_work *work,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp)
+{
+ struct ksmbd_file *fp;
+
+ if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
+ pr_err("Notify request is not the last compound command\n");
+ rsp->hdr.Status = STATUS_INTERNAL_ERROR;
+ return ERR_PTR(-EIO);
+ }
+
+ fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId,
+ req->PersistentFileId);
+ if (!fp) {
+ pr_err("Invalid file id for notify, fid %llu:%llu\n",
+ le64_to_cpu(req->PersistentFileId),
+ le64_to_cpu(req->VolatileFileId));
+ rsp->hdr.Status = STATUS_FILE_CLOSED;
+ return ERR_PTR(-ENOENT);
+ }
+
+ return fp;
+}
+
+static int ksmbd_notify_wait(struct ksmbd_work *work,
+ struct ksmbd_file *fp,
+ struct ksmbd_notify_req *notify_req)
+{
+ int err;
+
+ /*
+ * Handle close holds the file-table write lock while it marks the
+ * handle closed and walks blocked_works. Hold the matching read lock
+ * across the state check and registration so close cannot finish its
+ * walk between the lookup above and this list insertion.
+ */
+ read_lock(&work->sess->file_table.lock);
+ if (fp->f_state != FP_INITED) {
+ read_unlock(&work->sess->file_table.lock);
+ return -ENOENT;
+ }
+ spin_lock(&fp->f_lock);
+ list_add_tail(&work->fp_entry, &fp->blocked_works);
+ spin_unlock(&fp->f_lock);
+ read_unlock(&work->sess->file_table.lock);
+
+ smb2_send_interim_resp(work, STATUS_PENDING);
+
+ err = wait_event_interruptible(notify_req->wait,
+ READ_ONCE(work->state) !=
+ KSMBD_WORK_ACTIVE);
+ if (err && READ_ONCE(work->state) == KSMBD_WORK_ACTIVE) {
+ pr_err("Notify wait interrupted, async id %d: %d\n",
+ work->async_id, err);
+ /*
+ * Woken by a signal, not a real cancel/close. There is no
+ * notification backend yet to report anything else against,
+ * so treat this the same as a client-side cancel.
+ */
+ WRITE_ONCE(work->state, KSMBD_WORK_CANCELLED);
+ }
+
+ spin_lock(&fp->f_lock);
+ list_del_init(&work->fp_entry);
+ spin_unlock(&fp->f_lock);
+
+ return err;
+}
+
+/**
+ * ksmbd_handle_notify() - handle an SMB2 change notify request
+ * @work: smb work containing notify command buffer
+ * @req: SMB2 change notify request
+ * @rsp: SMB2 change notify response
+ *
+ * Return: 0 on success, otherwise error
+ */
+int ksmbd_handle_notify(struct ksmbd_work *work,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp)
+{
+ struct ksmbd_notify_req notify_req = {};
+ struct ksmbd_file *fp = NULL;
+ void **argv = NULL;
+ bool async_work = false;
+ int err = 0;
+
+ fp = ksmbd_notify_validate_req(work, req, rsp);
+ if (IS_ERR(fp)) {
+ err = PTR_ERR(fp);
+ fp = NULL;
+ goto out;
+ }
+
+ argv = kmalloc_obj(*argv, KSMBD_DEFAULT_GFP);
+ if (!argv) {
+ pr_err("Failed to allocate notify cancel arguments\n");
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ err = -ENOMEM;
+ goto out;
+ }
+ init_waitqueue_head(¬ify_req.wait);
+ argv[0] = ¬ify_req;
+
+ err = setup_async_work(work, smb2_notify_cancel, argv);
+ if (err) {
+ pr_err("Failed to set up asynchronous notify work: %d\n", err);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto out;
+ }
+ async_work = true;
+
+ err = ksmbd_notify_wait(work, fp, ¬ify_req);
+ if (err == -ENOENT) {
+ rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
+ goto out;
+ }
+
+ if (work->state == KSMBD_WORK_CLOSED) {
+ rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
+ } else {
+ rsp->hdr.Status = STATUS_CANCELLED;
+ }
+ smb2_send_interim_resp(work, rsp->hdr.Status);
+ work->send_no_response = 1;
+
+out:
+ if (rsp->hdr.Status != STATUS_SUCCESS && !work->send_no_response)
+ smb2_set_err_rsp(work);
+ if (async_work)
+ release_async_work(work);
+ else
+ kfree(argv);
+ if (fp)
+ ksmbd_fd_put(work, fp);
+ return err;
+}
diff --git a/fs/smb/server/notify.h b/fs/smb/server/notify.h
new file mode 100644
index 000000000000..8de46e07b02e
--- /dev/null
+++ b/fs/smb/server/notify.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * SMB2 CHANGE_NOTIFY
+ *
+ * Copyright (C) 2026 KylinSoft Co., Ltd. All rights reserved.
+ *
+ * Author(s): ChenXiaoSong <chenxiaosong@kylinos.cn>
+ * Gael Blivet <gael.blivet@gmail.com>
+ *
+ */
+
+#ifndef __SMB_SERVER_NOTIFY_H__
+#define __SMB_SERVER_NOTIFY_H__
+
+struct ksmbd_work;
+struct smb2_change_notify_req;
+struct smb2_change_notify_rsp;
+
+int ksmbd_handle_notify(struct ksmbd_work *work,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp);
+
+#endif /* __SMB_SERVER_NOTIFY_H__ */
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 7b9080508a3f..1f45a0836fe9 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -33,6 +33,7 @@
#include "vfs.h"
#include "vfs_cache.h"
#include "misc.h"
+#include "notify.h"
#include "server.h"
#include "smb_common.h"
@@ -11818,28 +11819,6 @@ int smb2_oplock_break(struct ksmbd_work *work)
return 0;
}
-struct ksmbd_notify_req {
- wait_queue_head_t wait;
-};
-
-/*
- * Cancel handler for a pending CHANGE_NOTIFY. Called either by
- * smb2_cancel() (conn->request_lock held, work->state already set to
- * KSMBD_WORK_CANCELLED by the caller) or by
- * set_close_state_blocked_works() (vfs_cache.c, fp->f_lock held,
- * work->state already set to KSMBD_WORK_CLOSED by the caller) -- both
- * callers hold a spinlock across this call, so it must not sleep.
- * wake_up() only wakes the waiter in smb2_notify(); it does not touch
- * fp->blocked_works itself, matching smb2_remove_blocked_lock()'s same
- * non-mutating style for the equivalent byte-range-lock wait.
- */
-static void smb2_notify_cancel(void **argv)
-{
- struct ksmbd_notify_req *notify_req = argv[0];
-
- wake_up(¬ify_req->wait);
-}
-
/**
* smb2_notify() - handler for smb2 notify request
* @work: smb work containing notify command buffer
@@ -11850,98 +11829,17 @@ int smb2_notify(struct ksmbd_work *work)
{
struct smb2_change_notify_req *req;
struct smb2_change_notify_rsp *rsp;
- struct ksmbd_notify_req notify_req;
- struct ksmbd_file *fp = NULL;
- void **argv = NULL;
- bool async_work = false;
- int err = 0;
ksmbd_debug(SMB, "Received smb2 notify\n");
WORK_BUFFERS(work, req, rsp);
- if (smb2_compound_has_failed(work, &rsp->hdr))
+ if (smb2_compound_has_failed(work, &rsp->hdr)) {
+ pr_err("Failed compound notify request\n");
return -EACCES;
-
- if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
- rsp->hdr.Status = STATUS_INTERNAL_ERROR;
- err = -EIO;
- goto out;
- }
-
- fp = ksmbd_lookup_fd_slow(work, req->VolatileFileId, req->PersistentFileId);
- if (!fp) {
- rsp->hdr.Status = STATUS_FILE_CLOSED;
- err = -ENOENT;
- goto out;
- }
-
- argv = kmalloc(sizeof(void *), KSMBD_DEFAULT_GFP);
- if (!argv) {
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- err = -ENOMEM;
- goto out;
- }
- init_waitqueue_head(¬ify_req.wait);
- argv[0] = ¬ify_req;
-
- err = setup_async_work(work, smb2_notify_cancel, argv);
- if (err) {
- rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
- goto out;
- }
- async_work = true;
-
- /*
- * Handle close holds the file-table write lock while it marks the
- * handle closed and walks blocked_works. Hold the matching read lock
- * across the state check and registration so close cannot finish its
- * walk between the lookup above and this list insertion.
- */
- read_lock(&work->sess->file_table.lock);
- if (fp->f_state != FP_INITED) {
- read_unlock(&work->sess->file_table.lock);
- rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
- err = -ENOENT;
- goto out;
}
- spin_lock(&fp->f_lock);
- list_add_tail(&work->fp_entry, &fp->blocked_works);
- spin_unlock(&fp->f_lock);
- read_unlock(&work->sess->file_table.lock);
- smb2_send_interim_resp(work, STATUS_PENDING);
-
- err = wait_event_interruptible(notify_req.wait,
- READ_ONCE(work->state) != KSMBD_WORK_ACTIVE);
- if (err && READ_ONCE(work->state) == KSMBD_WORK_ACTIVE) {
- /*
- * Woken by a signal, not a real cancel/close. There is no
- * notification backend yet to report anything else against,
- * so treat this the same as a client-side cancel.
- */
- WRITE_ONCE(work->state, KSMBD_WORK_CANCELLED);
- }
-
- spin_lock(&fp->f_lock);
- list_del_init(&work->fp_entry);
- spin_unlock(&fp->f_lock);
-
- rsp->hdr.Status = work->state == KSMBD_WORK_CLOSED ?
- STATUS_NOTIFY_CLEANUP : STATUS_CANCELLED;
- smb2_send_interim_resp(work, rsp->hdr.Status);
- work->send_no_response = 1;
-
-out:
- if (rsp->hdr.Status != STATUS_SUCCESS && !work->send_no_response)
- smb2_set_err_rsp(work);
- if (async_work)
- release_async_work(work);
- else
- kfree(argv);
- if (fp)
- ksmbd_fd_put(work, fp);
- return err;
+ return ksmbd_handle_notify(work, req, rsp);
}
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 02/12] smb/server: add more validation for change notify requests
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
2026-09-26 9:05 ` [PATCH 01/12] smb/server: move change notify handling into notify.c ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 03/12] smb/server: add debug type for change notify ChenXiaoSong
` (9 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Add validation for the file type, directory list access and output
buffer length before setting up a change notify watch.
Suggested-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 7e324af36b47..18cf0005a037 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -48,6 +48,7 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
struct smb2_change_notify_rsp *rsp)
{
struct ksmbd_file *fp;
+ int err;
if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
pr_err("Notify request is not the last compound command\n");
@@ -65,7 +66,37 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
return ERR_PTR(-ENOENT);
}
+ if (le32_to_cpu(req->OutputBufferLength) >
+ work->conn->vals->max_trans_size) {
+ pr_err("Notify output buffer length %u exceeds maximum %u\n",
+ le32_to_cpu(req->OutputBufferLength),
+ work->conn->vals->max_trans_size);
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ err = -EINVAL;
+ goto err_put_fp;
+ }
+
+ if (!S_ISDIR(file_inode(fp->filp)->i_mode)) {
+ pr_err("Notify file id is not a directory, fid %llu:%llu\n",
+ fp->persistent_id, fp->volatile_id);
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ err = -EINVAL;
+ goto err_put_fp;
+ }
+
+ if (!(fp->daccess & FILE_LIST_DIRECTORY_LE)) {
+ pr_err("No permission to monitor directory, fid %llu:%llu\n",
+ fp->persistent_id, fp->volatile_id);
+ rsp->hdr.Status = STATUS_ACCESS_DENIED;
+ err = -EACCES;
+ goto err_put_fp;
+ }
+
return fp;
+
+err_put_fp:
+ ksmbd_fd_put(work, fp);
+ return ERR_PTR(err);
}
static int ksmbd_notify_wait(struct ksmbd_work *work,
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 03/12] smb/server: add debug type for change notify
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
2026-09-26 9:05 ` [PATCH 01/12] smb/server: move change notify handling into notify.c ChenXiaoSong
2026-09-26 9:05 ` [PATCH 02/12] smb/server: add more validation for change notify requests ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 04/12] smb/server: support non-recursive directory change watches ChenXiaoSong
` (8 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Add KSMBD_DEBUG_NOTIFY and expose it as "notify" to print logs
for the change notify feature.
Example:
ksmbd.control --debug=notify
[ksmbd.control/802]: INFO: smb auth vfs oplock ipc conn rdma [notify]
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/glob.h | 3 ++-
fs/smb/server/notify.c | 12 ++++++++++++
fs/smb/server/server.c | 2 +-
fs/smb/server/smb2pdu.c | 2 +-
4 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/glob.h b/fs/smb/server/glob.h
index 4ea187af2348..5131bad88d96 100644
--- a/fs/smb/server/glob.h
+++ b/fs/smb/server/glob.h
@@ -21,10 +21,11 @@ extern int ksmbd_debug_types;
#define KSMBD_DEBUG_IPC BIT(4)
#define KSMBD_DEBUG_CONN BIT(5)
#define KSMBD_DEBUG_RDMA BIT(6)
+#define KSMBD_DEBUG_NOTIFY BIT(7)
#define KSMBD_DEBUG_ALL (KSMBD_DEBUG_SMB | KSMBD_DEBUG_AUTH | \
KSMBD_DEBUG_VFS | KSMBD_DEBUG_OPLOCK | \
KSMBD_DEBUG_IPC | KSMBD_DEBUG_CONN | \
- KSMBD_DEBUG_RDMA)
+ KSMBD_DEBUG_RDMA | KSMBD_DEBUG_NOTIFY)
#ifdef pr_fmt
#undef pr_fmt
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 18cf0005a037..502ec551c01e 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -39,6 +39,7 @@ static void smb2_notify_cancel(void **argv)
{
struct ksmbd_notify_req *notify_req = argv[0];
+ ksmbd_debug(NOTIFY, "Wake pending notify request\n");
wake_up(¬ify_req->wait);
}
@@ -66,6 +67,11 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
return ERR_PTR(-ENOENT);
}
+ ksmbd_debug(NOTIFY,
+ "fid %llu:%llu, handle notify request, filter 0x%x, flags 0x%x\n",
+ fp->persistent_id, fp->volatile_id,
+ le32_to_cpu(req->CompletionFilter), le16_to_cpu(req->Flags));
+
if (le32_to_cpu(req->OutputBufferLength) >
work->conn->vals->max_trans_size) {
pr_err("Notify output buffer length %u exceeds maximum %u\n",
@@ -121,6 +127,8 @@ static int ksmbd_notify_wait(struct ksmbd_work *work,
spin_unlock(&fp->f_lock);
read_unlock(&work->sess->file_table.lock);
+ ksmbd_debug(NOTIFY, "Notify request pending, async id %d\n",
+ work->async_id);
smb2_send_interim_resp(work, STATUS_PENDING);
err = wait_event_interruptible(notify_req->wait,
@@ -195,8 +203,12 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
if (work->state == KSMBD_WORK_CLOSED) {
rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
+ ksmbd_debug(NOTIFY, "Notify handle closed, async id %d\n",
+ work->async_id);
} else {
rsp->hdr.Status = STATUS_CANCELLED;
+ ksmbd_debug(NOTIFY, "Notify request cancelled, async id %d\n",
+ work->async_id);
}
smb2_send_interim_resp(work, rsp->hdr.Status);
work->send_no_response = 1;
diff --git a/fs/smb/server/server.c b/fs/smb/server/server.c
index 0827c8c51006..958e952223aa 100644
--- a/fs/smb/server/server.c
+++ b/fs/smb/server/server.c
@@ -564,7 +564,7 @@ static ssize_t kill_server_store(const struct class *class,
static const char * const debug_type_strings[] = {"smb", "auth", "vfs",
"oplock", "ipc", "conn",
- "rdma"};
+ "rdma", "notify"};
static ssize_t debug_show(const struct class *class, const struct class_attribute *attr,
char *buf)
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 1f45a0836fe9..eb15cda699b7 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -11830,7 +11830,7 @@ int smb2_notify(struct ksmbd_work *work)
struct smb2_change_notify_req *req;
struct smb2_change_notify_rsp *rsp;
- ksmbd_debug(SMB, "Received smb2 notify\n");
+ ksmbd_debug(NOTIFY, "Received smb2 notify\n");
WORK_BUFFERS(work, req, rsp);
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 04/12] smb/server: support non-recursive directory change watches
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (2 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 03/12] smb/server: add debug type for change notify ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-27 10:51 ` Namjae Jeon
2026-09-26 9:05 ` [PATCH 05/12] smb/server: support recursive " ChenXiaoSong
` (7 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Use fsnotify to watch a directory for changes. Add one watch for each
SMB2 CHANGE_NOTIFY request with SMB2_WATCH_TREE unset.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: touch /export/file
4. server debug log:
ksmbd: fid 4:4, notify event: mask=0x00000100 inode=2 name=file cookie=0
ksmbd: fid 4:4, notify event: mask=0x08000004 inode=2 name=file cookie=0
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/Kconfig | 1 +
fs/smb/server/notify.c | 232 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 231 insertions(+), 2 deletions(-)
diff --git a/fs/smb/server/Kconfig b/fs/smb/server/Kconfig
index b7665e0e4942..1f91926151b2 100644
--- a/fs/smb/server/Kconfig
+++ b/fs/smb/server/Kconfig
@@ -19,6 +19,7 @@ config SMB_SERVER
select ASN1
select OID_REGISTRY
select CRC32
+ select FSNOTIFY
default n
help
Choose Y here if you want to allow SMB3 compliant clients
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 502ec551c01e..b85f5fdb5066 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -10,6 +10,7 @@
*
*/
+#include <linux/fsnotify_backend.h>
#include "glob.h"
#include "../common/smb2status.h"
#include "connection.h"
@@ -20,10 +21,57 @@
#include "vfs_cache.h"
#include "mgmt/user_session.h"
+struct ksmbd_notify {
+ struct fsnotify_group *group;
+ struct fsnotify_mark *mark;
+ struct ksmbd_file *fp;
+ /* Protects filter, rename state and the queued events. */
+ spinlock_t lock;
+ u32 filter;
+ u32 mask;
+};
+
struct ksmbd_notify_req {
wait_queue_head_t wait;
};
+#define KSMBD_NOTIFY_NAME_EVENT_MASK (FS_CREATE | FS_DELETE | \
+ FS_MOVED_FROM | FS_MOVED_TO)
+
+static const struct {
+ u32 notify_mask;
+ u32 fsnotify_mask;
+} ksmbd_notify_mapping[] = {
+ { FILE_NOTIFY_CHANGE_FILE_NAME,
+ KSMBD_NOTIFY_NAME_EVENT_MASK },
+ { FILE_NOTIFY_CHANGE_DIR_NAME,
+ KSMBD_NOTIFY_NAME_EVENT_MASK },
+ { FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ FS_ATTRIB | FS_MOVED_FROM | FS_MOVED_TO | FS_MODIFY },
+ { FILE_NOTIFY_CHANGE_SIZE, FS_MODIFY },
+ { FILE_NOTIFY_CHANGE_LAST_WRITE, FS_ATTRIB },
+ { FILE_NOTIFY_CHANGE_LAST_ACCESS, FS_ATTRIB },
+ { FILE_NOTIFY_CHANGE_EA, FS_ATTRIB },
+ { FILE_NOTIFY_CHANGE_SECURITY, FS_ATTRIB },
+};
+
+static u32 ksmbd_notify_map(u32 filter)
+{
+ size_t i;
+ u32 mask = 0;
+
+ for (i = 0; i < ARRAY_SIZE(ksmbd_notify_mapping); i++) {
+ if (ksmbd_notify_mapping[i].notify_mask & filter)
+ mask |= ksmbd_notify_mapping[i].fsnotify_mask;
+ }
+
+ ksmbd_debug(NOTIFY,
+ "Mapped completion filter 0x%x to fsnotify mask 0x%x\n",
+ filter, mask);
+
+ return mask;
+}
+
/*
* Cancel handler for a pending CHANGE_NOTIFY. Called either by
* smb2_cancel() (conn->request_lock held, work->state already set to
@@ -43,6 +91,140 @@ static void smb2_notify_cancel(void **argv)
wake_up(¬ify_req->wait);
}
+static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
+ u32 mask, struct inode *inode,
+ struct inode *dir,
+ const struct qstr *file_name,
+ u32 cookie)
+{
+ struct inode *event_inode = dir ?: inode;
+ struct ksmbd_file *fp;
+
+ fp = notify->fp;
+
+ ksmbd_debug(NOTIFY,
+ "fid %llu:%llu, notify event: mask=0x%08x inode=%llu name=%.*s cookie=%u\n",
+ fp->persistent_id, fp->volatile_id, mask,
+ event_inode ? (unsigned long long)event_inode->i_ino : 0,
+ file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "", cookie);
+
+ return 0;
+}
+
+static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
+ const void *data, int data_type,
+ struct inode *dir,
+ const struct qstr *file_name, u32 cookie,
+ struct fsnotify_iter_info *iter_info)
+{
+ struct ksmbd_notify *notify = group->private;
+ struct inode *inode = fsnotify_data_inode(data, data_type);
+
+ return ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
+ file_name, cookie);
+}
+
+static void ksmbd_notify_free_mark(struct fsnotify_mark *mark)
+{
+ kfree(mark);
+}
+
+static const struct fsnotify_ops ksmbd_notify_fsnotify_ops = {
+ .handle_event = ksmbd_notify_handle_event,
+ .free_mark = ksmbd_notify_free_mark,
+};
+
+static struct fsnotify_mark *
+ksmbd_notify_add_mark(struct ksmbd_notify *notify, u32 mask, void *obj,
+ unsigned int obj_type)
+{
+ struct fsnotify_mark *mark;
+ int err;
+
+ mark = kzalloc_obj(*mark, KSMBD_DEFAULT_GFP);
+ if (!mark) {
+ pr_err("Failed to allocate fsnotify mark\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fsnotify_init_mark(mark, notify->group);
+ mark->mask = mask | FS_EVENT_ON_CHILD;
+ err = fsnotify_add_mark(mark, obj, obj_type, 0);
+ if (err) {
+ pr_err("Failed to add fsnotify mark, type %u: %d\n",
+ obj_type, err);
+ goto err_put_mark;
+ }
+
+ return mark;
+
+err_put_mark:
+ fsnotify_put_mark(mark);
+ return ERR_PTR(err);
+}
+
+static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
+{
+ if (notify->mark) {
+ fsnotify_destroy_mark(notify->mark, notify->group);
+ fsnotify_put_mark(notify->mark);
+ }
+ fsnotify_wait_marks_destroyed();
+ fsnotify_put_group(notify->group);
+}
+
+static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
+ struct ksmbd_notify **notify_out)
+{
+ struct ksmbd_notify *notify;
+ struct fsnotify_mark *mark;
+ int err = 0;
+
+ notify = kzalloc_obj(*notify, KSMBD_DEFAULT_GFP);
+ if (!notify) {
+ pr_err("Failed to allocate notify watch\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ notify->fp = fp;
+ spin_lock_init(¬ify->lock);
+ notify->filter = filter;
+ notify->mask = mask;
+
+ notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
+ if (IS_ERR(notify->group)) {
+ err = PTR_ERR(notify->group);
+ pr_err("Failed to allocate fsnotify group: %d\n", err);
+ kfree(notify);
+ goto out;
+ }
+ notify->group->private = notify;
+
+ mark = ksmbd_notify_add_mark(notify, mask, file_inode(fp->filp),
+ FSNOTIFY_OBJ_TYPE_INODE);
+ if (IS_ERR(mark)) {
+ err = PTR_ERR(mark);
+ goto err_destroy_marks;
+ }
+ notify->mark = mark;
+
+ *notify_out = notify;
+ ksmbd_debug(NOTIFY,
+ "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x\n",
+ (unsigned long long)file_inode(fp->filp)->i_ino, mark->mask,
+ filter);
+ goto out;
+
+err_destroy_marks:
+ ksmbd_notify_destroy_marks(notify);
+ kfree(notify);
+
+out:
+ return err;
+}
+
static struct ksmbd_file *
ksmbd_notify_validate_req(struct ksmbd_work *work,
struct smb2_change_notify_req *req,
@@ -82,6 +264,13 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
goto err_put_fp;
}
+ if (le16_to_cpu(req->Flags) & ~SMB2_WATCH_TREE) {
+ pr_err("Invalid notify flags 0x%x\n", le16_to_cpu(req->Flags));
+ rsp->hdr.Status = STATUS_INVALID_PARAMETER;
+ err = -EINVAL;
+ goto err_put_fp;
+ }
+
if (!S_ISDIR(file_inode(fp->filp)->i_mode)) {
pr_err("Notify file id is not a directory, fid %llu:%llu\n",
fp->persistent_id, fp->volatile_id);
@@ -105,10 +294,37 @@ ksmbd_notify_validate_req(struct ksmbd_work *work,
return ERR_PTR(err);
}
+static struct ksmbd_notify *
+ksmbd_notify_setup_watch(struct ksmbd_file *fp,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp)
+{
+ struct ksmbd_notify *notify;
+ u32 filter, mask;
+ int err;
+
+ filter = le32_to_cpu(req->CompletionFilter);
+ mask = ksmbd_notify_map(filter);
+ if (!mask)
+ ksmbd_debug(NOTIFY,
+ "No mapped completion filter bits; request will remain pending\n");
+
+ err = ksmbd_notify_add(fp, mask, filter, ¬ify);
+ if (err) {
+ pr_err("Failed to add notify watch, fid %llu:%llu: %d\n",
+ fp->persistent_id, fp->volatile_id, err);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ return ERR_PTR(err);
+ }
+
+ return notify;
+}
+
static int ksmbd_notify_wait(struct ksmbd_work *work,
- struct ksmbd_file *fp,
+ struct ksmbd_notify *notify,
struct ksmbd_notify_req *notify_req)
{
+ struct ksmbd_file *fp = notify->fp;
int err;
/*
@@ -165,6 +381,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
struct smb2_change_notify_rsp *rsp)
{
struct ksmbd_notify_req notify_req = {};
+ struct ksmbd_notify *notify = NULL;
struct ksmbd_file *fp = NULL;
void **argv = NULL;
bool async_work = false;
@@ -177,6 +394,13 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
goto out;
}
+ notify = ksmbd_notify_setup_watch(fp, req, rsp);
+ if (IS_ERR(notify)) {
+ err = PTR_ERR(notify);
+ notify = NULL;
+ goto out;
+ }
+
argv = kmalloc_obj(*argv, KSMBD_DEFAULT_GFP);
if (!argv) {
pr_err("Failed to allocate notify cancel arguments\n");
@@ -195,7 +419,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
}
async_work = true;
- err = ksmbd_notify_wait(work, fp, ¬ify_req);
+ err = ksmbd_notify_wait(work, notify, ¬ify_req);
if (err == -ENOENT) {
rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
goto out;
@@ -220,6 +444,10 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
release_async_work(work);
else
kfree(argv);
+ if (notify) {
+ ksmbd_notify_destroy_marks(notify);
+ kfree(notify);
+ }
if (fp)
ksmbd_fd_put(work, fp);
return err;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 04/12] smb/server: support non-recursive directory change watches
2026-09-26 9:05 ` [PATCH 04/12] smb/server: support non-recursive directory change watches ChenXiaoSong
@ 2026-09-27 10:51 ` Namjae Jeon
2026-09-28 0:38 ` ChenXiaoSong
0 siblings, 1 reply; 17+ messages in thread
From: Namjae Jeon @ 2026-09-27 10:51 UTC (permalink / raw)
To: ChenXiaoSong; +Cc: tom, senozhatsky, linux-cifs, ChenXiaoSong
> +static const struct {
> + u32 notify_mask;
> + u32 fsnotify_mask;
> +} ksmbd_notify_mapping[] = {
> + { FILE_NOTIFY_CHANGE_FILE_NAME,
> + KSMBD_NOTIFY_NAME_EVENT_MASK },
> + { FILE_NOTIFY_CHANGE_DIR_NAME,
> + KSMBD_NOTIFY_NAME_EVENT_MASK },
> + { FILE_NOTIFY_CHANGE_ATTRIBUTES,
> + FS_ATTRIB | FS_MOVED_FROM | FS_MOVED_TO | FS_MODIFY },
> + { FILE_NOTIFY_CHANGE_SIZE, FS_MODIFY },
> + { FILE_NOTIFY_CHANGE_LAST_WRITE, FS_ATTRIB },
> + { FILE_NOTIFY_CHANGE_LAST_ACCESS, FS_ATTRIB },
Should we also map FILE_NOTIFY_CHANGE_LAST_WRITE to FS_MODIFY and
FILE_NOTIFY_CHANGE_LAST_ACCESS to FS_ACCESS, then add FS_ACCESS to
KSMBD_NOTIFY_EVENT_MASK? With a LAST_WRITE-only filter, ordinary
writes emit FS_MODIFY, but this watch subscribes only to FS_ATTRIB, so
the request can remain pending. The same issue applies to reads with a
LAST_ACCESS-only filter.
> + { FILE_NOTIFY_CHANGE_EA, FS_ATTRIB },
> + { FILE_NOTIFY_CHANGE_SECURITY, FS_ATTRIB },
> +};
^ permalink raw reply [flat|nested] 17+ messages in thread* Re: [PATCH 04/12] smb/server: support non-recursive directory change watches
2026-09-27 10:51 ` Namjae Jeon
@ 2026-09-28 0:38 ` ChenXiaoSong
0 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-28 0:38 UTC (permalink / raw)
To: Namjae Jeon; +Cc: tom, senozhatsky, linux-cifs, ChenXiaoSong
Yes, makes sense. Thanks for your suggestion.
On 9/27/26 18:51, Namjae Jeon wrote:
> Should we also map FILE_NOTIFY_CHANGE_LAST_WRITE to FS_MODIFY and
> FILE_NOTIFY_CHANGE_LAST_ACCESS to FS_ACCESS, then add FS_ACCESS to
> KSMBD_NOTIFY_EVENT_MASK? With a LAST_WRITE-only filter, ordinary
> writes emit FS_MODIFY, but this watch subscribes only to FS_ATTRIB, so
> the request can remain pending. The same issue applies to reads with a
> LAST_ACCESS-only filter.
--
ChenXiaoSong <chenxiaosong@chenxiaosong.com>
Chinese Homepage: https://chenxiaosong.com
English Homepage: https://chenxiaosong.com/en
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 05/12] smb/server: support recursive directory change watches
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (3 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 04/12] smb/server: support non-recursive directory change watches ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 06/12] smb/server: keep notify watches on file handles ChenXiaoSong
` (6 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Support SMB2 CHANGE_NOTIFY requests with SMB2_WATCH_TREE set.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: mkdir /export/dir; touch /export/dir/file
4. server debug log:
ksmbd: fid 1:1, notify event: mask=0x00000100 inode=11 name=dir\file cookie=0
ksmbd: fid 1:1, notify event: mask=0x00000004 inode=11 name=dir\file cookie=0
Suggested-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 155 +++++++++++++++++++++++++++++++++++++++--
1 file changed, 149 insertions(+), 6 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index b85f5fdb5066..b34f2f9b6d3f 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -11,6 +11,7 @@
*/
#include <linux/fsnotify_backend.h>
+#include <linux/dcache.h>
#include "glob.h"
#include "../common/smb2status.h"
#include "connection.h"
@@ -24,11 +25,13 @@
struct ksmbd_notify {
struct fsnotify_group *group;
struct fsnotify_mark *mark;
+ struct fsnotify_mark *tree_mark;
struct ksmbd_file *fp;
/* Protects filter, rename state and the queued events. */
spinlock_t lock;
u32 filter;
u32 mask;
+ bool watch_tree;
};
struct ksmbd_notify_req {
@@ -112,6 +115,100 @@ static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
return 0;
}
+static char *ksmbd_notify_tree_name(struct ksmbd_notify *notify,
+ const void *data, int data_type,
+ struct inode *dir,
+ const struct qstr *file_name,
+ struct qstr *tree_name)
+{
+ struct dentry *root = file_dentry(notify->fp->filp);
+ struct inode *inode = fsnotify_data_inode(data, data_type);
+ struct dentry *dentry;
+ char *buf = NULL, *root_buf = NULL, *name = NULL;
+ char *path, *root_path, *relative;
+ size_t file_name_len = file_name ? file_name->len : 0;
+ size_t prefix_len, root_len, len, i;
+
+ /* Name events refer to @file_name below @dir. */
+ if (file_name && dir) {
+ dentry = d_find_alias(dir);
+ } else {
+ dentry = fsnotify_data_dentry(data, data_type);
+ if (dentry)
+ dget(dentry);
+ else
+ dentry = inode ? d_find_alias(inode) : NULL;
+ }
+ if (!dentry)
+ return ERR_PTR(-ENOENT);
+
+ /* A superblock mark also reports events outside the watched tree. */
+ if (!is_subdir(dentry, root)) {
+ name = ERR_PTR(-EXDEV);
+ goto out_dput;
+ }
+
+ buf = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
+ root_buf = kmalloc(PATH_MAX, KSMBD_DEFAULT_GFP);
+ if (!buf || !root_buf) {
+ name = ERR_PTR(-ENOMEM);
+ goto out_free_bufs;
+ }
+
+ path = dentry_path_raw(dentry, buf, PATH_MAX);
+ root_path = dentry_path_raw(root, root_buf, PATH_MAX);
+ if (IS_ERR(path) || IS_ERR(root_path)) {
+ name = ERR_PTR(IS_ERR(path) ? PTR_ERR(path) : PTR_ERR(root_path));
+ goto out_free_bufs;
+ }
+
+ root_len = strlen(root_path);
+ if (root_len == 1 && root_path[0] == '/') {
+ relative = path + 1;
+ } else if (!strncmp(path, root_path, root_len) &&
+ (path[root_len] == '/' || path[root_len] == '\0')) {
+ relative = path + root_len;
+ if (*relative == '/')
+ relative++;
+ } else {
+ /* The watched directory was renamed between the two snapshots. */
+ name = ERR_PTR(-EAGAIN);
+ goto out_free_bufs;
+ }
+
+ prefix_len = strlen(relative);
+ if (prefix_len > SIZE_MAX - file_name_len - 2) {
+ name = ERR_PTR(-EOVERFLOW);
+ goto out_free_bufs;
+ }
+ len = prefix_len + file_name_len + (prefix_len && file_name ? 1 : 0);
+ name = kmalloc(len + 1, KSMBD_DEFAULT_GFP);
+ if (!name) {
+ name = ERR_PTR(-ENOMEM);
+ goto out_free_bufs;
+ }
+
+ memcpy(name, relative, prefix_len);
+ if (prefix_len && file_name)
+ name[prefix_len++] = '\\';
+ if (file_name_len)
+ memcpy(name + prefix_len, file_name->name, file_name_len);
+ name[len] = '\0';
+ for (i = 0; i < len; i++) {
+ if (name[i] == '/')
+ name[i] = '\\';
+ }
+ tree_name->name = name;
+ tree_name->len = len;
+
+out_free_bufs:
+ kfree(root_buf);
+ kfree(buf);
+out_dput:
+ dput(dentry);
+ return name;
+}
+
static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
const void *data, int data_type,
struct inode *dir,
@@ -119,10 +216,32 @@ static int ksmbd_notify_handle_event(struct fsnotify_group *group, u32 mask,
struct fsnotify_iter_info *iter_info)
{
struct ksmbd_notify *notify = group->private;
- struct inode *inode = fsnotify_data_inode(data, data_type);
+ struct qstr tree_name = {};
+ struct inode *inode;
+ char *name = NULL;
+ int err;
+
+ if (!READ_ONCE(notify->watch_tree)) {
+ /* Non-tree watches accept events from the root inode mark only. */
+ if (!fsnotify_iter_inode_mark(iter_info) &&
+ !fsnotify_iter_parent_mark(iter_info))
+ return 0;
+ } else {
+ name = ksmbd_notify_tree_name(notify, data, data_type, dir,
+ file_name, &tree_name);
+ if (IS_ERR(name)) {
+ if (PTR_ERR(name) == -ENOMEM)
+ pr_err("Failed to allocate tree notify event name\n");
+ return 0;
+ }
+ file_name = &tree_name;
+ }
- return ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
+ inode = fsnotify_data_inode(data, data_type);
+ err = ksmbd_notify_handle_inode_event(notify, mask, inode, dir,
file_name, cookie);
+ kfree(name);
+ return err;
}
static void ksmbd_notify_free_mark(struct fsnotify_mark *mark)
@@ -166,6 +285,10 @@ ksmbd_notify_add_mark(struct ksmbd_notify *notify, u32 mask, void *obj,
static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
{
+ if (notify->tree_mark) {
+ fsnotify_destroy_mark(notify->tree_mark, notify->group);
+ fsnotify_put_mark(notify->tree_mark);
+ }
if (notify->mark) {
fsnotify_destroy_mark(notify->mark, notify->group);
fsnotify_put_mark(notify->mark);
@@ -175,10 +298,11 @@ static void ksmbd_notify_destroy_marks(struct ksmbd_notify *notify)
}
static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
+ bool watch_tree,
struct ksmbd_notify **notify_out)
{
struct ksmbd_notify *notify;
- struct fsnotify_mark *mark;
+ struct fsnotify_mark *mark, *tree_mark;
int err = 0;
notify = kzalloc_obj(*notify, KSMBD_DEFAULT_GFP);
@@ -192,6 +316,7 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
spin_lock_init(¬ify->lock);
notify->filter = filter;
notify->mask = mask;
+ notify->watch_tree = watch_tree;
notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
if (IS_ERR(notify->group)) {
@@ -210,11 +335,27 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
}
notify->mark = mark;
+ if (watch_tree) {
+ /*
+ * FS_EVENT_ON_CHILD covers only one level. A filesystem mark
+ * supplies recursive events; the callback limits them to the
+ * directory rooted at fp.
+ */
+ tree_mark = ksmbd_notify_add_mark(notify, mask,
+ file_inode(fp->filp)->i_sb,
+ FSNOTIFY_OBJ_TYPE_SB);
+ if (IS_ERR(tree_mark)) {
+ err = PTR_ERR(tree_mark);
+ goto err_destroy_marks;
+ }
+ notify->tree_mark = tree_mark;
+ }
+
*notify_out = notify;
ksmbd_debug(NOTIFY,
- "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x\n",
+ "Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x, watch tree %d\n",
(unsigned long long)file_inode(fp->filp)->i_ino, mark->mask,
- filter);
+ filter, watch_tree);
goto out;
err_destroy_marks:
@@ -309,7 +450,9 @@ ksmbd_notify_setup_watch(struct ksmbd_file *fp,
ksmbd_debug(NOTIFY,
"No mapped completion filter bits; request will remain pending\n");
- err = ksmbd_notify_add(fp, mask, filter, ¬ify);
+ err = ksmbd_notify_add(fp, mask, filter,
+ le16_to_cpu(req->Flags) & SMB2_WATCH_TREE,
+ ¬ify);
if (err) {
pr_err("Failed to add notify watch, fid %llu:%llu: %d\n",
fp->persistent_id, fp->volatile_id, err);
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 06/12] smb/server: keep notify watches on file handles
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (4 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 05/12] smb/server: support recursive " ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 07/12] smb/server: save simple notify events ChenXiaoSong
` (5 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Keep one notify watch on each file handle. Reuse it for later requests,
and remove it when the handle is closed.
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 44 +++++++++++++++++++++++++++++++++++----
fs/smb/server/notify.h | 2 ++
fs/smb/server/vfs_cache.c | 3 +++
fs/smb/server/vfs_cache.h | 4 ++++
4 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index b34f2f9b6d3f..b49c0ec5117d 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -305,6 +305,18 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
struct fsnotify_mark *mark, *tree_mark;
int err = 0;
+ mutex_lock(&fp->notify_lock);
+ if (fp->notify) {
+ /* Further requests on this open use the first request's options. */
+ ksmbd_debug(NOTIFY,
+ "Reusing fsnotify mark, inode %llu, mask 0x%x, filter 0x%x, watch tree %d\n",
+ (unsigned long long)file_inode(fp->filp)->i_ino,
+ fp->notify->mark->mask, fp->notify->filter,
+ fp->notify->watch_tree);
+ *notify_out = fp->notify;
+ goto out;
+ }
+
notify = kzalloc_obj(*notify, KSMBD_DEFAULT_GFP);
if (!notify) {
pr_err("Failed to allocate notify watch\n");
@@ -351,6 +363,7 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
notify->tree_mark = tree_mark;
}
+ fp->notify = notify;
*notify_out = notify;
ksmbd_debug(NOTIFY,
"Added fsnotify mark, inode %llu, mask 0x%x, filter 0x%x, watch tree %d\n",
@@ -363,9 +376,36 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
kfree(notify);
out:
+ mutex_unlock(&fp->notify_lock);
return err;
}
+/**
+ * ksmbd_notify_remove() - remove the notify watch for a closing handle
+ * @fp: file handle whose watch is being removed
+ *
+ * A cancelled CHANGE_NOTIFY request leaves this watch installed. The watch is
+ * owned by @fp and removed only when the file handle is finally closed.
+ */
+void ksmbd_notify_remove(struct ksmbd_file *fp)
+{
+ struct ksmbd_notify *notify;
+
+ mutex_lock(&fp->notify_lock);
+ notify = fp->notify;
+ fp->notify = NULL;
+ mutex_unlock(&fp->notify_lock);
+ if (!notify)
+ return;
+
+ ksmbd_debug(NOTIFY,
+ "Removing fsnotify mark, inode %llu, mask 0x%x, watch tree %d\n",
+ (unsigned long long)file_inode(fp->filp)->i_ino,
+ notify->mark->mask, notify->watch_tree);
+ ksmbd_notify_destroy_marks(notify);
+ kfree(notify);
+}
+
static struct ksmbd_file *
ksmbd_notify_validate_req(struct ksmbd_work *work,
struct smb2_change_notify_req *req,
@@ -587,10 +627,6 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
release_async_work(work);
else
kfree(argv);
- if (notify) {
- ksmbd_notify_destroy_marks(notify);
- kfree(notify);
- }
if (fp)
ksmbd_fd_put(work, fp);
return err;
diff --git a/fs/smb/server/notify.h b/fs/smb/server/notify.h
index 8de46e07b02e..1132b91c1d54 100644
--- a/fs/smb/server/notify.h
+++ b/fs/smb/server/notify.h
@@ -14,11 +14,13 @@
#define __SMB_SERVER_NOTIFY_H__
struct ksmbd_work;
+struct ksmbd_file;
struct smb2_change_notify_req;
struct smb2_change_notify_rsp;
int ksmbd_handle_notify(struct ksmbd_work *work,
struct smb2_change_notify_req *req,
struct smb2_change_notify_rsp *rsp);
+void ksmbd_notify_remove(struct ksmbd_file *fp);
#endif /* __SMB_SERVER_NOTIFY_H__ */
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index a96b764c4db5..b242babf5771 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -18,6 +18,7 @@
#include "vfs.h"
#include "connection.h"
#include "misc.h"
+#include "notify.h"
#include "mgmt/tree_connect.h"
#include "mgmt/user_session.h"
#include "mgmt/user_config.h"
@@ -626,6 +627,7 @@ static void __ksmbd_close_fd(struct ksmbd_file_table *ft, struct ksmbd_file *fp)
close_id_del_oplock(fp);
filp = fp->filp;
+ ksmbd_notify_remove(fp);
__ksmbd_inode_close(fp);
if (!IS_ERR_OR_NULL(filp))
fput(filp);
@@ -1220,6 +1222,7 @@ struct ksmbd_file *ksmbd_open_fd(struct ksmbd_work *work, struct file *filp)
INIT_LIST_HEAD(&fp->node);
INIT_LIST_HEAD(&fp->lock_list);
spin_lock_init(&fp->f_lock);
+ mutex_init(&fp->notify_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 732ae26dd6a7..fe8a01b062cd 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -33,6 +33,7 @@
#define SMB2_NO_FID (0xFFFFFFFFFFFFFFFFULL)
struct ksmbd_conn;
+struct ksmbd_notify;
struct ksmbd_session;
struct ksmbd_lock {
@@ -96,6 +97,9 @@ struct ksmbd_file {
u64 durable_volatile_id;
spinlock_t f_lock;
+ /* Protects notify watch creation and removal. */
+ struct mutex notify_lock;
+ struct ksmbd_notify *notify;
struct ksmbd_inode *f_ci;
struct ksmbd_inode *f_parent_ci;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 07/12] smb/server: save simple notify events
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (5 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 06/12] smb/server: keep notify watches on file handles ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-27 10:31 ` Namjae Jeon
2026-09-26 9:05 ` [PATCH 08/12] smb/server: save old names for rename " ChenXiaoSong
` (4 subsequent siblings)
11 siblings, 1 reply; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Save create, delete, and change events.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: touch /export/file
server debug log:
ksmbd: Queueing notify event, action 1, name file
ksmbd: Queueing notify event, action 3, name file
4. server: rm /export/file
server debug log:
ksmbd: Queueing notify event, action 2, name file
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 105 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index b49c0ec5117d..355370c66de0 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -22,6 +22,14 @@
#include "vfs_cache.h"
#include "mgmt/user_session.h"
+struct ksmbd_notify_event {
+ struct list_head list;
+ u32 action;
+ u64 when;
+ size_t name_len;
+ char name[];
+};
+
struct ksmbd_notify {
struct fsnotify_group *group;
struct fsnotify_mark *mark;
@@ -29,6 +37,8 @@ struct ksmbd_notify {
struct ksmbd_file *fp;
/* Protects filter, rename state and the queued events. */
spinlock_t lock;
+ struct list_head events;
+ unsigned int num_events;
u32 filter;
u32 mask;
bool watch_tree;
@@ -40,6 +50,8 @@ struct ksmbd_notify_req {
#define KSMBD_NOTIFY_NAME_EVENT_MASK (FS_CREATE | FS_DELETE | \
FS_MOVED_FROM | FS_MOVED_TO)
+#define KSMBD_NOTIFY_EVENT_MASK (FS_ATTRIB | FS_MODIFY | \
+ KSMBD_NOTIFY_NAME_EVENT_MASK)
static const struct {
u32 notify_mask;
@@ -75,6 +87,34 @@ static u32 ksmbd_notify_map(u32 filter)
return mask;
}
+static void ksmbd_notify_free_events(struct list_head *events)
+{
+ struct ksmbd_notify_event *event, *tmp;
+
+ list_for_each_entry_safe(event, tmp, events, list) {
+ list_del(&event->list);
+ kfree(event);
+ }
+}
+
+static bool ksmbd_notify_filter_match(struct ksmbd_notify *notify, u32 mask)
+{
+ u32 filter, notify_mask;
+
+ spin_lock(¬ify->lock);
+ filter = notify->filter;
+ notify_mask = notify->mask;
+ spin_unlock(¬ify->lock);
+
+ if (mask & KSMBD_NOTIFY_NAME_EVENT_MASK) {
+ if (mask & FS_ISDIR)
+ return filter & FILE_NOTIFY_CHANGE_DIR_NAME;
+ return filter & FILE_NOTIFY_CHANGE_FILE_NAME;
+ }
+
+ return mask & notify_mask;
+}
+
/*
* Cancel handler for a pending CHANGE_NOTIFY. Called either by
* smb2_cancel() (conn->request_lock held, work->state already set to
@@ -94,14 +134,54 @@ static void smb2_notify_cancel(void **argv)
wake_up(¬ify_req->wait);
}
+static struct ksmbd_notify_event *
+ksmbd_notify_alloc_event(u32 action, const struct qstr *file_name, gfp_t gfp)
+{
+ struct ksmbd_notify_event *event;
+ size_t name_len = file_name ? file_name->len : 0;
+
+ event = kmalloc(sizeof(*event) + name_len + 1, gfp);
+ if (!event) {
+ pr_err("Failed to allocate notify event, action %u, name %.*s\n",
+ action, file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "");
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&event->list);
+ event->action = action;
+ event->when = ktime_get_ns();
+ event->name_len = name_len;
+ if (name_len)
+ memcpy(event->name, file_name->name, name_len);
+ event->name[name_len] = '\0';
+
+ return event;
+}
+
+static void
+ksmbd_notify_queue_event(struct ksmbd_notify *notify,
+ struct ksmbd_notify_event *event)
+{
+ ksmbd_debug(NOTIFY, "Queueing notify event, action %u, name %s\n",
+ event->action, event->name);
+
+ spin_lock(¬ify->lock);
+ list_add_tail(&event->list, ¬ify->events);
+ notify->num_events++;
+ spin_unlock(¬ify->lock);
+}
+
static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
u32 mask, struct inode *inode,
struct inode *dir,
const struct qstr *file_name,
u32 cookie)
{
+ struct ksmbd_notify_event *event;
struct inode *event_inode = dir ?: inode;
struct ksmbd_file *fp;
+ u32 action;
fp = notify->fp;
@@ -112,6 +192,29 @@ static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
file_name ? file_name->len : 0,
file_name ? (const char *)file_name->name : "", cookie);
+ if (!(mask & KSMBD_NOTIFY_EVENT_MASK)) {
+ ksmbd_debug(NOTIFY, "Ignored notify event mask 0x%x\n", mask);
+ return 0;
+ }
+
+ if (mask & FS_CREATE) {
+ action = FILE_ACTION_ADDED;
+ } else if (mask & FS_DELETE) {
+ action = FILE_ACTION_REMOVED;
+ } else {
+ action = FILE_ACTION_MODIFIED;
+ }
+
+ if (!ksmbd_notify_filter_match(notify, mask))
+ return 0;
+
+ event = ksmbd_notify_alloc_event(action, file_name,
+ KSMBD_DEFAULT_GFP);
+ if (!event)
+ return 0;
+
+ ksmbd_notify_queue_event(notify, event);
+
return 0;
}
@@ -326,6 +429,7 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
notify->fp = fp;
spin_lock_init(¬ify->lock);
+ INIT_LIST_HEAD(¬ify->events);
notify->filter = filter;
notify->mask = mask;
notify->watch_tree = watch_tree;
@@ -403,6 +507,7 @@ void ksmbd_notify_remove(struct ksmbd_file *fp)
(unsigned long long)file_inode(fp->filp)->i_ino,
notify->mark->mask, notify->watch_tree);
ksmbd_notify_destroy_marks(notify);
+ ksmbd_notify_free_events(¬ify->events);
kfree(notify);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* Re: [PATCH 07/12] smb/server: save simple notify events
2026-09-26 9:05 ` [PATCH 07/12] smb/server: save simple notify events ChenXiaoSong
@ 2026-09-27 10:31 ` Namjae Jeon
2026-09-28 1:27 ` ChenXiaoSong
0 siblings, 1 reply; 17+ messages in thread
From: Namjae Jeon @ 2026-09-27 10:31 UTC (permalink / raw)
To: ChenXiaoSong; +Cc: tom, senozhatsky, linux-cifs, ChenXiaoSong
> +static void
> +ksmbd_notify_queue_event(struct ksmbd_notify *notify,
> + struct ksmbd_notify_event *event)
> +{
> + ksmbd_debug(NOTIFY, "Queueing notify event, action %u, name %s\n",
> + event->action, event->name);
> +
> + spin_lock(¬ify->lock);
> + list_add_tail(&event->list, ¬ify->events);
> + notify->num_events++;
Could we put a per-open limit on the memory used by notify->events?
> + spin_unlock(¬ify->lock);
> +}
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 08/12] smb/server: save old names for rename notify events
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (6 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 07/12] smb/server: save simple notify events ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 09/12] smb/server: match " ChenXiaoSong
` (3 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Save the old name from FS_MOVED_FROM events for matching with a subsequent
FS_MOVED_TO event. Queue unmatched events as removals after a short delay.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: mkdir /export/dir/; touch /export/file
4. server: mv /export/file /export/dir/
server debug log:
[40622.865858] ksmbd: Saved moved from, mask 0x40, name file, cookie 1134
[40622.975906] ksmbd: Queue moved from as removed, name file
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 76 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 355370c66de0..ab1867033f26 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -42,12 +42,17 @@ struct ksmbd_notify {
u32 filter;
u32 mask;
bool watch_tree;
+ struct ksmbd_notify_event *moved_from_event;
+ u32 moved_from_mask;
+ u32 moved_from_cookie;
+ struct delayed_work moved_from_work;
};
struct ksmbd_notify_req {
wait_queue_head_t wait;
};
+#define KSMBD_NOTIFY_MOVED_FROM_MSECS 100
#define KSMBD_NOTIFY_NAME_EVENT_MASK (FS_CREATE | FS_DELETE | \
FS_MOVED_FROM | FS_MOVED_TO)
#define KSMBD_NOTIFY_EVENT_MASK (FS_ATTRIB | FS_MODIFY | \
@@ -172,6 +177,65 @@ ksmbd_notify_queue_event(struct ksmbd_notify *notify,
spin_unlock(¬ify->lock);
}
+static void
+ksmbd_notify_trigger_removed(struct ksmbd_notify *notify,
+ struct ksmbd_notify_event *event)
+{
+ ksmbd_debug(NOTIFY,
+ "Queue moved from as removed, name %s\n",
+ event->name);
+ event->action = FILE_ACTION_REMOVED;
+ event->when = ktime_get_ns();
+ ksmbd_notify_queue_event(notify, event);
+}
+
+static void ksmbd_notify_moved_from_timeout(struct work_struct *work)
+{
+ struct ksmbd_notify_event *event;
+ struct ksmbd_notify *notify;
+
+ notify = container_of(to_delayed_work(work), struct ksmbd_notify,
+ moved_from_work);
+
+ spin_lock(¬ify->lock);
+ event = notify->moved_from_event;
+ notify->moved_from_event = NULL;
+ spin_unlock(¬ify->lock);
+
+ if (!event)
+ return;
+ ksmbd_notify_trigger_removed(notify, event);
+}
+
+static void ksmbd_notify_save_moved_from(struct ksmbd_notify *notify,
+ u32 mask, u32 cookie,
+ const struct qstr *file_name)
+{
+ struct ksmbd_notify_event *event, *old_event;
+
+ event = ksmbd_notify_alloc_event(FILE_ACTION_RENAMED_OLD_NAME,
+ file_name, KSMBD_DEFAULT_GFP);
+ if (!event)
+ return;
+
+ spin_lock(¬ify->lock);
+ old_event = notify->moved_from_event;
+ notify->moved_from_event = event;
+ notify->moved_from_mask = mask;
+ notify->moved_from_cookie = cookie;
+ spin_unlock(¬ify->lock);
+
+ ksmbd_debug(NOTIFY,
+ "Saved moved from, mask 0x%x, name %.*s, cookie %u\n",
+ mask, file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "", cookie);
+ if (old_event)
+ ksmbd_notify_trigger_removed(notify, old_event);
+
+ mod_delayed_work(system_dfl_wq, ¬ify->moved_from_work,
+ msecs_to_jiffies(KSMBD_NOTIFY_MOVED_FROM_MSECS));
+}
+
static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
u32 mask, struct inode *inode,
struct inode *dir,
@@ -197,6 +261,14 @@ static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
return 0;
}
+ if (mask & FS_MOVED_FROM) {
+ ksmbd_notify_save_moved_from(notify, mask, cookie, file_name);
+ return 0;
+ }
+
+ if (mask & FS_MOVED_TO)
+ return 0;
+
if (mask & FS_CREATE) {
action = FILE_ACTION_ADDED;
} else if (mask & FS_DELETE) {
@@ -433,6 +505,8 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
notify->filter = filter;
notify->mask = mask;
notify->watch_tree = watch_tree;
+ INIT_DELAYED_WORK(¬ify->moved_from_work,
+ ksmbd_notify_moved_from_timeout);
notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
if (IS_ERR(notify->group)) {
@@ -507,6 +581,8 @@ void ksmbd_notify_remove(struct ksmbd_file *fp)
(unsigned long long)file_inode(fp->filp)->i_ino,
notify->mark->mask, notify->watch_tree);
ksmbd_notify_destroy_marks(notify);
+ cancel_delayed_work_sync(¬ify->moved_from_work);
+ kfree(notify->moved_from_event);
ksmbd_notify_free_events(¬ify->events);
kfree(notify);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 09/12] smb/server: match rename notify events
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (7 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 08/12] smb/server: save old names for rename " ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 10/12] smb/server: encode " ChenXiaoSong
` (2 subsequent siblings)
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Match old and new rename names by cookie. Save both names as events.
Example:
1. client: smbinfo notify /mnt
2. server: ksmbd.control --debug=notify
3. server: touch /export/file1
4. server: mv /export/file1 /export/file2
server debug log:
ksmbd: fid 5:5, notify event: mask=0x00000040 inode=2 name=file1 cookie=974
ksmbd: Saved moved from, mask 0x40, name file1, cookie 974
ksmbd: fid 5:5, notify event: mask=0x00000080 inode=2 name=file2 cookie=974
ksmbd: Matched rename file1 to file2, cookie 974
ksmbd: Queueing notify event, action 4, name file1
ksmbd: Queueing notify event, action 5, name file2
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 71 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 67 insertions(+), 4 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index ab1867033f26..6b5312b46e39 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -236,6 +236,67 @@ static void ksmbd_notify_save_moved_from(struct ksmbd_notify *notify,
msecs_to_jiffies(KSMBD_NOTIFY_MOVED_FROM_MSECS));
}
+static bool
+ksmbd_notify_handle_rename(struct ksmbd_notify *notify, u32 mask,
+ u32 cookie, const struct qstr *file_name)
+{
+ struct ksmbd_notify_event *from, *to;
+ u32 from_mask;
+
+ to = ksmbd_notify_alloc_event(FILE_ACTION_RENAMED_NEW_NAME, file_name,
+ KSMBD_DEFAULT_GFP);
+ if (!to)
+ return false;
+
+ spin_lock(¬ify->lock);
+ if (!notify->moved_from_event ||
+ notify->moved_from_cookie != cookie) {
+ spin_unlock(¬ify->lock);
+ kfree(to);
+ ksmbd_debug(NOTIFY,
+ "No rename source for destination %.*s, cookie %u\n",
+ file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "",
+ cookie);
+ return false;
+ }
+ from = notify->moved_from_event;
+ from_mask = notify->moved_from_mask;
+ notify->moved_from_event = NULL;
+ notify->moved_from_mask = 0;
+ notify->moved_from_cookie = 0;
+ cancel_delayed_work(¬ify->moved_from_work);
+ spin_unlock(¬ify->lock);
+
+ from->action = FILE_ACTION_RENAMED_OLD_NAME;
+ ksmbd_debug(NOTIFY, "Matched rename %.*s to %.*s, cookie %u\n",
+ (int)from->name_len, from->name,
+ file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "", cookie);
+
+ if (!ksmbd_notify_filter_match(notify, from_mask)) {
+ ksmbd_debug(NOTIFY, "Filtered rename source %.*s\n",
+ (int)from->name_len, from->name);
+ kfree(from);
+ from = NULL;
+ }
+
+ if (!ksmbd_notify_filter_match(notify, mask)) {
+ ksmbd_debug(NOTIFY, "Filtered rename destination %.*s\n",
+ file_name ? file_name->len : 0,
+ file_name ? (const char *)file_name->name : "");
+ kfree(to);
+ to = NULL;
+ }
+
+ if (from)
+ ksmbd_notify_queue_event(notify, from);
+ if (to)
+ ksmbd_notify_queue_event(notify, to);
+
+ return true;
+}
+
static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
u32 mask, struct inode *inode,
struct inode *dir,
@@ -266,10 +327,12 @@ static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
return 0;
}
- if (mask & FS_MOVED_TO)
- return 0;
-
- if (mask & FS_CREATE) {
+ if (mask & FS_MOVED_TO) {
+ if (ksmbd_notify_handle_rename(notify, mask, cookie,
+ file_name))
+ return 0;
+ action = FILE_ACTION_ADDED;
+ } else if (mask & FS_CREATE) {
action = FILE_ACTION_ADDED;
} else if (mask & FS_DELETE) {
action = FILE_ACTION_REMOVED;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 10/12] smb/server: encode notify events
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (8 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 09/12] smb/server: match " ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 11/12] smb/server: send notify events to the client ChenXiaoSong
2026-09-26 9:05 ` [PATCH 12/12] smb/server: break directory leases before sending notify events ChenXiaoSong
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Sort and merge events, then encode their names as UTF-16 records.
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 106 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 6b5312b46e39..da30cc058d78 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -12,6 +12,7 @@
#include <linux/fsnotify_backend.h>
#include <linux/dcache.h>
+#include <linux/list_sort.h>
#include "glob.h"
#include "../common/smb2status.h"
#include "connection.h"
@@ -650,6 +651,111 @@ void ksmbd_notify_remove(struct ksmbd_file *fp)
kfree(notify);
}
+static int ksmbd_notify_event_cmp(void *priv, const struct list_head *a,
+ const struct list_head *b)
+{
+ struct ksmbd_notify_event *event_a;
+ struct ksmbd_notify_event *event_b;
+
+ event_a = list_entry(a, struct ksmbd_notify_event, list);
+ event_b = list_entry(b, struct ksmbd_notify_event, list);
+
+ if (event_a->when < event_b->when)
+ return -1;
+ if (event_a->when > event_b->when)
+ return 1;
+ return 0;
+}
+
+static void *ksmbd_notify_encode_events(struct ksmbd_work *work,
+ struct list_head *events,
+ u32 max_len, size_t *data_len)
+{
+ struct ksmbd_notify_event *event;
+ u8 *data = NULL;
+ size_t len = 0;
+
+ list_sort(NULL, events, ksmbd_notify_event_cmp);
+
+ list_for_each_entry(event, events, list) {
+ struct file_notify_information *info;
+ struct ksmbd_notify_event *next;
+ size_t alloc_len, buf_len, name_buf_len, record_len;
+ bool last = list_is_last(&event->list, events);
+ __le16 *name;
+ u8 *new_data;
+ int name_len;
+
+ /* Coalesce adjacent, case-sensitive duplicate records. */
+ if (!last) {
+ next = list_next_entry(event, list);
+ if (event->action == next->action &&
+ event->name_len == next->name_len &&
+ !memcmp(event->name, next->name, event->name_len))
+ continue;
+ }
+
+ name_buf_len = (event->name_len + 1) * sizeof(__le16);
+ name = kmalloc(name_buf_len, KSMBD_DEFAULT_GFP);
+ if (!name) {
+ pr_err("Failed to allocate notify event name buffer\n");
+ goto fail;
+ }
+
+ name_len = smbConvertToUTF16(name, event->name,
+ event->name_len,
+ work->conn->local_nls, 0);
+ name_len *= sizeof(__le16);
+ record_len = sizeof(*info) + name_len;
+ alloc_len = ALIGN(record_len, 4);
+ if (alloc_len > SIZE_MAX - 7 ||
+ len > SIZE_MAX - alloc_len - 7) {
+ pr_err("Notify event data length overflow\n");
+ kfree(name);
+ goto fail;
+ }
+
+ /*
+ * Compound response finalization can extend the last iov to an
+ * 8-byte boundary. Keep that padding inside this allocation and
+ * zeroed, while reporting only the protocol payload in data_len.
+ */
+ buf_len = ALIGN(len + alloc_len, 8);
+ new_data = kvrealloc(data, buf_len, KSMBD_DEFAULT_GFP);
+ if (!new_data) {
+ pr_err("Failed to allocate notify event data, length %zu\n",
+ buf_len);
+ kfree(name);
+ goto fail;
+ }
+ data = new_data;
+ memset(data + len, 0, buf_len - len);
+
+ info = (struct file_notify_information *)(data + len);
+ info->NextEntryOffset = last ? 0 : cpu_to_le32(alloc_len);
+ info->Action = cpu_to_le32(event->action);
+ info->FileNameLength = cpu_to_le32(name_len);
+ memcpy(info->FileName, name, name_len);
+ kfree(name);
+
+ len += alloc_len;
+ if (len > max_len) {
+ ksmbd_debug(NOTIFY,
+ "Notify event data length %zu exceeds output buffer %u\n",
+ len, max_len);
+ goto fail;
+ }
+ }
+
+ *data_len = len;
+ return data;
+
+fail:
+ kvfree(data);
+ *data_len = 0;
+ return NULL;
+}
+
static struct ksmbd_file *
ksmbd_notify_validate_req(struct ksmbd_work *work,
struct smb2_change_notify_req *req,
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 11/12] smb/server: send notify events to the client
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (9 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 10/12] smb/server: encode " ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
2026-09-26 9:05 ` [PATCH 12/12] smb/server: break directory leases before sending notify events ChenXiaoSong
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Wake one pending request and send its saved events in the reply.
Example:
smbinfo notify /mnt
# server: touch /export/file1
Notifications received, returned data_len is 48
Action: 0x00000001, FileName: file1
Action: 0x00000003, FileName: file1
# server: mv /export/file1 /export/file2
Notifications received, returned data_len is 48
Action: 0x00000004, FileName: file1
Action: 0x00000005, FileName: file2
Suggested-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 188 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 185 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index da30cc058d78..dc62b5d6d17d 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -47,12 +47,18 @@ struct ksmbd_notify {
u32 moved_from_mask;
u32 moved_from_cookie;
struct delayed_work moved_from_work;
+ struct delayed_work broadcast_work;
};
struct ksmbd_notify_req {
wait_queue_head_t wait;
+ struct list_head events;
+ unsigned int num_events;
+ bool notified;
};
+#define KSMBD_NOTIFY_BROADCAST_MSECS 1000
+#define KSMBD_NOTIFY_BROADCAST_MAX_EVENTS 100
#define KSMBD_NOTIFY_MOVED_FROM_MSECS 100
#define KSMBD_NOTIFY_NAME_EVENT_MASK (FS_CREATE | FS_DELETE | \
FS_MOVED_FROM | FS_MOVED_TO)
@@ -169,13 +175,28 @@ static void
ksmbd_notify_queue_event(struct ksmbd_notify *notify,
struct ksmbd_notify_event *event)
{
+ unsigned long broadcast_delay = 0;
+ bool schedule_broadcast = false;
+
ksmbd_debug(NOTIFY, "Queueing notify event, action %u, name %s\n",
event->action, event->name);
spin_lock(¬ify->lock);
list_add_tail(&event->list, ¬ify->events);
notify->num_events++;
+ /* Start one timer per batch; reaching the limit flushes it early. */
+ if (notify->num_events == 1) {
+ broadcast_delay =
+ msecs_to_jiffies(KSMBD_NOTIFY_BROADCAST_MSECS);
+ schedule_broadcast = true;
+ } else if (notify->num_events >= KSMBD_NOTIFY_BROADCAST_MAX_EVENTS) {
+ schedule_broadcast = true;
+ }
spin_unlock(¬ify->lock);
+
+ if (schedule_broadcast)
+ mod_delayed_work(system_dfl_long_wq, ¬ify->broadcast_work,
+ broadcast_delay);
}
static void
@@ -298,6 +319,57 @@ ksmbd_notify_handle_rename(struct ksmbd_notify *notify, u32 mask,
return true;
}
+/*
+ * Give queued events to the oldest pending notify request. The caller holds
+ * notify->lock so this lookup and transfer are atomic with synchronous takes.
+ */
+static bool ksmbd_notify_wake_waiter(struct ksmbd_notify *notify)
+{
+ struct ksmbd_notify_req *notify_req;
+ struct ksmbd_work *work;
+ bool found = false;
+
+ spin_lock(¬ify->fp->f_lock);
+ list_for_each_entry(work, ¬ify->fp->blocked_works, fp_entry) {
+ if (READ_ONCE(work->state) != KSMBD_WORK_ACTIVE ||
+ work->cancel_fn != smb2_notify_cancel ||
+ !work->cancel_argv)
+ continue;
+
+ notify_req = work->cancel_argv[0];
+ if (READ_ONCE(notify_req->notified))
+ continue;
+
+ list_splice_tail_init(¬ify->events, ¬ify_req->events);
+ notify_req->num_events = notify->num_events;
+ notify->num_events = 0;
+ WRITE_ONCE(notify_req->notified, true);
+ wake_up(¬ify_req->wait);
+ found = true;
+ break;
+ }
+ spin_unlock(¬ify->fp->f_lock);
+
+ return found;
+}
+
+static void ksmbd_notify_broadcast(struct ksmbd_notify *notify)
+{
+ spin_lock(¬ify->lock);
+ if (!list_empty(¬ify->events))
+ ksmbd_notify_wake_waiter(notify);
+ spin_unlock(¬ify->lock);
+}
+
+static void ksmbd_notify_broadcast_work(struct work_struct *work)
+{
+ struct ksmbd_notify *notify;
+
+ notify = container_of(to_delayed_work(work), struct ksmbd_notify,
+ broadcast_work);
+ ksmbd_notify_broadcast(notify);
+}
+
static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
u32 mask, struct inode *inode,
struct inode *dir,
@@ -571,6 +643,8 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
notify->watch_tree = watch_tree;
INIT_DELAYED_WORK(¬ify->moved_from_work,
ksmbd_notify_moved_from_timeout);
+ INIT_DELAYED_WORK(¬ify->broadcast_work,
+ ksmbd_notify_broadcast_work);
notify->group = fsnotify_alloc_group(&ksmbd_notify_fsnotify_ops, 0);
if (IS_ERR(notify->group)) {
@@ -646,11 +720,51 @@ void ksmbd_notify_remove(struct ksmbd_file *fp)
notify->mark->mask, notify->watch_tree);
ksmbd_notify_destroy_marks(notify);
cancel_delayed_work_sync(¬ify->moved_from_work);
+ cancel_delayed_work_sync(¬ify->broadcast_work);
kfree(notify->moved_from_event);
ksmbd_notify_free_events(¬ify->events);
kfree(notify);
}
+static unsigned int
+ksmbd_notify_take_events(struct ksmbd_notify *notify, struct list_head *events)
+{
+ unsigned int num_events;
+
+ spin_lock(¬ify->lock);
+ num_events = notify->num_events;
+ if (num_events && ksmbd_notify_wake_waiter(notify)) {
+ num_events = 0;
+ } else if (num_events) {
+ list_splice_tail_init(¬ify->events, events);
+ notify->num_events = 0;
+ }
+ spin_unlock(¬ify->lock);
+
+ if (num_events)
+ ksmbd_debug(NOTIFY,
+ "Take %u queued notify events for synchronous reply\n",
+ num_events);
+ return num_events;
+}
+
+static void
+ksmbd_notify_requeue_events(struct ksmbd_notify *notify,
+ struct ksmbd_notify_req *notify_req)
+{
+ if (!notify_req->num_events)
+ return;
+
+ spin_lock(¬ify->lock);
+ /* These events happened before any events already on the queue. */
+ list_splice_init(¬ify_req->events, ¬ify->events);
+ notify->num_events += notify_req->num_events;
+ notify_req->num_events = 0;
+ spin_unlock(¬ify->lock);
+
+ ksmbd_notify_broadcast(notify);
+}
+
static int ksmbd_notify_event_cmp(void *priv, const struct list_head *a,
const struct list_head *b)
{
@@ -756,6 +870,48 @@ static void *ksmbd_notify_encode_events(struct ksmbd_work *work,
return NULL;
}
+static int ksmbd_notify_reply(struct ksmbd_work *work,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp,
+ struct list_head *events)
+{
+ u32 max_len = le32_to_cpu(req->OutputBufferLength);
+ size_t data_len = 0;
+ void *data;
+ int err;
+
+ data = ksmbd_notify_encode_events(work, events, max_len, &data_len);
+ ksmbd_notify_free_events(events);
+
+ /* Maps a successful zero-length notify reply to ENUM_DIR. */
+ if (!data_len) {
+ ksmbd_debug(NOTIFY,
+ "Return notify enum directory, output buffer length %u\n",
+ max_len);
+ rsp->hdr.Status = STATUS_NOTIFY_ENUM_DIR;
+ return 0;
+ }
+
+ rsp->StructureSize = cpu_to_le16(9);
+ rsp->OutputBufferOffset = cpu_to_le16(72);
+ rsp->OutputBufferLength = cpu_to_le32(data_len);
+ err = ksmbd_iov_pin_rsp_read(work, rsp,
+ offsetof(struct smb2_change_notify_rsp, Buffer),
+ data, data_len);
+ if (err) {
+ pr_err("Failed to pin notify response data, length %zu: %d\n",
+ data_len, err);
+ kvfree(data);
+ rsp->hdr.Status = STATUS_INSUFFICIENT_RESOURCES;
+ } else {
+ ksmbd_debug(NOTIFY,
+ "Prepared notify response, data length %zu\n",
+ data_len);
+ }
+
+ return err;
+}
+
static struct ksmbd_file *
ksmbd_notify_validate_req(struct ksmbd_work *work,
struct smb2_change_notify_req *req,
@@ -876,11 +1032,15 @@ static int ksmbd_notify_wait(struct ksmbd_work *work,
spin_unlock(&fp->f_lock);
read_unlock(&work->sess->file_table.lock);
+ /* Close the race between the synchronous check and queuing the waiter. */
+ ksmbd_notify_broadcast(notify);
+
ksmbd_debug(NOTIFY, "Notify request pending, async id %d\n",
work->async_id);
smb2_send_interim_resp(work, STATUS_PENDING);
err = wait_event_interruptible(notify_req->wait,
+ READ_ONCE(notify_req->notified) ||
READ_ONCE(work->state) !=
KSMBD_WORK_ACTIVE);
if (err && READ_ONCE(work->state) == KSMBD_WORK_ACTIVE) {
@@ -916,6 +1076,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
struct ksmbd_notify_req notify_req = {};
struct ksmbd_notify *notify = NULL;
struct ksmbd_file *fp = NULL;
+ LIST_HEAD(events);
void **argv = NULL;
bool async_work = false;
int err = 0;
@@ -934,6 +1095,12 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
goto out;
}
+ /* Changes which arrived without a waiter are returned synchronously. */
+ if (ksmbd_notify_take_events(notify, &events)) {
+ err = ksmbd_notify_reply(work, req, rsp, &events);
+ goto out;
+ }
+
argv = kmalloc_obj(*argv, KSMBD_DEFAULT_GFP);
if (!argv) {
pr_err("Failed to allocate notify cancel arguments\n");
@@ -942,6 +1109,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
goto out;
}
init_waitqueue_head(¬ify_req.wait);
+ INIT_LIST_HEAD(¬ify_req.events);
argv[0] = ¬ify_req;
err = setup_async_work(work, smb2_notify_cancel, argv);
@@ -959,16 +1127,30 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
}
if (work->state == KSMBD_WORK_CLOSED) {
+ ksmbd_notify_requeue_events(notify, ¬ify_req);
rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
ksmbd_debug(NOTIFY, "Notify handle closed, async id %d\n",
work->async_id);
- } else {
+ smb2_send_interim_resp(work, rsp->hdr.Status);
+ work->send_no_response = 1;
+ } else if (work->state == KSMBD_WORK_CANCELLED) {
+ ksmbd_notify_requeue_events(notify, ¬ify_req);
rsp->hdr.Status = STATUS_CANCELLED;
ksmbd_debug(NOTIFY, "Notify request cancelled, async id %d\n",
work->async_id);
+ smb2_send_interim_resp(work, rsp->hdr.Status);
+ work->send_no_response = 1;
+ } else {
+ /* Complete the request using the AsyncId sent in STATUS_PENDING. */
+ rsp->hdr.Flags |= SMB2_FLAGS_ASYNC_COMMAND;
+ rsp->hdr.Id.AsyncId = cpu_to_le64(work->async_id);
+ err = ksmbd_notify_reply(work, req, rsp,
+ ¬ify_req.events);
+ if (!err)
+ ksmbd_debug(NOTIFY,
+ "Completed notify request, async id %d\n",
+ work->async_id);
}
- smb2_send_interim_resp(work, rsp->hdr.Status);
- work->send_no_response = 1;
out:
if (rsp->hdr.Status != STATUS_SUCCESS && !work->send_no_response)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread* [PATCH 12/12] smb/server: break directory leases before sending notify events
2026-09-26 9:05 [PATCH 00/12] smb/server: change notify support ChenXiaoSong
` (10 preceding siblings ...)
2026-09-26 9:05 ` [PATCH 11/12] smb/server: send notify events to the client ChenXiaoSong
@ 2026-09-26 9:05 ` ChenXiaoSong
11 siblings, 0 replies; 17+ messages in thread
From: ChenXiaoSong @ 2026-09-26 9:05 UTC (permalink / raw)
To: linkinjeon, tom, senozhatsky, chenxiaosong; +Cc: linux-cifs, ChenXiaoSong
From: ChenXiaoSong <chenxiaosong@kylinos.cn>
Reproducer:
1. ksmbd: `ksmbd.conf`:
[global]
smb2 leases = yes
2. ksmbd: systemctl start ksmbd
3. Windows 11 File Explorer:
Mount the share and enter the top-level directory of the mount point.
4. ksmbd: touch /export/file
5. Windows 11 File Explorer:
`file` does not appear in the top-level directory of the mount point.
Windows can keep directory data in a cache while it has a directory
lease. A file change made on the server does not use the SMB request
path, so the lease is not broken.
The server sends a notify event, but Windows may still use the old data.
Some new files are then not shown in File Explorer.
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 34 +++++++++++++++++++++++++++++++---
fs/smb/server/oplock.c | 40 ++++++++++++++++++++++++++++++++++++++++
fs/smb/server/oplock.h | 1 +
3 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index dc62b5d6d17d..ff014f856ac9 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -18,6 +18,7 @@
#include "connection.h"
#include "ksmbd_work.h"
#include "notify.h"
+#include "oplock.h"
#include "smb_common.h"
#include "smb2pdu.h"
#include "vfs_cache.h"
@@ -361,13 +362,34 @@ static void ksmbd_notify_broadcast(struct ksmbd_notify *notify)
spin_unlock(¬ify->lock);
}
+static bool ksmbd_notify_events_pending(struct ksmbd_notify *notify)
+{
+ bool pending;
+
+ spin_lock(¬ify->lock);
+ pending = !list_empty(¬ify->events);
+ spin_unlock(¬ify->lock);
+
+ return pending;
+}
+
+/* Invalidate directory caches before making the change visible to a client. */
+static void ksmbd_notify_dispatch(struct ksmbd_notify *notify)
+{
+ if (!ksmbd_notify_events_pending(notify))
+ return;
+
+ smb_break_dir_lease(notify->fp);
+ ksmbd_notify_broadcast(notify);
+}
+
static void ksmbd_notify_broadcast_work(struct work_struct *work)
{
struct ksmbd_notify *notify;
notify = container_of(to_delayed_work(work), struct ksmbd_notify,
broadcast_work);
- ksmbd_notify_broadcast(notify);
+ ksmbd_notify_dispatch(notify);
}
static int ksmbd_notify_handle_inode_event(struct ksmbd_notify *notify,
@@ -731,6 +753,12 @@ ksmbd_notify_take_events(struct ksmbd_notify *notify, struct list_head *events)
{
unsigned int num_events;
+ if (!ksmbd_notify_events_pending(notify))
+ return 0;
+
+ /* This path bypasses broadcast_work, so break directory leases here. */
+ smb_break_dir_lease(notify->fp);
+
spin_lock(¬ify->lock);
num_events = notify->num_events;
if (num_events && ksmbd_notify_wake_waiter(notify)) {
@@ -762,7 +790,7 @@ ksmbd_notify_requeue_events(struct ksmbd_notify *notify,
notify_req->num_events = 0;
spin_unlock(¬ify->lock);
- ksmbd_notify_broadcast(notify);
+ ksmbd_notify_dispatch(notify);
}
static int ksmbd_notify_event_cmp(void *priv, const struct list_head *a,
@@ -1033,7 +1061,7 @@ static int ksmbd_notify_wait(struct ksmbd_work *work,
read_unlock(&work->sess->file_table.lock);
/* Close the race between the synchronous check and queuing the waiter. */
- ksmbd_notify_broadcast(notify);
+ ksmbd_notify_dispatch(notify);
ksmbd_debug(NOTIFY, "Notify request pending, async id %d\n",
work->async_id);
diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c
index 1b8c3482d1e4..ec2ee2acca35 100644
--- a/fs/smb/server/oplock.c
+++ b/fs/smb/server/oplock.c
@@ -1526,6 +1526,46 @@ void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
ksmbd_inode_put(p_ci);
}
+/**
+ * smb_break_dir_lease() - break leases when a directory changes
+ * @fp: open directory that changed
+ *
+ * Some directory changes do not go through the SMB request path. fsnotify
+ * reports these changes. Break the directory leases before sending the
+ * changes to the SMB client.
+ *
+ * This function can sleep while it waits for a reply from the client. Do not
+ * call it from the fsnotify callback.
+ */
+void smb_break_dir_lease(struct ksmbd_file *fp)
+{
+ struct ksmbd_inode *ci = fp->f_ci;
+ struct oplock_info *opinfo;
+ LIST_HEAD(brk_list);
+
+ down_read(&ci->m_lock);
+ list_for_each_entry(opinfo, &ci->m_op_list, op_entry) {
+ if (!opinfo->conn || !opinfo->is_lease ||
+ !opinfo->o_lease->is_dir ||
+ opinfo->o_lease->state == SMB2_LEASE_NONE_LE)
+ continue;
+
+ if (!atomic_inc_not_zero(&opinfo->refcount))
+ continue;
+
+ if (ksmbd_conn_releasing(opinfo->conn)) {
+ opinfo_put(opinfo);
+ continue;
+ }
+
+ if (oplock_break_add(&brk_list, opinfo))
+ opinfo_put(opinfo);
+ }
+ up_read(&ci->m_lock);
+
+ oplock_break_drain_none(&brk_list, ci);
+}
+
void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp)
{
struct oplock_info *opinfo;
diff --git a/fs/smb/server/oplock.h b/fs/smb/server/oplock.h
index b08d21758e07..72ebcbacd2be 100644
--- a/fs/smb/server/oplock.h
+++ b/fs/smb/server/oplock.h
@@ -137,6 +137,7 @@ int find_same_lease_key(struct ksmbd_conn *conn, struct ksmbd_inode *ci,
void destroy_lease_table(struct ksmbd_conn *conn);
void smb_send_parent_lease_break_noti(struct ksmbd_file *fp,
struct lease_ctx_info *lctx);
+void smb_break_dir_lease(struct ksmbd_file *fp);
void smb_lazy_parent_lease_break_close(struct ksmbd_file *fp);
int smb2_check_durable_oplock(struct ksmbd_conn *conn,
struct ksmbd_share_config *share,
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread