mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
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:
committed by
Steve French
parent
32a6086809
commit
ef529f655a
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user