mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 23:22:31 +02: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:
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);
|
void cifs_free_hash(struct shash_desc **sdesc);
|
||||||
|
|
||||||
int cifs_try_adding_channels(struct cifs_ses *ses);
|
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);
|
bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface);
|
||||||
void cifs_ses_mark_for_reconnect(struct cifs_ses *ses);
|
void cifs_ses_mark_for_reconnect(struct cifs_ses *ses);
|
||||||
|
|
||||||
|
|
@ -660,7 +662,7 @@ bool
|
||||||
cifs_chan_is_iface_active(struct cifs_ses *ses,
|
cifs_chan_is_iface_active(struct cifs_ses *ses,
|
||||||
struct TCP_Server_Info *server);
|
struct TCP_Server_Info *server);
|
||||||
void
|
void
|
||||||
cifs_disable_secondary_channels(struct cifs_ses *ses);
|
cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan);
|
||||||
void
|
void
|
||||||
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
|
cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server);
|
||||||
int
|
int
|
||||||
|
|
|
||||||
|
|
@ -3926,7 +3926,9 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
|
||||||
ctx->prepath = NULL;
|
ctx->prepath = NULL;
|
||||||
|
|
||||||
out:
|
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);
|
rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon);
|
||||||
if (rc)
|
if (rc)
|
||||||
goto error;
|
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,
|
static int smb3_fs_context_parse_monolithic(struct fs_context *fc,
|
||||||
void *data);
|
void *data);
|
||||||
static int smb3_get_tree(struct fs_context *fc);
|
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 int smb3_reconfigure(struct fs_context *fc);
|
||||||
|
|
||||||
static const struct fs_context_operations smb3_fs_context_ops = {
|
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;
|
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)
|
static int smb3_reconfigure(struct fs_context *fc)
|
||||||
{
|
{
|
||||||
struct smb3_fs_context *ctx = smb3_fc2context(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;
|
ses->password2 = new_password2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
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, domainname);
|
||||||
STEAL_STRING(cifs_sb, ctx, nodename);
|
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.
|
* cifs_decrease_secondary_channels - Reduce the number of active secondary channels
|
||||||
* this always gets called from smb2_reconnect
|
* @ses: pointer to the CIFS session structure
|
||||||
* and cannot get called in parallel threads.
|
* @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
|
void
|
||||||
cifs_disable_secondary_channels(struct cifs_ses *ses)
|
cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan)
|
||||||
{
|
{
|
||||||
int i, chan_count;
|
int i, chan_count;
|
||||||
struct TCP_Server_Info *server;
|
struct TCP_Server_Info *server;
|
||||||
|
|
@ -281,12 +285,16 @@ cifs_disable_secondary_channels(struct cifs_ses *ses)
|
||||||
if (chan_count == 1)
|
if (chan_count == 1)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
/* 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;
|
ses->chan_count = 1;
|
||||||
|
} else {
|
||||||
|
ses->chan_count = ses->chan_max;
|
||||||
|
}
|
||||||
|
|
||||||
/* for all secondary channels reset the need reconnect bit */
|
/* Disable all secondary channels beyond the new chan_count */
|
||||||
ses->chans_need_reconnect &= 1;
|
for (i = ses->chan_count ; i < chan_count; i++) {
|
||||||
|
|
||||||
for (i = 1; i < chan_count; i++) {
|
|
||||||
iface = ses->chans[i].iface;
|
iface = ses->chans[i].iface;
|
||||||
server = ses->chans[i].server;
|
server = ses->chans[i].server;
|
||||||
|
|
||||||
|
|
@ -318,6 +326,15 @@ cifs_disable_secondary_channels(struct cifs_ses *ses)
|
||||||
spin_lock(&ses->chan_lock);
|
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:
|
done:
|
||||||
spin_unlock(&ses->chan_lock);
|
spin_unlock(&ses->chan_lock);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ smb2_hdr_assemble(struct smb2_hdr *shdr, __le16 smb2_cmd,
|
||||||
static int
|
static int
|
||||||
cifs_chan_skip_or_disable(struct cifs_ses *ses,
|
cifs_chan_skip_or_disable(struct cifs_ses *ses,
|
||||||
struct TCP_Server_Info *server,
|
struct TCP_Server_Info *server,
|
||||||
bool from_reconnect)
|
bool from_reconnect, bool disable_mchan)
|
||||||
{
|
{
|
||||||
struct TCP_Server_Info *pserver;
|
struct TCP_Server_Info *pserver;
|
||||||
unsigned int chan_index;
|
unsigned int chan_index;
|
||||||
|
|
@ -206,14 +206,46 @@ cifs_chan_skip_or_disable(struct cifs_ses *ses,
|
||||||
return -EHOSTDOWN;
|
return -EHOSTDOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
cifs_server_dbg(VFS,
|
cifs_decrease_secondary_channels(ses, disable_mchan);
|
||||||
"server does not support multichannel anymore. Disable all other channels\n");
|
|
||||||
cifs_disable_secondary_channels(ses);
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
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
|
static int
|
||||||
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||||
struct TCP_Server_Info *server, bool from_reconnect)
|
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 &&
|
if (ses->chan_count > 1 &&
|
||||||
!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
|
!(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
|
||||||
rc = cifs_chan_skip_or_disable(ses, server,
|
rc = smb3_update_ses_channels(ses, server,
|
||||||
from_reconnect);
|
from_reconnect, true /* disable_mchan */);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
mutex_unlock(&ses->session_mutex);
|
mutex_unlock(&ses->session_mutex);
|
||||||
goto out;
|
goto out;
|
||||||
|
|
@ -438,8 +470,9 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
|
||||||
* treat this as server not supporting multichannel
|
* treat this as server not supporting multichannel
|
||||||
*/
|
*/
|
||||||
|
|
||||||
rc = cifs_chan_skip_or_disable(ses, server,
|
rc = smb3_update_ses_channels(ses, server,
|
||||||
from_reconnect);
|
from_reconnect,
|
||||||
|
true /* disable_mchan */);
|
||||||
goto skip_add_channels;
|
goto skip_add_channels;
|
||||||
} else if (rc)
|
} else if (rc)
|
||||||
cifs_tcon_dbg(FYI, "%s: failed to query server interfaces: %d\n",
|
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)
|
if (ses->chan_count == 1)
|
||||||
cifs_server_dbg(VFS, "supports multichannel now\n");
|
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 {
|
} else {
|
||||||
mutex_unlock(&ses->session_mutex);
|
mutex_unlock(&ses->session_mutex);
|
||||||
|
|
@ -1105,7 +1139,6 @@ SMB2_negotiate(const unsigned int xid,
|
||||||
req->SecurityMode = 0;
|
req->SecurityMode = 0;
|
||||||
|
|
||||||
req->Capabilities = cpu_to_le32(server->vals->req_capabilities);
|
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 */
|
/* ClientGUID must be zero for SMB2.02 dialect */
|
||||||
|
|
@ -1332,7 +1365,6 @@ int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
|
||||||
|
|
||||||
pneg_inbuf->Capabilities =
|
pneg_inbuf->Capabilities =
|
||||||
cpu_to_le32(server->vals->req_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,
|
memcpy(pneg_inbuf->Guid, server->client_guid,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user