* [PATCH 1/9] smb: client: sync runtime state into ctx on reconfigure
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 2/9] smb: client: block non-reconfigurable option changes on remount rajasimandalos
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
smb3_init_fs_context() builds a fresh context with init defaults on
every call, including reconfigure (remount). Many fields displayed
by cifs_show_options() are sourced from tcon/server/ses rather than
from ctx, so the init defaults do not reflect the live mount and
cannot be used as a baseline for comparing new vs old options on
remount.
Detect FS_CONTEXT_FOR_RECONFIGURE in smb3_init_fs_context() and
duplicate the existing cifs_sb->ctx instead. Before duplicating,
sync ctx with runtime state via a new helper
smb3_sync_ctx_from_runtime() so the baseline matches what
cifs_show_options() displays. Add srv_lock / tc_lock around the
relevant runtime writers (SMB2_negotiate, SMB2_tcon,
reset_cifs_unix_caps, cifs_swn_set_server_dstaddr) so the helper
can read them without torn-read races.
Also preserve inherited multichannel/max_channels values in
smb3_handle_conflicting_options() during reconfigure when neither
option is explicitly specified, since the dup'd context already
carries them.
This is preparatory plumbing for a subsequent commit that compares
new_ctx against old_ctx in smb3_verify_reconfigure_ctx() to reject
non-reconfigurable option changes.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Meetakshi Setiya <msetiya@microsoft.com>
---
fs/smb/client/cifs_swn.h | 14 ++++-
fs/smb/client/fs_context.c | 111 +++++++++++++++++++++++++++++++++++++
fs/smb/client/smb1ops.c | 7 ++-
fs/smb/client/smb2pdu.c | 11 +++-
4 files changed, 139 insertions(+), 4 deletions(-)
diff --git a/fs/smb/client/cifs_swn.h b/fs/smb/client/cifs_swn.h
index 955d07b69450..caf0779d4d07 100644
--- a/fs/smb/client/cifs_swn.h
+++ b/fs/smb/client/cifs_swn.h
@@ -26,11 +26,21 @@ void cifs_swn_check(void);
static inline bool cifs_swn_set_server_dstaddr(struct TCP_Server_Info *server)
{
+ bool ret = false;
+
+ /*
+ * srv_lock serializes the 128-byte sockaddr_storage write against
+ * concurrent readers (e.g. cifs_show_address(), reconn_set_ipaddr_
+ * from_hostname() snapshot, smb3_sync_ctx_from_runtime()) and other
+ * writers like cifs_chan_update_iface() which already use srv_lock.
+ */
+ spin_lock(&server->srv_lock);
if (server->use_swn_dstaddr) {
server->dstaddr = server->swn_dstaddr;
- return true;
+ ret = true;
}
- return false;
+ spin_unlock(&server->srv_lock);
+ return ret;
}
static inline void cifs_swn_reset_server_dstaddr(struct TCP_Server_Info *server)
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 9addc74ce57e..576a78b6d0cb 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -926,6 +926,81 @@ static void smb3_fs_context_free(struct fs_context *fc)
smb3_cleanup_fs_context(ctx);
}
+/*
+ * Sync cifs_sb->ctx with runtime state from tcon/server/ses so the
+ * baseline matches what cifs_show_options() displays. Wide fields
+ * (dstaddr, ops/vals) are protected by the matching server/tcon lock;
+ * the remaining word-sized scalars rely on the same unsynchronized-read
+ * pattern already used by cifs_show_options().
+ */
+static int smb3_sync_ctx_from_runtime(struct cifs_sb_info *cifs_sb)
+{
+ struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
+ struct TCP_Server_Info *server = tcon->ses->server;
+ struct cifs_ses *ses = tcon->ses;
+ struct smb3_fs_context *ctx = cifs_sb->ctx;
+ const char *domain;
+ int unicode;
+
+ /*
+ * Server fields that can drift from ctx after mount:
+ * - ops/vals: dialect renegotiation during reconnect (paired,
+ * so read under srv_lock to match the writer in SMB2_negotiate)
+ * - dstaddr: SWN witness failover updates server->dstaddr; the
+ * 128-byte sockaddr_storage is not atomic, so srv_lock is
+ * required against torn reads
+ * - nosharesock: can be flipped to true post-mount by SMB2_tcon
+ * on STATUS_BAD_NETWORK_NAME with ISOLATED_TRANSPORT, so read
+ * under srv_lock to pair with that writer
+ */
+ spin_lock(&server->srv_lock);
+ ctx->ops = server->ops;
+ ctx->vals = server->vals;
+ ctx->dstaddr = server->dstaddr;
+ ctx->nosharesock = server->nosharesock;
+ spin_unlock(&server->srv_lock);
+
+ /*
+ * tcon->unix_ext can be flipped post-mount by reset_cifs_unix_caps()
+ * on SMB1 reconnect (smb1_reconnect path). Read under tc_lock to pair
+ * with that writer. tcon->posix_extensions is only ever set at
+ * mount-time pre-publish, but read it under the same lock so the
+ * derived linux_ext/no_linux_ext pair is consistent.
+ */
+ spin_lock(&tcon->tc_lock);
+ if (tcon->posix_extensions || tcon->unix_ext) {
+ ctx->linux_ext = 1;
+ ctx->no_linux_ext = 0;
+ } else {
+ ctx->linux_ext = 0;
+ ctx->no_linux_ext = 1;
+ }
+ spin_unlock(&tcon->tc_lock);
+ ctx->seal = tcon->seal;
+ ctx->persistent = tcon->use_persistent;
+ ctx->nopersistent = !tcon->use_persistent;
+ ctx->resilient = tcon->use_resilient;
+ ctx->witness = tcon->use_witness;
+
+ /*
+ * Session fields: domainName and unicode are effectively
+ * write-once (set during session setup, never freed/replaced
+ * while the session exists), so plain reads are safe.
+ */
+ domain = ses->domainName;
+ unicode = ses->unicode;
+
+ if (domain && !ctx->domainname) {
+ ctx->domainname = kstrdup(domain, GFP_KERNEL);
+ if (!ctx->domainname)
+ return -ENOMEM;
+ }
+ if (unicode >= 0)
+ ctx->unicode = unicode;
+
+ return 0;
+}
+
/*
* Compare the old and new proposed context during reconfigure
* and check if the changes are compatible.
@@ -1961,6 +2036,42 @@ int smb3_init_fs_context(struct fs_context *fc)
char *nodename = utsname()->nodename;
int i;
+ /*
+ * For reconfigure (remount), duplicate the existing mount context
+ * instead of building one from scratch with init defaults.
+ *
+ * VFS sets fc->root before calling init_fs_context for reconfigure,
+ * so we can access the existing superblock's context. We first sync
+ * cifs_sb->ctx with runtime state (tcon/server/ses) so that ctx
+ * matches what cifs_show_options() displays. Then we dup old_ctx
+ * into new_ctx. The parser will overwrite only the options
+ * explicitly passed on remount, so any difference between new_ctx
+ * and old_ctx in smb3_verify_reconfigure_ctx() represents a real,
+ * intentional change by the user.
+ */
+ if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
+ struct cifs_sb_info *cifs_sb = CIFS_SB(fc->root->d_sb);
+ int rc;
+
+ rc = smb3_sync_ctx_from_runtime(cifs_sb);
+ if (rc)
+ return rc;
+
+ ctx = kzalloc_obj(struct smb3_fs_context);
+ if (!ctx)
+ return -ENOMEM;
+
+ rc = smb3_fs_context_dup(ctx, cifs_sb->ctx);
+ if (rc) {
+ kfree(ctx);
+ return rc;
+ }
+
+ fc->fs_private = ctx;
+ fc->ops = &smb3_fs_context_ops;
+ return 0;
+ }
+
ctx = kzalloc_obj(struct smb3_fs_context);
if (unlikely(!ctx))
return -ENOMEM;
diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c
index d34b3d99f6ed..33c890e14988 100644
--- a/fs/smb/client/smb1ops.c
+++ b/fs/smb/client/smb1ops.c
@@ -36,11 +36,16 @@ void reset_cifs_unix_caps(unsigned int xid, struct cifs_tcon *tcon,
if (ctx && ctx->no_linux_ext) {
tcon->fsUnixInfo.Capability = 0;
+ spin_lock(&tcon->tc_lock);
tcon->unix_ext = 0; /* Unix Extensions disabled */
+ spin_unlock(&tcon->tc_lock);
cifs_dbg(FYI, "Linux protocol extensions disabled\n");
return;
- } else if (ctx)
+ } else if (ctx) {
+ spin_lock(&tcon->tc_lock);
tcon->unix_ext = 1; /* Unix Extensions supported */
+ spin_unlock(&tcon->tc_lock);
+ }
if (!tcon->unix_ext) {
cifs_dbg(FYI, "Unix extensions disabled so not set on reconnect\n");
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 4972cfe249f6..958ca0753774 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -1189,8 +1189,10 @@ SMB2_negotiate(const unsigned int xid,
goto neg_exit;
case SMB311_PROT_ID:
/* ops set to 3.0 by default for default so update */
+ spin_lock(&server->srv_lock);
server->ops = &smb311_operations;
server->vals = &smb311_values;
+ spin_unlock(&server->srv_lock);
break;
default:
break;
@@ -1205,12 +1207,16 @@ SMB2_negotiate(const unsigned int xid,
goto neg_exit;
case SMB21_PROT_ID:
/* ops set to 3.0 by default for default so update */
+ spin_lock(&server->srv_lock);
server->ops = &smb21_operations;
server->vals = &smb21_values;
+ spin_unlock(&server->srv_lock);
break;
case SMB311_PROT_ID:
+ spin_lock(&server->srv_lock);
server->ops = &smb311_operations;
server->vals = &smb311_values;
+ spin_unlock(&server->srv_lock);
break;
default:
break;
@@ -2226,8 +2232,11 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
if (server->ops->validate_negotiate)
rc = server->ops->validate_negotiate(xid, tcon);
if (rc == 0) /* See MS-SMB2 2.2.10 and 3.2.5.5 */
- if (tcon->share_flags & SMB2_SHAREFLAG_ISOLATED_TRANSPORT)
+ if (tcon->share_flags & SMB2_SHAREFLAG_ISOLATED_TRANSPORT) {
+ spin_lock(&server->srv_lock);
server->nosharesock = true;
+ spin_unlock(&server->srv_lock);
+ }
tcon_exit:
free_rsp_buf(resp_buftype, rsp);
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 2/9] smb: client: block non-reconfigurable option changes on remount
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
2026-07-02 11:35 ` [PATCH 1/9] smb: client: sync runtime state into ctx on reconfigure rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 3/9] smb: client: sync retrans " rajasimandalos
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
Several mount options (seal, sign, vers, ip, rdma, nosharesock,
persistent/resilient handles, etc.) require tearing down the
connection to take effect, but smb3_verify_reconfigure_ctx() does
not reject them. A remount that changes any of these silently
ignores the new value, confusing users.
Now that smb3_init_fs_context() duplicates the live context (with
runtime state synced in) on reconfigure, simple != checks in
smb3_verify_reconfigure_ctx() are sound -- a difference between
new_ctx and old_ctx represents a real, intentional change by the
user rather than a spurious mismatch against init defaults.
Add comprehensive checks for connection-tied options with clear
error messages so the user sees why the remount failed.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Meetakshi Setiya <msetiya@microsoft.com>
---
fs/smb/client/fs_context.c | 141 +++++++++++++++++++++++++++++++++++++
1 file changed, 141 insertions(+)
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 576a78b6d0cb..d5a7d4dd402b 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1070,6 +1070,147 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
cifs_errorf(fc, "can not change nbsessinit during remount\n");
return -EINVAL;
}
+ if (new_ctx->compress != old_ctx->compress) {
+ cifs_errorf(fc, "can not change compress during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->noblocksnd != old_ctx->noblocksnd) {
+ cifs_errorf(fc, "can not change noblocksend during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->noautotune != old_ctx->noautotune) {
+ cifs_errorf(fc, "can not change noautotune during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->no_sparse != old_ctx->no_sparse) {
+ cifs_errorf(fc, "can not change nosparse during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nodelete != old_ctx->nodelete) {
+ cifs_errorf(fc, "can not change nodelete during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->cruid_specified &&
+ !uid_eq(new_ctx->cred_uid, old_ctx->cred_uid)) {
+ cifs_errorf(fc, "can not change cruid during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->port != old_ctx->port) {
+ cifs_errorf(fc, "can not change port during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->min_offload != old_ctx->min_offload) {
+ cifs_errorf(fc, "can not change min_enc_offload during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->echo_interval != old_ctx->echo_interval) {
+ cifs_errorf(fc, "can not change echo_interval during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->snapshot_time != old_ctx->snapshot_time) {
+ cifs_errorf(fc, "can not change snapshot during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->max_credits != old_ctx->max_credits) {
+ cifs_errorf(fc, "can not change max_credits during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->handle_timeout != old_ctx->handle_timeout) {
+ cifs_errorf(fc, "can not change handletimeout during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->got_ip &&
+ !cifs_match_ipaddr((struct sockaddr *)&new_ctx->dstaddr,
+ (struct sockaddr *)&old_ctx->dstaddr)) {
+ cifs_errorf(fc, "can not change ip during remount\n");
+ return -EINVAL;
+ }
+ if (!cifs_match_ipaddr((struct sockaddr *)&new_ctx->srcaddr,
+ (struct sockaddr *)&old_ctx->srcaddr)) {
+ cifs_errorf(fc, "can not change srcaddr during remount\n");
+ return -EINVAL;
+ }
+ if (memcmp(new_ctx->source_rfc1001_name, old_ctx->source_rfc1001_name,
+ RFC1001_NAME_LEN)) {
+ cifs_errorf(fc, "can not change netbiosname during remount\n");
+ return -EINVAL;
+ }
+ if (memcmp(new_ctx->target_rfc1001_name, old_ctx->target_rfc1001_name,
+ RFC1001_NAME_LEN)) {
+ cifs_errorf(fc, "can not change servern during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->got_version &&
+ (new_ctx->ops != old_ctx->ops || new_ctx->vals != old_ctx->vals)) {
+ cifs_errorf(fc, "can not change vers during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->witness != old_ctx->witness) {
+ cifs_errorf(fc, "can not change witness during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->rootfs != old_ctx->rootfs) {
+ cifs_errorf(fc, "can not change rootfs during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->linux_ext != old_ctx->linux_ext ||
+ new_ctx->no_linux_ext != old_ctx->no_linux_ext) {
+ cifs_errorf(fc, "can not change unix during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nocase != old_ctx->nocase) {
+ cifs_errorf(fc, "can not change nocase during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->intr != old_ctx->intr) {
+ cifs_errorf(fc, "can not change intr during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->no_psx_acl != old_ctx->no_psx_acl) {
+ cifs_errorf(fc, "can not change acl during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->local_lease != old_ctx->local_lease) {
+ cifs_errorf(fc, "can not change locallease during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->sign != old_ctx->sign) {
+ cifs_errorf(fc, "can not change sign during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->ignore_signature != old_ctx->ignore_signature) {
+ cifs_errorf(fc, "can not change ignore_signature during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->seal != old_ctx->seal) {
+ cifs_errorf(fc, "can not change seal during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->nosharesock != old_ctx->nosharesock) {
+ cifs_errorf(fc, "can not change nosharesock during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->persistent != old_ctx->persistent ||
+ new_ctx->nopersistent != old_ctx->nopersistent) {
+ cifs_errorf(fc, "can not change persistenthandles during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->resilient != old_ctx->resilient) {
+ cifs_errorf(fc, "can not change resilienthandles during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->sockopt_tcp_nodelay != old_ctx->sockopt_tcp_nodelay) {
+ cifs_errorf(fc, "can not change tcpnodelay during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->domainauto != old_ctx->domainauto) {
+ cifs_errorf(fc, "can not change domainauto during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->rdma != old_ctx->rdma) {
+ cifs_errorf(fc, "can not change rdma during remount\n");
+ return -EINVAL;
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 3/9] smb: client: sync retrans on remount
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
2026-07-02 11:35 ` [PATCH 1/9] smb: client: sync runtime state into ctx on reconfigure rajasimandalos
2026-07-02 11:35 ` [PATCH 2/9] smb: client: block non-reconfigurable option changes on remount rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 4/9] smb: client: block cache=ro and cache=singleclient " rajasimandalos
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
The retrans mount option controls how many times the client retries
sending a request before giving up. Although remount already stored
the new value in cifs_sb->ctx, it was never propagated to
server->retrans, so the running connection kept using the old count.
Introduce smb3_sync_server_opts() to push ctx options that live on
TCP_Server_Info into the live server struct after a successful
remount. For now it handles retrans; subsequent patches will extend
it to other server-level knobs.
The previous patch in this series ("smb: client: sync runtime state
into ctx on reconfigure") duplicates the existing context on remount,
so a bare 'mount -o remount' carries the old retrans value forward and
the parser only overwrites it when the user explicitly passes
retrans=N. No special-casing for the bare-remount /proc/mounts replay
is therefore needed; in particular, an explicit retrans=0 from the
user now correctly resets the value to the compile-time default.
Writes to server->retrans are unsynchronized to match the existing
convention: every other reader of the field (smb2ops.c retry path,
cifs_show_options(), the original cifs_get_tcp_session() writer) does
not take server->srv_lock, and match_server() only reads it under
srv_lock as part of the larger match function.
Note that retrans lives on TCP_Server_Info and is therefore shared
across all mounts to the same server; reconfiguring one mount updates
the value seen by every other mount sharing the connection. This is
consistent with the existing semantics: cifs_show_options() already
reads server->retrans for /proc/mounts on every mount, and the
runtime retry path always uses the server's value rather than any
per-mount copy.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Meetakshi Setiya <msetiya@microsoft.com>
---
fs/smb/client/fs_context.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index d5a7d4dd402b..bfbea0648f6a 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1278,6 +1278,24 @@ static void smb3_sync_ses_chan_max(struct cifs_ses *ses, size_t max_channels)
spin_unlock(&ses->chan_lock);
}
+/*
+ * Synchronize server-level options that are stored on TCP_Server_Info
+ * at mount time. These fields are consulted at runtime (retry logic)
+ * so remount needs to update the live server struct in addition to
+ * cifs_sb->ctx. Note these live on TCP_Server_Info and are therefore
+ * shared across all mounts to the same server; reconfiguring one mount
+ * updates the value seen by every other mount sharing the connection,
+ * matching how cifs_show_options() and the runtime retry path already
+ * read them unsynchronized from the server struct.
+ */
+static void smb3_sync_server_opts(struct cifs_sb_info *cifs_sb)
+{
+ struct TCP_Server_Info *server = cifs_sb_master_tcon(cifs_sb)->ses->server;
+ struct smb3_fs_context *ctx = cifs_sb->ctx;
+
+ server->retrans = ctx->retrans;
+}
+
static int smb3_reconfigure(struct fs_context *fc)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
@@ -1447,6 +1465,8 @@ static int smb3_reconfigure(struct fs_context *fc)
if (!rc)
rc = dfs_cache_remount_fs(cifs_sb);
#endif
+ if (!rc)
+ smb3_sync_server_opts(cifs_sb);
return rc;
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 4/9] smb: client: block cache=ro and cache=singleclient on remount
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (2 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 3/9] smb: client: sync retrans " rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 5/9] smb: client: apply rasize " rajasimandalos
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
cache=ro and cache=singleclient are mount-time environment declarations
where the admin promises that the share is read-only or exclusively
accessed. The client bypasses server-based coherency (oplocks/leases)
and caches aggressively based on this promise.
These modes were intentionally excluded from smb3_update_mnt_flags()
when it was introduced in commit 2d39f50c2b15 ("cifs: move update of
flags into a separate function") — only cache=strict, cache=none and
cache=loose were made reconfigurable. However, remount currently
silently accepts cache=ro and cache=singleclient without actually
applying them, which is confusing.
Add explicit checks in smb3_verify_reconfigure_ctx() to reject
attempts to change these options during remount with a clear error
message.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Meetakshi Setiya <msetiya@microsoft.com>
---
fs/smb/client/fs_context.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index bfbea0648f6a..659fb0adb528 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1211,6 +1211,15 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
cifs_errorf(fc, "can not change rdma during remount\n");
return -EINVAL;
}
+ /* init default: cache_ro = false, cache_rw = false (i.e. cache=strict) */
+ if (new_ctx->cache_ro != old_ctx->cache_ro) {
+ cifs_errorf(fc, "can not change cache=ro during remount\n");
+ return -EINVAL;
+ }
+ if (new_ctx->cache_rw != old_ctx->cache_rw) {
+ cifs_errorf(fc, "can not change cache=singleclient during remount\n");
+ return -EINVAL;
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 5/9] smb: client: apply rasize on remount
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (3 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 4/9] smb: client: block cache=ro and cache=singleclient " rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 6/9] smb: client: move struct tcon_list to cifsglob.h rajasimandalos
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
rasize is accepted during remount parsing but sb->s_bdi->ra_pages is
only set in cifs_read_super() at mount time. A remount with a new
rasize value silently has no effect on the readahead window.
Update ra_pages in smb3_reconfigure() after the context has been
duplicated, using the same logic as cifs_read_super(): if rasize is
set, use it directly; otherwise fall back to 2 * rsize.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Meetakshi Setiya <msetiya@microsoft.com>
---
fs/smb/client/fs_context.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 659fb0adb528..0a72e1089ff7 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1313,7 +1313,7 @@ static int smb3_reconfigure(struct fs_context *fc)
struct dentry *root = fc->root;
struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
- unsigned int rsize = ctx->rsize, wsize = ctx->wsize;
+ unsigned int rsize = ctx->rsize, wsize = ctx->wsize, rasize = ctx->rasize;
char *new_password = NULL, *new_password2 = NULL;
bool need_recon = false;
bool need_mchan_update;
@@ -1379,9 +1379,10 @@ static int smb3_reconfigure(struct fs_context *fc)
STEAL_STRING_SENSITIVE(cifs_sb, ctx, password2);
}
- /* if rsize or wsize not passed in on remount, use previous values */
+ /* if rsize, wsize, or rasize not passed in on remount, use previous values */
ctx->rsize = rsize ? CIFS_ALIGN_RSIZE(fc, rsize) : cifs_sb->ctx->rsize;
ctx->wsize = wsize ? CIFS_ALIGN_WSIZE(fc, wsize) : cifs_sb->ctx->wsize;
+ ctx->rasize = rasize ? rasize : cifs_sb->ctx->rasize;
new_ctx = kzalloc_obj(*new_ctx);
if (!new_ctx) {
@@ -1470,6 +1471,12 @@ static int smb3_reconfigure(struct fs_context *fc)
smb3_cleanup_fs_context(old_ctx);
old_ctx = NULL;
smb3_update_mnt_flags(cifs_sb);
+
+ if (cifs_sb->ctx->rasize)
+ root->d_sb->s_bdi->ra_pages = cifs_sb->ctx->rasize / PAGE_SIZE;
+ else
+ root->d_sb->s_bdi->ra_pages = 2 * (cifs_sb->ctx->rsize / PAGE_SIZE);
+
#ifdef CONFIG_CIFS_DFS_UPCALL
if (!rc)
rc = dfs_cache_remount_fs(cifs_sb);
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6/9] smb: client: move struct tcon_list to cifsglob.h
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (4 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 5/9] smb: client: apply rasize " rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 7/9] smb: client: allow nolease option to be reconfigured on remount rajasimandalos
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
struct tcon_list is a small list-node wrapper that pairs a list_head
with a cifs_tcon pointer, used to safely iterate tcons under a
superblock outside tlink_tree_lock.
It is currently file-static in misc.c, used only by
cifs_close_all_deferred_files_sb(). Move it next to the similar
struct file_list in cifsglob.h so that other _sb() iteration helpers
can reuse it instead of redefining the same shape locally.
No functional change.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Reviewed-by: Bharath SM <bharathsm@microsoft.com>
---
fs/smb/client/cifsglob.h | 5 +++++
fs/smb/client/misc.c | 5 -----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index a462c1590a9e..38e27ba6d8f6 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1807,6 +1807,11 @@ struct file_list {
struct cifsFileInfo *cfile;
};
+struct tcon_list {
+ struct list_head entry;
+ struct cifs_tcon *tcon;
+};
+
struct cifs_mount_ctx {
struct cifs_sb_info *cifs_sb;
struct smb3_fs_context *fs_ctx;
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 0c54b9b79a2c..7ef135dc8268 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -28,11 +28,6 @@
#include "fs_context.h"
#include "cached_dir.h"
-struct tcon_list {
- struct list_head entry;
- struct cifs_tcon *tcon;
-};
-
/* The xid serves as a useful identifier for each incoming vfs request,
in a similar way to the mid which is useful to track each sent smb,
and CurrentXid can also provide a running counter (although it
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 7/9] smb: client: allow nolease option to be reconfigured on remount
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (5 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 6/9] smb: client: move struct tcon_list to cifsglob.h rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 8/9] smb: client: review fixes for remount ctx/retrans sync (patches 1-4) rajasimandalos
2026-07-02 11:35 ` [PATCH 9/9] smb: client: review fixes for nolease remount (patch 7) rajasimandalos
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
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@microsoft.com>
Reviewed-by: Bharath SM <bharathsm@microsoft.com>
---
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
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 8/9] smb: client: review fixes for remount ctx/retrans sync (patches 1-4)
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (6 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 7/9] smb: client: allow nolease option to be reconfigured on remount rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
2026-07-02 11:35 ` [PATCH 9/9] smb: client: review fixes for nolease remount (patch 7) rajasimandalos
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
Fold the review feedback for the first four remount patches into one
change. Each fix, in two lines:
- ctx sync (patch 1): sync runtime state into a private reconfigure
context, never the shared cifs_sb->ctx, to avoid racing concurrent
fspick/remount and /proc/mounts readers.
- ctx sync (patch 1): compare the new context against a runtime-synced
private base_ctx (dup + sync), freed before commit, instead of the raw
cifs_sb->ctx.
- ctx sync (patch 1): drop the fabricated "nopersistent = !use_persistent"
derivation; nopersistent is an independent user option and must keep the
dup'd value.
- block options (patch 2): mention "(signloosely)" in the ignore_signature
remount rejection so the message names the user-facing option.
- retrans (patch 3): read/write server->retrans with READ_ONCE/WRITE_ONCE
to pair the new remount writer with the lockless retry-path reader.
- retrans (patch 3): seed the baseline from READ_ONCE(server->retrans) so a
bare remount carries the live value forward instead of clobbering it.
- block cache (patch 4): reword the cache-mode comment to state that
cache=ro sets cache_ro and cache=singleclient sets cache_rw.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
---
fs/smb/client/cifsfs.c | 6 +-
fs/smb/client/connect.c | 4 +-
fs/smb/client/fs_context.c | 122 +++++++++++++++++++++++++++++--------
fs/smb/client/smb2ops.c | 2 +-
4 files changed, 104 insertions(+), 30 deletions(-)
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 6b97f7a91235..e99d427fb2c5 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -612,6 +612,7 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
struct sockaddr *srcaddr;
unsigned int sbflags;
+ unsigned int retrans;
srcaddr = (struct sockaddr *)&tcon->ses->server->srcaddr;
@@ -765,8 +766,9 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
seq_printf(s, ",rasize=%u", cifs_sb->ctx->rasize);
if (tcon->ses->server->min_offload)
seq_printf(s, ",esize=%u", tcon->ses->server->min_offload);
- if (tcon->ses->server->retrans)
- seq_printf(s, ",retrans=%u", tcon->ses->server->retrans);
+ retrans = READ_ONCE(tcon->ses->server->retrans);
+ if (retrans)
+ seq_printf(s, ",retrans=%u", retrans);
seq_printf(s, ",echo_interval=%lu",
tcon->ses->server->echo_interval / HZ);
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index 104658e318b6..14dd472c2b0f 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -1657,7 +1657,7 @@ static int match_server(struct TCP_Server_Info *server,
if (server->min_offload != ctx->min_offload)
return 0;
- if (server->retrans != ctx->retrans)
+ if (READ_ONCE(server->retrans) != ctx->retrans)
return 0;
return 1;
@@ -1872,7 +1872,7 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
*/
__module_get(THIS_MODULE);
tcp_ses->min_offload = ctx->min_offload;
- tcp_ses->retrans = ctx->retrans;
+ WRITE_ONCE(tcp_ses->retrans, ctx->retrans);
/*
* at this point we are the only ones with the pointer
* to the struct since the kernel thread not created yet
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 53f986d8e9e0..89bf6f4604b3 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -928,18 +928,22 @@ static void smb3_fs_context_free(struct fs_context *fc)
}
/*
- * Sync cifs_sb->ctx with runtime state from tcon/server/ses so the
- * baseline matches what cifs_show_options() displays. Wide fields
- * (dstaddr, ops/vals) are protected by the matching server/tcon lock;
- * the remaining word-sized scalars rely on the same unsynchronized-read
- * pattern already used by cifs_show_options().
+ * Sync a private reconfigure context with runtime state from
+ * tcon/server/ses so the baseline matches what cifs_show_options()
+ * displays. @ctx must be the caller's freshly-duplicated context, not
+ * the live cifs_sb->ctx: this function writes into @ctx, and mutating
+ * the shared cifs_sb->ctx here would race with concurrent fspick/remount
+ * and /proc/mounts readers. Wide fields (dstaddr, ops/vals) are read
+ * under the matching server/tcon lock; the remaining word-sized scalars
+ * rely on the same unsynchronized-read pattern already used by
+ * cifs_show_options().
*/
-static int smb3_sync_ctx_from_runtime(struct cifs_sb_info *cifs_sb)
+static int smb3_sync_ctx_from_runtime(struct cifs_sb_info *cifs_sb,
+ struct smb3_fs_context *ctx)
{
struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
struct TCP_Server_Info *server = tcon->ses->server;
struct cifs_ses *ses = tcon->ses;
- struct smb3_fs_context *ctx = cifs_sb->ctx;
const char *domain;
int unicode;
@@ -961,6 +965,16 @@ static int smb3_sync_ctx_from_runtime(struct cifs_sb_info *cifs_sb)
ctx->nosharesock = server->nosharesock;
spin_unlock(&server->srv_lock);
+ /*
+ * retrans lives on the shared TCP_Server_Info and can be updated by
+ * a remount of any mount sharing the connection. Sync it from the
+ * live server so a bare remount carries the current value forward
+ * instead of writing a stale per-mount copy back in
+ * smb3_sync_server_opts(). Read with READ_ONCE to pair with the
+ * lockless WRITE_ONCE writers and the runtime retry-path reader.
+ */
+ ctx->retrans = READ_ONCE(server->retrans);
+
/*
* tcon->unix_ext can be flipped post-mount by reset_cifs_unix_caps()
* on SMB1 reconnect (smb1_reconnect path). Read under tc_lock to pair
@@ -978,15 +992,27 @@ static int smb3_sync_ctx_from_runtime(struct cifs_sb_info *cifs_sb)
}
spin_unlock(&tcon->tc_lock);
ctx->seal = tcon->seal;
+ /*
+ * persistent reflects the live tcon->use_persistent so the baseline
+ * matches what cifs_show_options() reports (persistenthandles). Do
+ * NOT derive nopersistent from use_persistent: persistent and
+ * nopersistent are independent user options and use_persistent is a
+ * derived runtime result. nopersistent is not reported by
+ * cifs_show_options(), so it must keep the user's original value
+ * (carried by the dup); fabricating it here would, after the
+ * reconfigure commits the ctx, wrongly suppress persistent-handle
+ * auto-enable on continuous-availability shares at reconnect.
+ */
ctx->persistent = tcon->use_persistent;
- ctx->nopersistent = !tcon->use_persistent;
ctx->resilient = tcon->use_resilient;
ctx->witness = tcon->use_witness;
/*
* Session fields: domainName and unicode are effectively
* write-once (set during session setup, never freed/replaced
- * while the session exists), so plain reads are safe.
+ * while the session exists), so plain reads are safe. @ctx is
+ * the caller's private copy, so filling in a missing domainname
+ * here cannot leak or race.
*/
domain = ses->domainName;
unicode = ses->unicode;
@@ -1180,7 +1206,7 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
return -EINVAL;
}
if (new_ctx->ignore_signature != old_ctx->ignore_signature) {
- cifs_errorf(fc, "can not change ignore_signature during remount\n");
+ cifs_errorf(fc, "can not change ignore_signature (signloosely) during remount\n");
return -EINVAL;
}
if (new_ctx->seal != old_ctx->seal) {
@@ -1212,7 +1238,11 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
cifs_errorf(fc, "can not change rdma during remount\n");
return -EINVAL;
}
- /* init default: cache_ro = false, cache_rw = false (i.e. cache=strict) */
+ /*
+ * cache=ro sets cache_ro and cache=singleclient sets cache_rw; the
+ * other cache modes (strict/loose/none) leave both clear and are
+ * handled separately in smb3_update_mnt_flags().
+ */
if (new_ctx->cache_ro != old_ctx->cache_ro) {
cifs_errorf(fc, "can not change cache=ro during remount\n");
return -EINVAL;
@@ -1340,7 +1370,7 @@ static void smb3_sync_server_opts(struct cifs_sb_info *cifs_sb)
struct TCP_Server_Info *server = cifs_sb_master_tcon(cifs_sb)->ses->server;
struct smb3_fs_context *ctx = cifs_sb->ctx;
- server->retrans = ctx->retrans;
+ WRITE_ONCE(server->retrans, ctx->retrans);
}
static int smb3_reconfigure(struct fs_context *fc)
@@ -1348,6 +1378,7 @@ static int smb3_reconfigure(struct fs_context *fc)
struct smb3_fs_context *ctx = smb3_fc2context(fc);
struct smb3_fs_context *new_ctx = NULL;
struct smb3_fs_context *old_ctx = NULL;
+ struct smb3_fs_context *base_ctx = NULL;
struct dentry *root = fc->root;
struct cifs_sb_info *cifs_sb = CIFS_SB(root->d_sb);
struct cifs_ses *ses = cifs_sb_master_tcon(cifs_sb)->ses;
@@ -1360,9 +1391,37 @@ static int smb3_reconfigure(struct fs_context *fc)
if (ses->expired_pwd)
need_recon = true;
- rc = smb3_verify_reconfigure_ctx(fc, ctx, cifs_sb->ctx, need_recon);
+ /*
+ * Compare the new context against a runtime-synced baseline, not the
+ * raw cifs_sb->ctx. smb3_init_fs_context() syncs the new context
+ * (fc->fs_private) from runtime state, so the comparison baseline must
+ * be synced the same way; otherwise a field that drifted at runtime
+ * (e.g. dstaddr after witness failover, or use_persistent auto-enabled
+ * on a continuous-availability share) would look like a user-requested
+ * change and be rejected on an otherwise bare remount. Sync a private
+ * copy rather than cifs_sb->ctx: s_umount serializes reconfigures, but
+ * not the fspick/fsconfig init path, which dup()s cifs_sb->ctx in
+ * smb3_init_fs_context() before reconfigure_super() takes s_umount. An
+ * in-place sync would therefore race that lockless dup (e.g. a torn
+ * read of the 128-byte dstaddr).
+ */
+ base_ctx = kzalloc_obj(*base_ctx);
+ if (!base_ctx)
+ return -ENOMEM;
+ rc = smb3_fs_context_dup(base_ctx, cifs_sb->ctx);
if (rc)
- return rc;
+ goto free_base_ctx;
+ rc = smb3_sync_ctx_from_runtime(cifs_sb, base_ctx);
+ if (rc)
+ goto cleanup_base_ctx;
+
+ rc = smb3_verify_reconfigure_ctx(fc, ctx, base_ctx, need_recon);
+ if (rc)
+ goto cleanup_base_ctx;
+
+ smb3_cleanup_fs_context_contents(base_ctx);
+ kfree(base_ctx);
+ base_ctx = NULL;
rc = smb3_handle_conflicting_options(fc);
if (rc)
@@ -1537,6 +1596,13 @@ static int smb3_reconfigure(struct fs_context *fc)
free_old_ctx:
kfree(old_ctx);
+ return rc;
+
+cleanup_base_ctx:
+ smb3_cleanup_fs_context_contents(base_ctx);
+free_base_ctx:
+ kfree(base_ctx);
+
return rc;
}
@@ -2258,28 +2324,34 @@ int smb3_init_fs_context(struct fs_context *fc)
* instead of building one from scratch with init defaults.
*
* VFS sets fc->root before calling init_fs_context for reconfigure,
- * so we can access the existing superblock's context. We first sync
- * cifs_sb->ctx with runtime state (tcon/server/ses) so that ctx
- * matches what cifs_show_options() displays. Then we dup old_ctx
- * into new_ctx. The parser will overwrite only the options
- * explicitly passed on remount, so any difference between new_ctx
- * and old_ctx in smb3_verify_reconfigure_ctx() represents a real,
- * intentional change by the user.
+ * so we can access the existing superblock's context. We dup the
+ * live cifs_sb->ctx into a private new_ctx, then sync new_ctx with
+ * runtime state (tcon/server/ses) so it matches what
+ * cifs_show_options() displays. Syncing into the private copy (not
+ * the shared cifs_sb->ctx) avoids racing with concurrent
+ * fspick/remount and /proc/mounts readers. The parser will overwrite
+ * only the options explicitly passed on remount, so any difference
+ * between new_ctx and old_ctx in smb3_verify_reconfigure_ctx()
+ * represents a real, intentional change by the user.
*/
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
struct cifs_sb_info *cifs_sb = CIFS_SB(fc->root->d_sb);
int rc;
- rc = smb3_sync_ctx_from_runtime(cifs_sb);
- if (rc)
- return rc;
-
ctx = kzalloc_obj(struct smb3_fs_context);
if (!ctx)
return -ENOMEM;
rc = smb3_fs_context_dup(ctx, cifs_sb->ctx);
if (rc) {
+ smb3_cleanup_fs_context_contents(ctx);
+ kfree(ctx);
+ return rc;
+ }
+
+ rc = smb3_sync_ctx_from_runtime(cifs_sb, ctx);
+ if (rc) {
+ smb3_cleanup_fs_context_contents(ctx);
kfree(ctx);
return rc;
}
diff --git a/fs/smb/client/smb2ops.c b/fs/smb/client/smb2ops.c
index a8f8feeeccb5..b30c16b0d157 100644
--- a/fs/smb/client/smb2ops.c
+++ b/fs/smb/client/smb2ops.c
@@ -2806,7 +2806,7 @@ bool smb2_should_replay(struct cifs_tcon *tcon,
if (!pretries || !pcur_sleep)
return false;
- if (tcon->retry || (*pretries)++ < tcon->ses->server->retrans) {
+ if (tcon->retry || (*pretries)++ < READ_ONCE(tcon->ses->server->retrans)) {
/* Update sleep time for exponential backoff */
if (!(*pcur_sleep))
(*pcur_sleep) = 1;
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 9/9] smb: client: review fixes for nolease remount (patch 7)
2026-07-02 11:35 [PATCH RESEND 0/9] smb: client: remount reconfigure option fixes rajasimandalos
` (7 preceding siblings ...)
2026-07-02 11:35 ` [PATCH 8/9] smb: client: review fixes for remount ctx/retrans sync (patches 1-4) rajasimandalos
@ 2026-07-02 11:35 ` rajasimandalos
8 siblings, 0 replies; 10+ messages in thread
From: rajasimandalos @ 2026-07-02 11:35 UTC (permalink / raw)
To: linux-cifs; +Cc: bharathsm, msetiya, smfrench, sfrench, stfrench, Rajasi Mandal
From: Rajasi Mandal <rajasimandal@microsoft.com>
Fold the review feedback for the nolease reconfigure patch into one
change. Each fix, in two lines:
- nolease transition: track the lease -> nolease transition
(became_nolease) and only run the expensive cache eviction when nolease
is actually switched on, not on every bare remount.
- nolease locking: set tcon->no_lease under tc_lock to pair with
match_tcon(), which reads tcon->no_lease with tc_lock held.
- cached dir leak: add a close_handles arg to invalidate_all_cached_dirs()
so a live remount keeps is_open and lets the laundromat send SMB2_close
instead of leaking the server handle/lease.
- callers: cifs_debug.c, file.c and smb2pdu.c pass close_handles=false to
preserve their existing teardown behaviour.
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
---
fs/smb/client/cached_dir.c | 32 ++++++++++++++++++++++----------
fs/smb/client/cached_dir.h | 3 ++-
fs/smb/client/cifs_debug.c | 2 +-
fs/smb/client/file.c | 2 +-
fs/smb/client/fs_context.c | 20 +++++++++++++++++---
fs/smb/client/smb2pdu.c | 2 +-
6 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/fs/smb/client/cached_dir.c b/fs/smb/client/cached_dir.c
index 58d68a8f4d41..420a598f5963 100644
--- a/fs/smb/client/cached_dir.c
+++ b/fs/smb/client/cached_dir.c
@@ -598,10 +598,17 @@ void close_all_cached_dirs(struct cifs_sb_info *cifs_sb)
}
/*
- * Invalidate all cached dirs when a TCON has been reset
- * due to a session loss.
+ * Invalidate all cached dirs on a TCON, moving them to the dying list
+ * for the laundromat to clean up.
+ *
+ * @close_handles: if true, the connection is still live (e.g. remount),
+ * so leave cfid->is_open set and let smb2_close_cached_fid() send
+ * SMB2_close to release the server-side directory handle and lease. If
+ * false (the session-loss teardown case), mark the cfids closed so the
+ * doomed SMB2_close is skipped -- the server has already dropped them.
*/
-void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync)
+void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync,
+ bool close_handles)
{
struct cached_fids *cfids = tcon->cfids;
struct cached_fid *cfid, *q;
@@ -610,15 +617,16 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync)
return;
/*
- * Mark all the cfids as closed, and move them to the cfids->dying list.
- * They'll be cleaned up by laundromat. Take a reference to each cfid
- * during this process.
+ * Move all the cfids to the cfids->dying list. They'll be cleaned
+ * up by laundromat. Take a reference to each cfid during this
+ * process.
*/
spin_lock(&cfids->cfid_list_lock);
list_for_each_entry_safe(cfid, q, &cfids->entries, entry) {
list_move(&cfid->entry, &cfids->dying);
cfids->num_entries--;
- cfid->is_open = false;
+ if (!close_handles)
+ cfid->is_open = false;
cfid->on_list = false;
if (cfid->has_lease) {
/*
@@ -640,10 +648,14 @@ void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync)
/*
* 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
+ * then close their cached dirs outside the spinlock since that can
+ * sleep. Holding a tc_count reference prevents the tcon from being
* freed by tlink_expire_delayed() between dropping the spinlock and
* the call.
+ *
+ * Called on remount while the connection is live (e.g. switching to
+ * nolease), so pass close_handles=true to actually release the
+ * server-side directory handles and their leases.
*/
void invalidate_all_cached_dirs_sb(struct cifs_sb_info *cifs_sb)
{
@@ -674,7 +686,7 @@ void invalidate_all_cached_dirs_sb(struct cifs_sb_info *cifs_sb)
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);
+ invalidate_all_cached_dirs(tmp_list->tcon, true, true);
list_del(&tmp_list->entry);
cifs_put_tcon(tmp_list->tcon, netfs_trace_tcon_ref_put_cached_inval_sb);
kfree(tmp_list);
diff --git a/fs/smb/client/cached_dir.h b/fs/smb/client/cached_dir.h
index 606ba2a0b64f..3b86339ca884 100644
--- a/fs/smb/client/cached_dir.h
+++ b/fs/smb/client/cached_dir.h
@@ -91,7 +91,8 @@ 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);
+void invalidate_all_cached_dirs(struct cifs_tcon *tcon, bool sync,
+ bool close_handles);
bool cached_dir_lease_break(struct cifs_tcon *tcon, __u8 lease_key[16]);
#endif /* _CACHED_DIR_H */
diff --git a/fs/smb/client/cifs_debug.c b/fs/smb/client/cifs_debug.c
index 4ed4f55a0bb7..28b0d433810f 100644
--- a/fs/smb/client/cifs_debug.c
+++ b/fs/smb/client/cifs_debug.c
@@ -383,7 +383,7 @@ static ssize_t cifs_debug_dirs_proc_write(struct file *file, const char __user *
if (cifs_ses_exiting(ses))
continue;
list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
- invalidate_all_cached_dirs(tcon, false);
+ invalidate_all_cached_dirs(tcon, false, false);
}
}
spin_unlock(&cifs_tcp_ses_lock);
diff --git a/fs/smb/client/file.c b/fs/smb/client/file.c
index 58430ba51b10..c06117b1975e 100644
--- a/fs/smb/client/file.c
+++ b/fs/smb/client/file.c
@@ -393,7 +393,7 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
}
spin_unlock(&tcon->open_file_lock);
- invalidate_all_cached_dirs(tcon, true);
+ invalidate_all_cached_dirs(tcon, true, false);
spin_lock(&tcon->tc_lock);
if (tcon->status == TID_IN_FILES_INVALIDATE)
tcon->status = TID_NEED_TCON;
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 89bf6f4604b3..70fa869b9b6d 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -1333,6 +1333,7 @@ static void smb3_sync_tcon_opts(struct cifs_sb_info *cifs_sb,
struct tcon_link *tlink;
struct cifs_tcon *tcon;
struct rb_node *node;
+ bool became_nolease = false;
spin_lock(&cifs_sb->tlink_tree_lock);
for (node = rb_first(&cifs_sb->tlink_tree); node; node = rb_next(node)) {
@@ -1340,16 +1341,29 @@ static void smb3_sync_tcon_opts(struct cifs_sb_info *cifs_sb,
tcon = tlink_tcon(tlink);
if (IS_ERR(tcon))
continue;
+ /*
+ * Update under tc_lock to pair with match_tcon(), which reads
+ * tcon->no_lease with tc_lock held. Track whether this is the
+ * lease -> nolease transition so the expensive cleanup below
+ * only runs when nolease is actually being switched on.
+ */
+ spin_lock(&tcon->tc_lock);
+ if (ctx->no_lease && !tcon->no_lease)
+ became_nolease = true;
tcon->no_lease = ctx->no_lease;
+ spin_unlock(&tcon->tc_lock);
}
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
+ * Only when switching to nolease must we evict lease-bearing cached
+ * state (deferred handles and cached dir fids). Skipping this when
+ * nolease was already set avoids dropping caches on every bare
+ * remount. 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) {
+ if (became_nolease) {
cifs_close_all_deferred_files_sb(cifs_sb);
invalidate_all_cached_dirs_sb(cifs_sb);
}
diff --git a/fs/smb/client/smb2pdu.c b/fs/smb/client/smb2pdu.c
index 958ca0753774..e68f96df8ee3 100644
--- a/fs/smb/client/smb2pdu.c
+++ b/fs/smb/client/smb2pdu.c
@@ -2278,7 +2278,7 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon)
}
spin_unlock(&ses->chan_lock);
- invalidate_all_cached_dirs(tcon, true);
+ invalidate_all_cached_dirs(tcon, true, false);
rc = smb2_plain_req_init(SMB2_TREE_DISCONNECT, tcon, server,
(void **) &req,
--
2.43.0
^ permalink raw reply related [flat|nested] 10+ messages in thread