[PATCH v4 7/7] smb: client: allow nolease option to be reconfigured on remount

From: rajasimandalos

Date: Tue Jun 16 2026 - 01:57:32 EST


From: Rajasi Mandal <rajasimandal@xxxxxxxxxxxxx>

Changing nolease via remount is silently accepted but has no effect:
the value is not propagated to the live tcons, and stale lease-bearing
state from before the switch keeps using the old behavior.

Make nolease take effect on remount:

- Add smb3_sync_tcon_opts() which walks the tlink_tree under
tlink_tree_lock and propagates ctx->no_lease to tcon->no_lease for
every tcon under this superblock so future opens honor the new
setting. Call it from smb3_reconfigure() after the context has
been updated.

- On switch to nolease, drop deferred file handles via
cifs_close_all_deferred_files_sb() (each holds an active lease).

- On switch to nolease, evict cached directory fids via the new
invalidate_all_cached_dirs_sb() helper (each holds a directory
lease).

invalidate_all_cached_dirs_sb() mirrors cifs_close_all_deferred_files_sb():
take tc_count refs on every tcon under tlink_tree_lock, drop the lock,
then call invalidate_all_cached_dirs() per tcon (it can sleep). Two
new tcon_ref trace tags are added for the audit trail.

Move tcon->no_lease out of the bitfield word into a plain bool. As a
runtime writer it now shares storage with need_reconnect/need_reopen_files,
which the reconnect/reopen paths update locklessly; a bitfield write is a
read-modify-write of the whole word and could clobber a concurrent
need_reconnect update. Its own memory location removes that race.

Existing open handles keep their leases until the server breaks them
or userspace closes the file -- nolease only governs new opens.

Signed-off-by: Rajasi Mandal <rajasimandal@xxxxxxxxxxxxx>
Reviewed-by: Bharath SM <bharathsm@xxxxxxxxxxxxx>
---
fs/smb/client/cached_dir.c | 44 ++++++++++++++++++++++++++++++++++++++
fs/smb/client/cached_dir.h | 1 +
fs/smb/client/cifsglob.h | 2 +-
fs/smb/client/fs_context.c | 40 ++++++++++++++++++++++++++++++++++
fs/smb/client/trace.h | 2 ++
5 files changed, 88 insertions(+), 1 deletion(-)

diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 88d5e9a32f28..58d68a8f4d41 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -637,6 +637,50 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync)
flush_delayed_work(&cfids->laundromat_work);
}

+/*
+ * Invalidate cached directory entries across all tcons under a
+ * superblock. Collect references on each tcon under tlink_tree_lock,
+ * then call invalidate_all_cached_dirs() outside the spinlock since it
+ * can sleep. Holding a tc_count reference prevents the tcon from being
+ * freed by tlink_expire_delayed() between dropping the spinlock and
+ * the call.
+ */
+void invalidate_all_cached_dirs_sb(struct cifs_sb_info *cifs_sb)
+{
+ struct rb_root *root = &cifs_sb->tlink_tree;
+ struct rb_node *node;
+ struct cifs_tcon *tcon;
+ struct tcon_link *tlink;
+ struct tcon_list *tmp_list, *q;
+ LIST_HEAD(tcon_head);
+
+ spin_lock(&cifs_sb->tlink_tree_lock);
+ for (node = rb_first(root); node; node = rb_next(node)) {
+ tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+ tcon = tlink_tcon(tlink);
+ if (IS_ERR(tcon))
+ continue;
+ tmp_list = kmalloc_obj(struct tcon_list, GFP_ATOMIC);
+ if (!tmp_list)
+ break;
+ tmp_list->tcon = tcon;
+ spin_lock(&tcon->tc_lock);
+ ++tcon->tc_count;
+ trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
+ netfs_trace_tcon_ref_get_cached_inval_sb);
+ spin_unlock(&tcon->tc_lock);
+ list_add_tail(&tmp_list->entry, &tcon_head);
+ }
+ spin_unlock(&cifs_sb->tlink_tree_lock);
+
+ list_for_each_entry_safe(tmp_list, q, &tcon_head, entry) {
+ invalidate_all_cached_dirs(tmp_list->tcon, true);
+ list_del(&tmp_list->entry);
+ cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_cached_inval_sb);
+ kfree(tmp_list);
+ }
+}
+
static void
cached_dir_offload_close(struct work_struct *work)
{
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index fc756836da95..606ba2a0b64f 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -90,6 +90,7 @@ void close_cached_dir(struct cached_fid *cfid);
void drop_cached_dir_by_name(const unsigned int xid, struct cifs_tcon *tcon,
const char *name, struct cifs_sb_info *cifs_sb);
void close_all_cached_dirs(struct cifs_sb_info *cifs_sb);
+void invalidate_all_cached_dirs_sb(struct cifs_sb_info *cifs_sb);
void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync);
bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);

diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 38e27ba6d8f6..713ca152b0ea 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1262,9 +1262,9 @@ struct cifs_tcon {
bool need_reopen_files:1; /* need to reopen tcon file handles */
bool use_resilient:1; /* use resilient instead of durable handles */
bool use_persistent:1; /* use persistent instead of durable handles */
- bool no_lease:1; /* Do not request leases on files or directories */
bool use_witness:1; /* use witness protocol */
bool dummy:1; /* dummy tcon used for reconnecting channels */
+ bool no_lease; /* Do not request leases on files or directories */
__le32 capabilities;
__u32 share_flags;
__u32 maximal_access;
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 0a72e1089ff7..53f986d8e9e0 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -35,6 +35,7 @@
#include "nterr.h"
#include "rfc1002pdu.h"
#include "fs_context.h"
+#include "cached_dir.h"

DEFINE_MUTEX(cifs_mount_mutex);

@@ -1287,6 +1288,43 @@ static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels)
spin_unlock(&ses->chan_lock);
}

+/*
+ * Propagate ctx->no_lease to every tcon under this superblock so future
+ * opens honor the new setting after a remount. When switching to nolease,
+ * also drop deferred file handles and invalidate cached directory fids,
+ * since each holds an active lease from before the switch.
+ *
+ * Existing open handles keep their leases until the server breaks them
+ * or userspace closes the file -- nolease only governs new opens.
+ */
+static void smb3_sync_tcon_opts(struct cifs_sb_info *cifs_sb,
+ struct smb3_fs_context *ctx)
+{
+ struct tcon_link *tlink;
+ struct cifs_tcon *tcon;
+ struct rb_node *node;
+
+ spin_lock(&cifs_sb->tlink_tree_lock);
+ for (node = rb_first(&cifs_sb->tlink_tree); node; node = rb_next(node)) {
+ tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+ tcon = tlink_tcon(tlink);
+ if (IS_ERR(tcon))
+ continue;
+ tcon->no_lease = ctx->no_lease;
+ }
+ spin_unlock(&cifs_sb->tlink_tree_lock);
+
+ /*
+ * Both _sb() helpers iterate all tcons internally and handle
+ * their own locking. They can sleep, so they must be called
+ * outside tlink_tree_lock.
+ */
+ if (ctx->no_lease) {
+ cifs_close_all_deferred_files_sb(cifs_sb);
+ invalidate_all_cached_dirs_sb(cifs_sb);
+ }
+}
+
/*
* Synchronize server-level options that are stored on TCP_Server_Info
* at mount time. These fields are consulted at runtime (retry logic)
@@ -1483,6 +1521,8 @@ static int smb3_reconfigure(struct fs_context *fc)
#endif
if (!rc)
smb3_sync_server_opts(cifs_sb);
+ if (!rc)
+ smb3_sync_tcon_opts(cifs_sb, cifs_sb->ctx);

return rc;

diff --git a/fs/smb/client/trace.h b/fs/smb/client/trace.h
index 5b21ad3c15fb..1e61a36759ed 100644
--- a/fs/smb/client/trace.h
+++ b/fs/smb/client/trace.h
@@ -173,6 +173,7 @@
EM(netfs_trace_tcon_ref_free_ipc, "FRE Ipc ") \
EM(netfs_trace_tcon_ref_free_ipc_fail, "FRE Ipc-F ") \
EM(netfs_trace_tcon_ref_free_reconnect_server, "FRE Reconn") \
+ EM(netfs_trace_tcon_ref_get_cached_inval_sb, "GET Ch-IvS") \
EM(netfs_trace_tcon_ref_get_cached_laundromat, "GET Ch-Lau") \
EM(netfs_trace_tcon_ref_get_cached_lease_break, "GET Ch-Lea") \
EM(netfs_trace_tcon_ref_get_cancelled_close, "GET Cn-Cls") \
@@ -186,6 +187,7 @@
EM(netfs_trace_tcon_ref_new_ipc, "NEW Ipc ") \
EM(netfs_trace_tcon_ref_new_reconnect_server, "NEW Reconn") \
EM(netfs_trace_tcon_ref_put_cached_close, "PUT Ch-Cls") \
+ EM(netfs_trace_tcon_ref_put_cached_inval_sb, "PUT Ch-IvS") \
EM(netfs_trace_tcon_ref_put_cancelled_close, "PUT Cn-Cls") \
EM(netfs_trace_tcon_ref_put_cancelled_close_fid, "PUT Cn-Fid") \
EM(netfs_trace_tcon_ref_put_cancelled_mid, "PUT Cn-Mid") \
--
2.43.0