cifs: client: allow changing multichannel mount options on remount

Previously, the client did not update a session's channel state when
multichannel or max_channels mount options were changed via remount.
This led to inconsistent behavior and prevented enabling or disabling
multichannel support without a full unmount/remount cycle.

Enable dynamic reconfiguration of multichannel and max_channels during
remount by:
- Introducing smb3_sync_ses_chan_max(), a centralized function for
  channel updates which synchronizes the session's channels with the
  updated configuration.
- Replacing cifs_disable_secondary_channels() with
  cifs_decrease_secondary_channels(), which accepts a disable_mchan
  flag to support multichannel disable when the server stops supporting
  multichannel.
- Updating remount logic to detect changes in multichannel or
  max_channels and trigger appropriate session/channel updates.

Current limitation:
- The query_interfaces worker runs even when max_channels=1 so that
  multichannel can be enabled later via remount without requiring an
  unmount. This is a temporary approach and may be refined in the
  future.

Users can safely modify multichannel and max_channels on an existing
mount. The client will correctly adjust the session's channel state to
match the new configuration, preserving durability where possible and
avoiding unnecessary disconnects.

Reviewed-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Rajasi Mandal <rajasimandal@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Rajasi Mandal
2025-12-05 20:11:51 +00:00
committed by Steve French
parent 32a6086809
commit ef529f655a
5 changed files with 128 additions and 26 deletions

View File

@@ -635,6 +635,8 @@ int cifs_alloc_hash(const char *name, struct shash_desc **sdesc);
void cifs_free_hash(struct shash_desc **sdesc);
int cifs_try_adding_channels(struct cifs_ses *ses);
int smb3_update_ses_channels(struct cifs_ses *ses, struct TCP_Server_Info *server,
bool from_reconnect, bool disable_mchan);
bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface);
void cifs_ses_mark_for_reconnect(struct cifs_ses *ses);
@@ -660,7 +662,7 @@ bool
cifs_chan_is_iface_active(struct cifs_ses *ses,
struct TCP_Server_Info *server);
void
cifs_disable_secondary_channels(struct cifs_ses *ses);
cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan);
void
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
int

View File

@@ -3926,7 +3926,9 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
ctx->prepath = NULL;
out:
cifs_try_adding_channels(mnt_ctx.ses);
smb3_update_ses_channels(mnt_ctx.ses, mnt_ctx.server,
false /* from_reconnect */,
false /* disable_mchan */);
rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon);
if (rc)
goto error;

View File

@@ -758,6 +758,7 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
void *data);
static int smb3_get_tree(struct fs_context *fc);
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels);
static int smb3_reconfigure(struct fs_context *fc);
static const struct fs_context_operations smb3_fs_context_ops = {
@@ -1055,6 +1056,22 @@ int smb3_sync_session_ctx_passwords(struct cifs_sb_info *cifs_sb, struct cifs_se
return 0;
}
/*
* smb3_sync_ses_chan_max - Synchronize the session's maximum channel count
* @ses: pointer to the old CIFS session structure
* @max_channels: new maximum number of channels to allow
*
* Updates the session's chan_max field to the new value, protecting the update
* with the session's channel lock. This should be called whenever the maximum
* allowed channels for a session changes (e.g., after a remount or reconfigure).
*/
static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels)
{
spin_lock(&ses->chan_lock);
ses->chan_max = max_channels;
spin_unlock(&ses->chan_lock);
}
static int smb3_reconfigure(struct fs_context *fc)
{
struct smb3_fs_context *ctx = smb3_fc2context(fc);
@@ -1137,7 +1154,39 @@ static int smb3_reconfigure(struct fs_context *fc)
ses->password2 = new_password2;
}
mutex_unlock(&ses->session_mutex);
/*
* If multichannel or max_channels has changed, update the session's channels accordingly.
* This may add or remove channels to match the new configuration.
*/
if ((ctx->multichannel != cifs_sb->ctx->multichannel) ||
(ctx->max_channels != cifs_sb->ctx->max_channels)) {
/* Synchronize ses->chan_max with the new mount context */
smb3_sync_ses_chan_max(ses, ctx->max_channels);
/* Now update the session's channels to match the new configuration */
/* Prevent concurrent scaling operations */
spin_lock(&ses->ses_lock);
if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) {
spin_unlock(&ses->ses_lock);
mutex_unlock(&ses->session_mutex);
return -EINVAL;
}
ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS;
spin_unlock(&ses->ses_lock);
mutex_unlock(&ses->session_mutex);
rc = smb3_update_ses_channels(ses, ses->server,
false /* from_reconnect */,
false /* disable_mchan */);
/* Clear scaling flag after operation */
spin_lock(&ses->ses_lock);
ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS;
spin_unlock(&ses->ses_lock);
} else {
mutex_unlock(&ses->session_mutex);
}
STEAL_STRING(cifs_sb, ctx, domainname);
STEAL_STRING(cifs_sb, ctx, nodename);

View File

@@ -265,12 +265,16 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
}
/*
* called when multichannel is disabled by the server.
* this always gets called from smb2_reconnect
* and cannot get called in parallel threads.
* cifs_decrease_secondary_channels - Reduce the number of active secondary channels
* @ses: pointer to the CIFS session structure
* @disable_mchan: if true, reduce to a single channel; if false, reduce to chan_max
*
* This function disables and cleans up extra secondary channels for a CIFS session.
* If called during reconfiguration, it reduces the channel count to the new maximum (chan_max).
* Otherwise, it disables all but the primary channel.
*/
void
cifs_disable_secondary_channels(struct cifs_ses *ses)
cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan)
{
int i, chan_count;
struct TCP_Server_Info *server;
@@ -281,12 +285,16 @@ cifs_disable_secondary_channels(struct cifs_ses *ses)
if (chan_count == 1)
goto done;
ses->chan_count = 1;
/* Update the chan_count to the new maximum */
if (disable_mchan) {
cifs_dbg(FYI, "server does not support multichannel anymore.\n");
ses->chan_count = 1;
} else {
ses->chan_count = ses->chan_max;
}
/* for all secondary channels reset the need reconnect bit */
ses->chans_need_reconnect &= 1;
for (i = 1; i < chan_count; i++) {
/* Disable all secondary channels beyond the new chan_count */
for (i = ses->chan_count ; i < chan_count; i++) {
iface = ses->chans[i].iface;
server = ses->chans[i].server;
@@ -318,6 +326,15 @@ cifs_disable_secondary_channels(struct cifs_ses *ses)
spin_lock(&ses->chan_lock);
}
/* For extra secondary channels, reset the need reconnect bit */
if (ses->chan_count == 1) {
cifs_dbg(VFS, "Disable all secondary channels\n");
ses->chans_need_reconnect &= 1;
} else {
cifs_dbg(VFS, "Disable extra secondary channels\n");
ses->chans_need_reconnect &= ((1UL << ses->chan_max) - 1);
}
done:
spin_unlock(&ses->chan_lock);
}

View File

@@ -168,7 +168,7 @@ smb2_hdr_assemble(struct smb2_hdr *shdr, __le16 smb2_cmd,
static int
cifs_chan_skip_or_disable(struct cifs_ses *ses,
struct TCP_Server_Info *server,
bool from_reconnect)
bool from_reconnect, bool disable_mchan)
{
struct TCP_Server_Info *pserver;
unsigned int chan_index;
@@ -206,14 +206,46 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses,
return -EHOSTDOWN;
}
cifs_server_dbg(VFS,
"server does not support multichannel anymore. Disable all other channels\n");
cifs_disable_secondary_channels(ses);
cifs_decrease_secondary_channels(ses, disable_mchan);
return 0;
}
/*
* smb3_update_ses_channels - Synchronize session channels with new configuration
* @ses: pointer to the CIFS session structure
* @server: pointer to the TCP server info structure
* @from_reconnect: indicates if called from reconnect context
* @disable_mchan: indicates if called from reconnect to disable multichannel
*
* Returns 0 on success or error code on failure.
*
* Outside of reconfigure, this function is called from cifs_mount() during mount
* and from reconnect scenarios to adjust channel count when the
* server's multichannel support changes.
*/
int smb3_update_ses_channels(struct cifs_ses *ses, struct TCP_Server_Info *server,
bool from_reconnect, bool disable_mchan)
{
int rc = 0;
/*
* Manage session channels based on current count vs max:
* - If disable requested, skip or disable the channel
* - If below max channels, attempt to add more
* - If above max channels, skip or disable excess channels
*/
if (disable_mchan)
rc = cifs_chan_skip_or_disable(ses, server, from_reconnect, disable_mchan);
else {
if (ses->chan_count < ses->chan_max)
rc = cifs_try_adding_channels(ses);
else if (ses->chan_count > ses->chan_max)
rc = cifs_chan_skip_or_disable(ses, server, from_reconnect, disable_mchan);
}
return rc;
}
static int
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
struct TCP_Server_Info *server, bool from_reconnect)
@@ -355,8 +387,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
*/
if (ses->chan_count > 1 &&
!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
rc = cifs_chan_skip_or_disable(ses, server,
from_reconnect);
rc = smb3_update_ses_channels(ses, server,
from_reconnect, true /* disable_mchan */);
if (rc) {
mutex_unlock(&ses->session_mutex);
goto out;
@@ -438,8 +470,9 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
* treat this as server not supporting multichannel
*/
rc = cifs_chan_skip_or_disable(ses, server,
from_reconnect);
rc = smb3_update_ses_channels(ses, server,
from_reconnect,
true /* disable_mchan */);
goto skip_add_channels;
} else if (rc)
cifs_tcon_dbg(FYI, "%s: failed to query server interfaces: %d\n",
@@ -451,7 +484,8 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
if (ses->chan_count == 1)
cifs_server_dbg(VFS, "supports multichannel now\n");
cifs_try_adding_channels(ses);
smb3_update_ses_channels(ses, server, from_reconnect,
false /* disable_mchan */);
}
} else {
mutex_unlock(&ses->session_mutex);
@@ -1105,8 +1139,7 @@ SMB2_negotiate(const unsigned int xid,
req->SecurityMode = 0;
req->Capabilities = cpu_to_le32(server->vals->req_capabilities);
if (ses->chan_max > 1)
req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);
req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);
/* ClientGUID must be zero for SMB2.02 dialect */
if (server->vals->protocol_id == SMB20_PROT_ID)
@@ -1332,8 +1365,7 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
pneg_inbuf->Capabilities =
cpu_to_le32(server->vals->req_capabilities);
if (tcon->ses->chan_max > 1)
pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);
pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);
memcpy(pneg_inbuf->Guid, server->client_guid,
SMB2_CLIENT_GUID_SIZE);