Commit ef529f65 authored by Rajasi Mandal's avatar Rajasi Mandal Committed by Steve French
Browse files

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: default avatarShyam Prasad N <sprasad@microsoft.com>
Signed-off-by: default avatarRajasi Mandal <rajasimandal@microsoft.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 32a60868
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -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
+3 −1
Original line number Diff line number Diff line
@@ -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;
+50 −1
Original line number Diff line number Diff line
@@ -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;
	}

	/*
	 * 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);
+26 −9
Original line number Diff line number Diff line
@@ -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;

	/* 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);
}
+46 −14
Original line number Diff line number Diff line
@@ -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,7 +1139,6 @@ 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);

	/* 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 =
			cpu_to_le32(server->vals->req_capabilities);
	if (tcon->ses->chan_max > 1)
	pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);

	memcpy(pneg_inbuf->Guid, server->client_guid,