[PATCH v4 3/3] ksmbd: refactor smb2_notify() to a blocking wait
ChenXiaoSong
chenxiaosong at chenxiaosong.com
Sun Aug 9 16:19:16 UTC 2026
From: Gael Blivet <gael.blivet at gmail.com>
The previous design registered a synthetic work struct (in_work)
directly on conn->async_requests and deferred the response to a
workqueue -- a bespoke async/cancel implementation duplicating what
setup_async_work(), release_async_work(), and smb2_send_interim_resp()
already provide for smb2_lock()'s pending byte-range lock.
Replace it with that same pattern: setup_async_work() on the calling
work itself, registered on fp->blocked_works, woken by cancel or by
the handle closing via the existing set_close_state_blocked_works().
This removes the synthetic work struct, the notify_pendings list and
its close-time drain, and the deferred workqueue send, leaving
smb2_notify() sharing the same async/cancel machinery as smb2_lock()
instead of its own separate copy.
The worker now blocks on ksmbd_wq for as long as the watch stays
open, instead of returning immediately. This also makes the skeleton
ready for a future event-delivery implementation on the same
blocking wait.
Suggested-by: ChenXiaoSong <chenxiaosong at kylinos.cn>
Signed-off-by: Gael Blivet <gael.blivet at gmail.com>
Assisted-by: Claude:claude-sonnet-5
Tested-by: ChenXiaoSong <chenxiaosong at kylinos.cn>
Reviewed-by: ChenXiaoSong <chenxiaosong at kylinos.cn>
---
fs/smb/server/ksmbd_work.h | 1 +
fs/smb/server/smb2pdu.c | 93 ++++++++++++++++++++++++++++++++++++--
2 files changed, 89 insertions(+), 5 deletions(-)
diff --git a/fs/smb/server/ksmbd_work.h b/fs/smb/server/ksmbd_work.h
index 0b55ad457cd3..4c0b7b2535b9 100644
--- a/fs/smb/server/ksmbd_work.h
+++ b/fs/smb/server/ksmbd_work.h
@@ -113,6 +113,7 @@ struct ksmbd_work {
struct list_head request_entry;
/* List head at conn->async_requests */
struct list_head async_request_entry;
+ /* List head at ksmbd_file->blocked_works */
struct list_head fp_entry;
};
diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index 81a4dd232b6d..1e17fcd7d60a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -10977,6 +10977,28 @@ 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
@@ -10987,6 +11009,11 @@ 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");
@@ -10997,13 +11024,69 @@ int smb2_notify(struct ksmbd_work *work)
if (work->next_smb2_rcv_hdr_off && req->hdr.NextCommand) {
rsp->hdr.Status = STATUS_INTERNAL_ERROR;
- smb2_set_err_rsp(work);
- return -EIO;
+ err = -EIO;
+ goto out;
}
- smb2_set_err_rsp(work);
- rsp->hdr.Status = STATUS_NOT_IMPLEMENTED;
- return -EOPNOTSUPP;
+ 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;
+
+ spin_lock(&fp->f_lock);
+ list_add_tail(&work->fp_entry, &fp->blocked_works);
+ spin_unlock(&fp->f_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;
}
/**
--
2.54.0
More information about the samba-technical
mailing list