[PATCH v4 3/3] ksmbd: refactor smb2_notify() to a blocking wait
ChenXiaoSong <[email protected]>
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
From: Gael Blivet <[email protected]> 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 <[email protected]> Signed-off-by: Gael Blivet <[email protected]> Assisted-by: Claude:claude-sonnet-5 Tested-by: ChenXiaoSong <[email protected]> Reviewed-by: ChenXiaoSong <[email protected]> --- 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