From: ChenXiaoSong <chenxiaosong@chenxiaosong.com>
To: smfrench@gmail.com, linkinjeon@kernel.org, pc@manguebit.org,
ronniesahlberg@gmail.com, sprasad@microsoft.com, tom@talpey.com,
bharathsm@microsoft.com, senozhatsky@chromium.org,
dhowells@redhat.com, metze@samba.org, gael.blivet@gmail.com
Cc: linux-cifs@vger.kernel.org, ChenXiaoSong <chenxiaosong@kylinos.cn>
Subject: [RFC PATCH 10/11] smb/server: send notify events to the client
Date: Thu, 23 Jul 2026 03:16:38 +0000 [thread overview]
Message-ID: <20260723031644.312866-11-chenxiaosong@chenxiaosong.com> (raw)
In-Reply-To: <20260723031644.312866-1-chenxiaosong@chenxiaosong.com>
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
Signed-off-by: ChenXiaoSong <chenxiaosong@kylinos.cn>
---
fs/smb/server/notify.c | 171 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 171 insertions(+)
diff --git a/fs/smb/server/notify.c b/fs/smb/server/notify.c
index 6fff7b5584b9..63e7ed7d615c 100644
--- a/fs/smb/server/notify.c
+++ b/fs/smb/server/notify.c
@@ -53,12 +53,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)
@@ -163,13 +169,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
@@ -292,6 +313,48 @@ ksmbd_notify_handle_rename(struct ksmbd_notify *notify, u32 mask,
return true;
}
+static void ksmbd_notify_broadcast(struct ksmbd_notify *notify)
+{
+ struct ksmbd_notify_req *notify_req;
+ struct ksmbd_work *work;
+
+ spin_lock(¬ify->lock);
+ if (list_empty(¬ify->events))
+ goto out_unlock;
+
+ 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);
+ break;
+ }
+ spin_unlock(¬ify->fp->f_lock);
+
+out_unlock:
+ 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 fsnotify_mark *mark,
u32 mask, struct inode *inode,
struct inode *dir,
@@ -454,6 +517,8 @@ static int ksmbd_notify_add(struct ksmbd_file *fp, u32 mask, u32 filter,
notify->max_buffer_size = max_buffer_size;
INIT_DELAYED_WORK(¬ify->moved_from_work,
ksmbd_notify_moved_from_timeout);
+ INIT_DELAYED_WORK(¬ify->broadcast_work,
+ ksmbd_notify_broadcast_work);
mark = ksmbd_notify_add_mark(notify, mask, ¬ify->group);
if (IS_ERR(mark)) {
@@ -500,11 +565,49 @@ void ksmbd_notify_remove(struct ksmbd_file *fp)
notify->mark->mask);
ksmbd_notify_destroy_mark(notify->group, notify->mark);
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) {
+ 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)
{
@@ -603,6 +706,50 @@ static void *ksmbd_notify_encode_events(struct ksmbd_work *work,
return NULL;
}
+static int ksmbd_notify_reply(struct ksmbd_work *work,
+ struct ksmbd_notify *notify,
+ struct smb2_change_notify_req *req,
+ struct smb2_change_notify_rsp *rsp,
+ struct list_head *events)
+{
+ u32 max_len = min(le32_to_cpu(req->OutputBufferLength),
+ notify->max_buffer_size);
+ 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,
@@ -699,11 +846,15 @@ static int ksmbd_notify_wait(struct ksmbd_work *work,
list_add_tail(&work->fp_entry, &fp->blocked_works);
spin_unlock(&fp->f_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) {
@@ -734,6 +885,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
struct ksmbd_notify_req *notify_req = NULL;
struct ksmbd_notify *notify = NULL;
struct ksmbd_file *fp = NULL;
+ LIST_HEAD(events);
void **argv = NULL;
bool async_work = false;
int err = 0;
@@ -752,6 +904,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, notify, req, rsp, &events);
+ goto out;
+ }
+
notify_req = kzalloc_obj(*notify_req, KSMBD_DEFAULT_GFP);
if (!notify_req) {
pr_err("Failed to allocate notify request\n");
@@ -769,6 +927,7 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
}
init_waitqueue_head(¬ify_req->wait);
+ INIT_LIST_HEAD(¬ify_req->events);
argv[0] = notify_req;
err = setup_async_work(work, smb2_notify_cancel, argv);
if (err) {
@@ -781,17 +940,29 @@ int ksmbd_handle_notify(struct ksmbd_work *work,
err = ksmbd_notify_wait(work, notify, notify_req);
if (work->state == KSMBD_WORK_CANCELLED) {
+ ksmbd_notify_requeue_events(notify, notify_req);
ksmbd_debug(NOTIFY, "Notify request cancelled, async id %d\n",
work->async_id);
rsp->hdr.Status = STATUS_CANCELLED;
smb2_send_interim_resp(work, STATUS_CANCELLED);
work->send_no_response = 1;
} else if (work->state == KSMBD_WORK_CLOSED) {
+ ksmbd_notify_requeue_events(notify, notify_req);
ksmbd_debug(NOTIFY, "Notify handle closed, async id %d\n",
work->async_id);
rsp->hdr.Status = STATUS_NOTIFY_CLEANUP;
smb2_send_interim_resp(work, STATUS_NOTIFY_CLEANUP);
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, notify, req, rsp,
+ ¬ify_req->events);
+ if (!err)
+ ksmbd_debug(NOTIFY,
+ "Completed notify request, async id %d\n",
+ work->async_id);
}
out:
--
2.54.0
next prev parent reply other threads:[~2026-07-23 3:18 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 3:16 [RFC PATCH 00/11] smb/server: change notify support ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 01/11] smb/server: add debug type for change notify ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 02/11] smb/server: validate notify requests ChenXiaoSong
2026-07-25 13:31 ` Namjae Jeon
2026-07-23 3:16 ` [RFC PATCH 03/11] smb/server: support canceling " ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 04/11] smb/server: watch directories for changes ChenXiaoSong
2026-07-25 13:44 ` Namjae Jeon
2026-07-23 3:16 ` [RFC PATCH 05/11] smb/server: keep notify watches on file handles ChenXiaoSong
2026-07-25 13:51 ` Namjae Jeon
2026-07-23 3:16 ` [RFC PATCH 06/11] smb/server: save simple notify events ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 07/11] smb/server: save old names for rename " ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 08/11] smb/server: match " ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 09/11] smb/server: encode " ChenXiaoSong
2026-07-23 3:16 ` ChenXiaoSong [this message]
2026-07-25 13:58 ` [RFC PATCH 10/11] smb/server: send notify events to the client Namjae Jeon
2026-07-25 14:02 ` Namjae Jeon
2026-07-25 14:14 ` ChenXiaoSong
2026-07-23 3:16 ` [RFC PATCH 11/11] smb/server: break directory leases before sending notify events ChenXiaoSong
2026-07-24 13:35 ` [RFC PATCH 00/11] smb/server: change notify support ChenXiaoSong
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260723031644.312866-11-chenxiaosong@chenxiaosong.com \
--to=chenxiaosong@chenxiaosong.com \
--cc=bharathsm@microsoft.com \
--cc=chenxiaosong@kylinos.cn \
--cc=dhowells@redhat.com \
--cc=gael.blivet@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=linux-cifs@vger.kernel.org \
--cc=metze@samba.org \
--cc=pc@manguebit.org \
--cc=ronniesahlberg@gmail.com \
--cc=senozhatsky@chromium.org \
--cc=smfrench@gmail.com \
--cc=sprasad@microsoft.com \
--cc=tom@talpey.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox