spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry_safe(ses, nses, &pserver->smb_ses_list, smb_ses_list) {
+ /*
+ * if channel has been marked for termination, nothing to do
+ * for the channel. in fact, we cannot find the channel for the
+ * server. So safe to exit here
+ */
+ if (server->terminate)
+ break;
+
/* check if iface is still active */
if (!cifs_chan_is_iface_active(ses, server))
cifs_chan_update_iface(ses, server);
spin_lock(&tcon->tc_lock);
tcon->status = TID_NEED_RECON;
spin_unlock(&tcon->tc_lock);
+
+ cancel_delayed_work(&tcon->query_interfaces);
}
if (ses->tcon_ipc) {
ses->tcon_ipc->need_reconnect = true;
return new_chan_count - old_chan_count;
}
+/*
+ * called when multichannel is disabled by the server.
+ * this always gets called from smb2_reconnect
+ * and cannot get called in parallel threads.
+ */
+void
+cifs_disable_secondary_channels(struct cifs_ses *ses)
+{
+ int i, chan_count;
+ struct TCP_Server_Info *server;
+ struct cifs_server_iface *iface;
+
+ spin_lock(&ses->chan_lock);
+ chan_count = ses->chan_count;
+ if (chan_count == 1)
+ goto done;
+
+ ses->chan_count = 1;
+
+ /* for all secondary channels reset the need reconnect bit */
+ ses->chans_need_reconnect &= 1;
+
+ for (i = 1; i < chan_count; i++) {
+ iface = ses->chans[i].iface;
+ server = ses->chans[i].server;
+
+ if (iface) {
+ spin_lock(&ses->iface_lock);
+ kref_put(&iface->refcount, release_iface);
+ ses->chans[i].iface = NULL;
+ iface->num_channels--;
+ if (iface->weight_fulfilled)
+ iface->weight_fulfilled--;
+ spin_unlock(&ses->iface_lock);
+ }
+
+ spin_unlock(&ses->chan_lock);
+ if (server && !server->terminate) {
+ server->terminate = true;
+ cifs_signal_cifsd_for_reconnect(server, false);
+ }
+ spin_lock(&ses->chan_lock);
+
+ if (server) {
+ ses->chans[i].server = NULL;
+ cifs_put_tcp_session(server, false);
+ }
+
+ }
+
+done:
+ 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
out:
if (rc && chan->server) {
- /*
- * we should avoid race with these delayed works before we
- * remove this channel
- */
- cancel_delayed_work_sync(&chan->server->echo);
- cancel_delayed_work_sync(&chan->server->reconnect);
+ cifs_put_tcp_session(chan->server, 0);
spin_lock(&ses->chan_lock);
+
/* we rely on all bits beyond chan_count to be clear */
cifs_chan_clear_need_reconnect(ses, chan->server);
ses->chan_count--;
*/
WARN_ON(ses->chan_count < 1);
spin_unlock(&ses->chan_lock);
-
- cifs_put_tcp_session(chan->server, 0);
}
kfree(ctx->UNC);
struct nls_table *nls_codepage = NULL;
struct cifs_ses *ses;
int xid;
+ struct TCP_Server_Info *pserver;
+ unsigned int chan_index;
/*
* SMB2s NegProt, SessSetup, Logoff do not have tcon yet so
return -EAGAIN;
}
}
+
+ /* if server is marked for termination, cifsd will cleanup */
+ if (server->terminate) {
+ spin_unlock(&server->srv_lock);
+ return -EHOSTDOWN;
+ }
spin_unlock(&server->srv_lock);
again:
tcon->need_reconnect);
mutex_lock(&ses->session_mutex);
+ /*
+ * if this is called by delayed work, and the channel has been disabled
+ * in parallel, the delayed work can continue to execute in parallel
+ * there's a chance that this channel may not exist anymore
+ */
+ spin_lock(&server->srv_lock);
+ if (server->tcpStatus == CifsExiting) {
+ spin_unlock(&server->srv_lock);
+ mutex_unlock(&ses->session_mutex);
+ rc = -EHOSTDOWN;
+ goto out;
+ }
+
/*
* Recheck after acquire mutex. If another thread is negotiating
* and the server never sends an answer the socket will be closed
* and tcpStatus set to reconnect.
*/
- spin_lock(&server->srv_lock);
if (server->tcpStatus == CifsNeedReconnect) {
spin_unlock(&server->srv_lock);
mutex_unlock(&ses->session_mutex);
rc = cifs_negotiate_protocol(0, ses, server);
if (!rc) {
+ /*
+ * if server stopped supporting multichannel
+ * and the first channel reconnected, disable all the others.
+ */
+ if (ses->chan_count > 1 &&
+ !(server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
+ if (SERVER_IS_CHAN(server)) {
+ cifs_dbg(VFS, "server %s does not support " \
+ "multichannel anymore. skipping secondary channel\n",
+ ses->server->hostname);
+
+ spin_lock(&ses->chan_lock);
+ chan_index = cifs_ses_get_chan_index(ses, server);
+ if (chan_index == CIFS_INVAL_CHAN_INDEX) {
+ spin_unlock(&ses->chan_lock);
+ goto skip_terminate;
+ }
+
+ ses->chans[chan_index].server = NULL;
+ spin_unlock(&ses->chan_lock);
+
+ /*
+ * the above reference of server by channel
+ * needs to be dropped without holding chan_lock
+ * as cifs_put_tcp_session takes a higher lock
+ * i.e. cifs_tcp_ses_lock
+ */
+ cifs_put_tcp_session(server, 1);
+
+ server->terminate = true;
+ cifs_signal_cifsd_for_reconnect(server, false);
+
+ /* mark primary server as needing reconnect */
+ pserver = server->primary_server;
+ cifs_signal_cifsd_for_reconnect(pserver, false);
+
+skip_terminate:
+ mutex_unlock(&ses->session_mutex);
+ rc = -EHOSTDOWN;
+ goto out;
+ } else {
+ cifs_server_dbg(VFS, "does not support " \
+ "multichannel anymore. disabling all other channels\n");
+ cifs_disable_secondary_channels(ses);
+ }
+ }
+
rc = cifs_setup_session(0, ses, server, nls_codepage);
if ((rc == -EACCES) && !tcon->retry) {
mutex_unlock(&ses->session_mutex);
/* Prevent simultaneous reconnects that can corrupt tcon->rlist list */
mutex_lock(&pserver->reconnect_mutex);
+ /* if the server is marked for termination, drop the ref count here */
+ if (server->terminate) {
+ cifs_put_tcp_session(server, true);
+ mutex_unlock(&pserver->reconnect_mutex);
+ return;
+ }
+
INIT_LIST_HEAD(&tmp_list);
INIT_LIST_HEAD(&tmp_ses_list);
cifs_dbg(FYI, "Reconnecting tcons and channels\n");