When a server stops supporting multichannel, we will keep attempting reconnects to the secondary channels today. Avoid this by freeing extra channels when negotiate returns no multichannel support. Signed-off-by: Shyam Prasad N <sprasad@xxxxxxxxxxxxx> --- fs/cifs/cifsproto.h | 2 ++ fs/cifs/connect.c | 6 ++++++ fs/cifs/sess.c | 35 +++++++++++++++++++++++++++++++++++ fs/cifs/smb2ops.c | 8 ++++++++ 4 files changed, 51 insertions(+) diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 30fd81268eb7..343e582672b9 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -638,6 +638,8 @@ cifs_chan_needs_reconnect(struct cifs_ses *ses, bool cifs_chan_is_iface_active(struct cifs_ses *ses, struct TCP_Server_Info *server); +void +cifs_disable_extra_channels(struct cifs_ses *ses); int cifs_chan_update_iface(struct cifs_ses *ses, struct TCP_Server_Info *server); int diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index b9af60417194..6375b08b9bcb 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -130,6 +130,12 @@ static void smb2_query_server_interfaces(struct work_struct *work) if (rc) { cifs_dbg(FYI, "%s: failed to query server interfaces: %d\n", __func__, rc); + + if (rc == -EOPNOTSUPP) { + /* cancel polling of interfaces and do not resched */ + cancel_delayed_work_sync(&tcon->query_interfaces); + return; + } } queue_delayed_work(cifsiod_wq, &tcon->query_interfaces, diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 9b51b2309e9c..34ae292bdff2 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -274,6 +274,41 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) return new_chan_count - old_chan_count; } +/* + * called when multichannel is disabled by the server + */ +void +cifs_disable_extra_channels(struct cifs_ses *ses) +{ + int i, chan_count; + struct cifs_server_iface *iface = NULL; + struct TCP_Server_Info *server = NULL; + + spin_lock(&ses->chan_lock); + chan_count = ses->chan_count; + ses->chan_count = 1; + for (i = 1; i < chan_count; i++) { + iface = ses->chans[i].iface; + server = ses->chans[i].server; + spin_unlock(&ses->chan_lock); + + if (iface) { + spin_lock(&ses->iface_lock); + kref_put(&iface->refcount, release_iface); + iface->num_channels--; + if (--iface->weight_fulfilled < 0) + iface->weight_fulfilled = 0; + spin_unlock(&ses->iface_lock); + } + cifs_put_tcp_session(server, 0); + + spin_lock(&ses->chan_lock); + ses->chans[i].iface = NULL; + ses->chans[i].server = NULL; + } + spin_unlock(&ses->chan_lock); +} + /* * update the iface for the channel if necessary. * will return 0 when iface is updated, 1 if removed, 2 otherwise diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index a5e53cb1ac49..c7a8a6049291 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -411,6 +411,14 @@ smb2_negotiate(const unsigned int xid, /* BB we probably don't need to retry with modern servers */ if (rc == -EAGAIN) rc = -EHOSTDOWN; + + if (!rc && + ses->chan_count > 1 && + !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) { + cifs_dbg(VFS, "server %s does not support multichannel anymore\n", ses->server->hostname); + cifs_disable_extra_channels(ses); + } + return rc; } -- 2.34.1