[RFC PATCH samba] smbd: break directory leases before sending notify events

ChenXiaoSong chenxiaosong at chenxiaosong.com
Mon Aug 10 08:29:55 UTC 2026


From: ChenXiaoSong <chenxiaosong at kylinos.cn>

Reproducer:

  1. samba: `smb.conf`:
     [global]
         smb3 directory leases = yes
  2. samba:
     systemctl start smb # fedora
  3. Windows 11 File Explorer:
     Mount the share and enter the top-level directory of the mount point.
  4. samba: 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 at kylinos.cn>
---
 source3/smbd/notify.c           |  7 ++++++-
 source3/smbd/notifyd/fcn_wait.c |  2 +-
 source3/smbd/notifyd/notifyd.c  |  2 +-
 source3/smbd/proto.h            |  1 +
 source3/smbd/smb2_oplock.c      | 37 +++++++++++++++++++++++++++++++++
 5 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c
index 5970a7a9220..24dcedb9dba 100644
--- a/source3/smbd/notify.c
+++ b/source3/smbd/notify.c
@@ -262,10 +262,15 @@ static struct files_struct *notify_fsp_cb(struct files_struct *fsp,
 					  void *private_data)
 {
 	struct notify_fsp_state *state = private_data;
+	uint32_t action = state->e->action;
 
 	if (fsp == state->notified_fsp) {
 		DBG_DEBUG("notify_callback called for %s\n", fsp_str_dbg(fsp));
-		notify_fsp(fsp, state->when, state->e->action, state->e->path);
+		if (action & NOTIFY_ACTION_DIRLEASE_BREAK) {
+			contend_dirleases_by_fsp(fsp);
+			action &= ~NOTIFY_ACTION_DIRLEASE_BREAK;
+		}
+		notify_fsp(fsp, state->when, action, state->e->path);
 		return fsp;
 	}
 
diff --git a/source3/smbd/notifyd/fcn_wait.c b/source3/smbd/notifyd/fcn_wait.c
index e32240d911a..6b5b6e66d43 100644
--- a/source3/smbd/notifyd/fcn_wait.c
+++ b/source3/smbd/notifyd/fcn_wait.c
@@ -256,7 +256,7 @@ NTSTATUS fcn_wait_recv(
 		*when = evt->msg.when;
 	}
 	if (action != NULL) {
-		*action = evt->msg.action;
+		*action = evt->msg.action & ~NOTIFY_ACTION_DIRLEASE_BREAK;
 	}
 
 	DLIST_REMOVE(state->events, evt);
diff --git a/source3/smbd/notifyd/notifyd.c b/source3/smbd/notifyd/notifyd.c
index 0b07ab3e435..a42a7a9162e 100644
--- a/source3/smbd/notifyd/notifyd.c
+++ b/source3/smbd/notifyd/notifyd.c
@@ -579,7 +579,7 @@ static void notifyd_sys_callback(struct sys_notify_context *ctx,
 
 	msg = (struct notify_trigger_msg) {
 		.when = timespec_current(),
-		.action = ev->action,
+		.action = ev->action | NOTIFY_ACTION_DIRLEASE_BREAK,
 		.filter = filter,
 	};
 
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
index 7ced3a3ffd5..012039c7a5f 100644
--- a/source3/smbd/proto.h
+++ b/source3/smbd/proto.h
@@ -730,6 +730,7 @@ void smbd_contend_level2_oplocks_end(files_struct *fsp,
 void contend_dirleases(struct connection_struct *conn,
 		       const struct smb_filename *smb_fname,
 		       const struct smb2_lease *lease);
+void contend_dirleases_by_fsp(struct files_struct *fsp);
 bool init_oplocks(struct smbd_server_connection *sconn);
 void init_kernel_oplocks(struct smbd_server_connection *sconn);
 
diff --git a/source3/smbd/smb2_oplock.c b/source3/smbd/smb2_oplock.c
index 2489090af55..4de33889eff 100644
--- a/source3/smbd/smb2_oplock.c
+++ b/source3/smbd/smb2_oplock.c
@@ -1398,6 +1398,43 @@ void contend_dirleases(struct connection_struct *conn,
 	TALLOC_FREE(lck);
 }
 
+void contend_dirleases_by_fsp(struct files_struct *fsp)
+{
+	struct dirlease_break_state state = {
+		.sconn = fsp->conn->sconn,
+		.file_id = fsp->file_id,
+	};
+	struct share_mode_lock *lck = NULL;
+	uint32_t access_mask, share_mode;
+	bool ok;
+
+	if (!lp_smb3_directory_leases() || !fsp->fsp_flags.is_directory) {
+		return;
+	}
+
+	lck = get_existing_share_mode_lock(talloc_tos(), state.file_id);
+	if (lck == NULL) {
+		return;
+	}
+
+	ok = share_mode_forall_leases(lck, do_dirlease_break_to_none, &state);
+	if (!ok) {
+		DBG_WARNING("share_mode_forall_leases failed\n");
+	}
+
+	share_mode_flags_get(lck,
+			     &access_mask,
+			     &share_mode,
+			     NULL);
+	share_mode_flags_set(lck,
+			     access_mask,
+			     share_mode,
+			     state.total_lease_types,
+			     NULL);
+
+	TALLOC_FREE(lck);
+}
+
 /****************************************************************************
  This function is called on any file modification or lock request. If a file
  is level 2 oplocked then it must tell all other level 2 holders to break to
-- 
2.54.0




More information about the samba-technical mailing list