]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
cifs: client: allow changing multichannel mount options on remount
authorRajasi Mandal <rajasimandal@microsoft.com>
Fri, 5 Dec 2025 20:11:51 +0000 (20:11 +0000)
committerSteve French <stfrench@microsoft.com>
Fri, 5 Dec 2025 23:14:14 +0000 (17:14 -0600)
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>
fs/smb/client/cifsproto.h
fs/smb/client/connect.c
fs/smb/client/fs_context.c
fs/smb/client/sess.c
fs/smb/client/smb2pdu.c

index 823ca4f369140815295c209e0093089ed1a5c88b..bbe07e6018c0f5fdad35e73bab85bb7d33b21a90 100644 (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
index 2f184e9d3c67ca768262b2eb1a8c85169e66136a..cca6c722598d64d56052502269790dbd7fb42eef 100644 (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;
index 1419a7336d3feb49b74b6bccc7676345a87c83bf..c2de97e4ad59d7199c7bf0f097a6d57802bdc32f 100644 (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);
index 3e39faf637861baa9fa756348d4bbc41cd4a3570..a72d6a6d20f08e78a2fca721dc704cb2c35dff3c 100644 (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;
-
-       /* for all secondary channels reset the need reconnect bit */
-       ses->chans_need_reconnect &= 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 (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);
 }
index bff39a5f39b0fc95987ba87b3c26d0e38dd5632e..e8b423a28e18cff4f36e02ac404f98c4943f1f04 100644 (file)
@@ -168,7 +168,7 @@ out:
 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 @@ skip_terminate:
                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 @@ again:
         */
        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 @@ skip_sess_setup:
                         * 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 @@ skip_sess_setup:
                        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);