[RFC PATCH 11/11] smb/server: break directory leases before sending notify events
ChenXiaoSong <[email protected]> Thu, 23 Jul 2026 03:16:39 +0000
| Newsgroups | org.kernel.vger.linux-cifs |
|---|---|
| Message-ID | <[email protected]> |
From: ChenXiaoSong <[email protected]> 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 <[email protected]> --- 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 63e7ed7d615c..1b27c17a8aef 100644 --- a/fs/smb/server/notify.c +++ b/fs/smb/server/notify.c @@ -21,6 +21,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" @@ -346,13 +347,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 fsnotify_mark *mark, @@ -576,6 +598,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) { @@ -605,7 +633,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, @@ -847,7 +875,7 @@ static int ksmbd_notify_wait(struct ksmbd_work *work, spin_unlock(&fp->f_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 591b2fca1d4e..6bd870e84cf6 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -1404,6 +1404,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); +} + 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 23274b645ede..3f05fd684640 100644 --- a/fs/smb/server/oplock.h +++ b/fs/smb/server/oplock.h @@ -136,6 +136,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.54.0