Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Rajasi Mandal and committed by
Steve French
ef529f65 32a60868

+128 -26
+3 -1
fs/smb/client/cifsproto.h
··· 635 635 void cifs_free_hash(struct shash_desc **sdesc); 636 636 637 637 int cifs_try_adding_channels(struct cifs_ses *ses); 638 + int smb3_update_ses_channels(struct cifs_ses *ses, struct TCP_Server_Info *server, 639 + bool from_reconnect, bool disable_mchan); 638 640 bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface); 639 641 void cifs_ses_mark_for_reconnect(struct cifs_ses *ses); 640 642 ··· 662 660 cifs_chan_is_iface_active(struct cifs_ses *ses, 663 661 struct TCP_Server_Info *server); 664 662 void 665 - cifs_disable_secondary_channels(struct cifs_ses *ses); 663 + cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan); 666 664 void 667 665 cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server); 668 666 int
+3 -1
fs/smb/client/connect.c
··· 3926 3926 ctx->prepath = NULL; 3927 3927 3928 3928 out: 3929 - cifs_try_adding_channels(mnt_ctx.ses); 3929 + smb3_update_ses_channels(mnt_ctx.ses, mnt_ctx.server, 3930 + false /* from_reconnect */, 3931 + false /* disable_mchan */); 3930 3932 rc = mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon); 3931 3933 if (rc) 3932 3934 goto error;
+50 -1
fs/smb/client/fs_context.c
··· 758 758 static int smb3_fs_context_parse_monolithic(struct fs_context *fc, 759 759 void *data); 760 760 static int smb3_get_tree(struct fs_context *fc); 761 + static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels); 761 762 static int smb3_reconfigure(struct fs_context *fc); 762 763 763 764 static const struct fs_context_operations smb3_fs_context_ops = { ··· 1056 1055 return 0; 1057 1056 } 1058 1057 1058 + /* 1059 + * smb3_sync_ses_chan_max - Synchronize the session's maximum channel count 1060 + * @ses: pointer to the old CIFS session structure 1061 + * @max_channels: new maximum number of channels to allow 1062 + * 1063 + * Updates the session's chan_max field to the new value, protecting the update 1064 + * with the session's channel lock. This should be called whenever the maximum 1065 + * allowed channels for a session changes (e.g., after a remount or reconfigure). 1066 + */ 1067 + static void smb3_sync_ses_chan_max(struct cifs_ses *ses, unsigned int max_channels) 1068 + { 1069 + spin_lock(&ses->chan_lock); 1070 + ses->chan_max = max_channels; 1071 + spin_unlock(&ses->chan_lock); 1072 + } 1073 + 1059 1074 static int smb3_reconfigure(struct fs_context *fc) 1060 1075 { 1061 1076 struct smb3_fs_context *ctx = smb3_fc2context(fc); ··· 1154 1137 ses->password2 = new_password2; 1155 1138 } 1156 1139 1157 - mutex_unlock(&ses->session_mutex); 1140 + /* 1141 + * If multichannel or max_channels has changed, update the session's channels accordingly. 1142 + * This may add or remove channels to match the new configuration. 1143 + */ 1144 + if ((ctx->multichannel != cifs_sb->ctx->multichannel) || 1145 + (ctx->max_channels != cifs_sb->ctx->max_channels)) { 1146 + 1147 + /* Synchronize ses->chan_max with the new mount context */ 1148 + smb3_sync_ses_chan_max(ses, ctx->max_channels); 1149 + /* Now update the session's channels to match the new configuration */ 1150 + /* Prevent concurrent scaling operations */ 1151 + spin_lock(&ses->ses_lock); 1152 + if (ses->flags & CIFS_SES_FLAG_SCALE_CHANNELS) { 1153 + spin_unlock(&ses->ses_lock); 1154 + mutex_unlock(&ses->session_mutex); 1155 + return -EINVAL; 1156 + } 1157 + ses->flags |= CIFS_SES_FLAG_SCALE_CHANNELS; 1158 + spin_unlock(&ses->ses_lock); 1159 + 1160 + mutex_unlock(&ses->session_mutex); 1161 + 1162 + rc = smb3_update_ses_channels(ses, ses->server, 1163 + false /* from_reconnect */, 1164 + false /* disable_mchan */); 1165 + 1166 + /* Clear scaling flag after operation */ 1167 + spin_lock(&ses->ses_lock); 1168 + ses->flags &= ~CIFS_SES_FLAG_SCALE_CHANNELS; 1169 + spin_unlock(&ses->ses_lock); 1170 + } else { 1171 + mutex_unlock(&ses->session_mutex); 1172 + } 1158 1173 1159 1174 STEAL_STRING(cifs_sb, ctx, domainname); 1160 1175 STEAL_STRING(cifs_sb, ctx, nodename);
+26 -9
fs/smb/client/sess.c
··· 265 265 } 266 266 267 267 /* 268 - * called when multichannel is disabled by the server. 269 - * this always gets called from smb2_reconnect 270 - * and cannot get called in parallel threads. 268 + * cifs_decrease_secondary_channels - Reduce the number of active secondary channels 269 + * @ses: pointer to the CIFS session structure 270 + * @disable_mchan: if true, reduce to a single channel; if false, reduce to chan_max 271 + * 272 + * This function disables and cleans up extra secondary channels for a CIFS session. 273 + * If called during reconfiguration, it reduces the channel count to the new maximum (chan_max). 274 + * Otherwise, it disables all but the primary channel. 271 275 */ 272 276 void 273 - cifs_disable_secondary_channels(struct cifs_ses *ses) 277 + cifs_decrease_secondary_channels(struct cifs_ses *ses, bool disable_mchan) 274 278 { 275 279 int i, chan_count; 276 280 struct TCP_Server_Info *server; ··· 285 281 if (chan_count == 1) 286 282 goto done; 287 283 288 - ses->chan_count = 1; 284 + /* Update the chan_count to the new maximum */ 285 + if (disable_mchan) { 286 + cifs_dbg(FYI, "server does not support multichannel anymore.\n"); 287 + ses->chan_count = 1; 288 + } else { 289 + ses->chan_count = ses->chan_max; 290 + } 289 291 290 - /* for all secondary channels reset the need reconnect bit */ 291 - ses->chans_need_reconnect &= 1; 292 - 293 - for (i = 1; i < chan_count; i++) { 292 + /* Disable all secondary channels beyond the new chan_count */ 293 + for (i = ses->chan_count ; i < chan_count; i++) { 294 294 iface = ses->chans[i].iface; 295 295 server = ses->chans[i].server; 296 296 ··· 324 316 } 325 317 326 318 spin_lock(&ses->chan_lock); 319 + } 320 + 321 + /* For extra secondary channels, reset the need reconnect bit */ 322 + if (ses->chan_count == 1) { 323 + cifs_dbg(VFS, "Disable all secondary channels\n"); 324 + ses->chans_need_reconnect &= 1; 325 + } else { 326 + cifs_dbg(VFS, "Disable extra secondary channels\n"); 327 + ses->chans_need_reconnect &= ((1UL << ses->chan_max) - 1); 327 328 } 328 329 329 330 done:
+46 -14
fs/smb/client/smb2pdu.c
··· 168 168 static int 169 169 cifs_chan_skip_or_disable(struct cifs_ses *ses, 170 170 struct TCP_Server_Info *server, 171 - bool from_reconnect) 171 + bool from_reconnect, bool disable_mchan) 172 172 { 173 173 struct TCP_Server_Info *pserver; 174 174 unsigned int chan_index; ··· 206 206 return -EHOSTDOWN; 207 207 } 208 208 209 - cifs_server_dbg(VFS, 210 - "server does not support multichannel anymore. Disable all other channels\n"); 211 - cifs_disable_secondary_channels(ses); 212 - 209 + cifs_decrease_secondary_channels(ses, disable_mchan); 213 210 214 211 return 0; 212 + } 213 + 214 + /* 215 + * smb3_update_ses_channels - Synchronize session channels with new configuration 216 + * @ses: pointer to the CIFS session structure 217 + * @server: pointer to the TCP server info structure 218 + * @from_reconnect: indicates if called from reconnect context 219 + * @disable_mchan: indicates if called from reconnect to disable multichannel 220 + * 221 + * Returns 0 on success or error code on failure. 222 + * 223 + * Outside of reconfigure, this function is called from cifs_mount() during mount 224 + * and from reconnect scenarios to adjust channel count when the 225 + * server's multichannel support changes. 226 + */ 227 + int smb3_update_ses_channels(struct cifs_ses *ses, struct TCP_Server_Info *server, 228 + bool from_reconnect, bool disable_mchan) 229 + { 230 + int rc = 0; 231 + /* 232 + * Manage session channels based on current count vs max: 233 + * - If disable requested, skip or disable the channel 234 + * - If below max channels, attempt to add more 235 + * - If above max channels, skip or disable excess channels 236 + */ 237 + if (disable_mchan) 238 + rc = cifs_chan_skip_or_disable(ses, server, from_reconnect, disable_mchan); 239 + else { 240 + if (ses->chan_count < ses->chan_max) 241 + rc = cifs_try_adding_channels(ses); 242 + else if (ses->chan_count > ses->chan_max) 243 + rc = cifs_chan_skip_or_disable(ses, server, from_reconnect, disable_mchan); 244 + } 245 + 246 + return rc; 215 247 } 216 248 217 249 static int ··· 387 355 */ 388 356 if (ses->chan_count > 1 && 389 357 !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) { 390 - rc = cifs_chan_skip_or_disable(ses, server, 391 - from_reconnect); 358 + rc = smb3_update_ses_channels(ses, server, 359 + from_reconnect, true /* disable_mchan */); 392 360 if (rc) { 393 361 mutex_unlock(&ses->session_mutex); 394 362 goto out; ··· 470 438 * treat this as server not supporting multichannel 471 439 */ 472 440 473 - rc = cifs_chan_skip_or_disable(ses, server, 474 - from_reconnect); 441 + rc = smb3_update_ses_channels(ses, server, 442 + from_reconnect, 443 + true /* disable_mchan */); 475 444 goto skip_add_channels; 476 445 } else if (rc) 477 446 cifs_tcon_dbg(FYI, "%s: failed to query server interfaces: %d\n", ··· 484 451 if (ses->chan_count == 1) 485 452 cifs_server_dbg(VFS, "supports multichannel now\n"); 486 453 487 - cifs_try_adding_channels(ses); 454 + smb3_update_ses_channels(ses, server, from_reconnect, 455 + false /* disable_mchan */); 488 456 } 489 457 } else { 490 458 mutex_unlock(&ses->session_mutex); ··· 1139 1105 req->SecurityMode = 0; 1140 1106 1141 1107 req->Capabilities = cpu_to_le32(server->vals->req_capabilities); 1142 - if (ses->chan_max > 1) 1143 - req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL); 1108 + req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL); 1144 1109 1145 1110 /* ClientGUID must be zero for SMB2.02 dialect */ 1146 1111 if (server->vals->protocol_id == SMB20_PROT_ID) ··· 1365 1332 1366 1333 pneg_inbuf->Capabilities = 1367 1334 cpu_to_le32(server->vals->req_capabilities); 1368 - if (tcon->ses->chan_max > 1) 1369 - pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL); 1335 + pneg_inbuf->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL); 1370 1336 1371 1337 memcpy(pneg_inbuf->Guid, server->client_guid, 1372 1338 SMB2_CLIENT_GUID_SIZE);