From 065480ea8b1e03fd9f5f83dee42685fe2bea6445 Mon Sep 17 00:00:00 2001 From: Shyam Prasad N Date: Fri, 8 Apr 2022 13:31:37 +0000 Subject: [PATCH] cifs: avoid parallel session setups on same channel After allowing channels to reconnect in parallel, it now becomes important to take care that multiple processes do not call negotiate/session setup in parallel on the same channel. This change avoids that by marking a channel as "in_reconnect". During session setup if the channel in question has this flag set, we return immediately. Signed-off-by: Shyam Prasad N Signed-off-by: Steve French --- fs/cifs/cifs_debug.c | 4 ++++ fs/cifs/cifsglob.h | 5 +++++ fs/cifs/cifsproto.h | 9 +++++++++ fs/cifs/connect.c | 27 ++++++++++++++++++++------- fs/cifs/sess.c | 27 +++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index c098735d41c6e..1dd995efd5b86 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -419,6 +419,8 @@ skip_rdma: spin_lock(&ses->chan_lock); if (CIFS_CHAN_NEEDS_RECONNECT(ses, 0)) seq_puts(m, "\tPrimary channel: DISCONNECTED "); + if (CIFS_CHAN_IN_RECONNECT(ses, 0)) + seq_puts(m, "\t[RECONNECTING] "); if (ses->chan_count > 1) { seq_printf(m, "\n\n\tExtra Channels: %zu ", @@ -427,6 +429,8 @@ skip_rdma: cifs_dump_channel(m, j, &ses->chans[j]); if (CIFS_CHAN_NEEDS_RECONNECT(ses, j)) seq_puts(m, "\tDISCONNECTED "); + if (CIFS_CHAN_IN_RECONNECT(ses, j)) + seq_puts(m, "\t[RECONNECTING] "); } } spin_unlock(&ses->chan_lock); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 711cf51ac14f9..8880a26f234f4 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -922,6 +922,7 @@ struct cifs_server_iface { }; struct cifs_chan { + unsigned int in_reconnect : 1; /* if session setup in progress for this channel */ struct TCP_Server_Info *server; __u8 signkey[SMB3_SIGN_KEY_SIZE]; }; @@ -984,12 +985,16 @@ struct cifs_ses { #define CIFS_MAX_CHANNELS 16 #define CIFS_ALL_CHANNELS_SET(ses) \ ((1UL << (ses)->chan_count) - 1) +#define CIFS_ALL_CHANS_GOOD(ses) \ + (!(ses)->chans_need_reconnect) #define CIFS_ALL_CHANS_NEED_RECONNECT(ses) \ ((ses)->chans_need_reconnect == CIFS_ALL_CHANNELS_SET(ses)) #define CIFS_SET_ALL_CHANS_NEED_RECONNECT(ses) \ ((ses)->chans_need_reconnect = CIFS_ALL_CHANNELS_SET(ses)) #define CIFS_CHAN_NEEDS_RECONNECT(ses, index) \ test_bit((index), &(ses)->chans_need_reconnect) +#define CIFS_CHAN_IN_RECONNECT(ses, index) \ + ((ses)->chans[(index)].in_reconnect) struct cifs_chan chans[CIFS_MAX_CHANNELS]; size_t chan_count; diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index 0df3b24a0bf4c..3b7366ec03c76 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -619,6 +619,15 @@ unsigned int cifs_ses_get_chan_index(struct cifs_ses *ses, struct TCP_Server_Info *server); void +cifs_chan_set_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); +void +cifs_chan_clear_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); +bool +cifs_chan_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server); +void cifs_chan_set_need_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server); void diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index df4bcc581559f..199b076f7a642 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -3995,17 +3995,27 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, int rc = -ENOSYS; bool is_binding = false; - /* only send once per connect */ - spin_lock(&ses->chan_lock); - is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); - spin_unlock(&ses->chan_lock); spin_lock(&cifs_tcp_ses_lock); - if (ses->ses_status == SES_EXITING) { + if (ses->ses_status != SES_GOOD && + ses->ses_status != SES_NEW && + ses->ses_status != SES_NEED_RECON) { spin_unlock(&cifs_tcp_ses_lock); return 0; } + /* only send once per connect */ + spin_lock(&ses->chan_lock); + if (CIFS_ALL_CHANS_GOOD(ses) || + cifs_chan_in_reconnect(ses, server)) { + spin_unlock(&ses->chan_lock); + spin_unlock(&cifs_tcp_ses_lock); + return 0; + } + is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses); + cifs_chan_set_in_reconnect(ses, server); + spin_unlock(&ses->chan_lock); + if (!is_binding) ses->ses_status = SES_IN_SETUP; spin_unlock(&cifs_tcp_ses_lock); @@ -4035,16 +4045,19 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, spin_lock(&cifs_tcp_ses_lock); if (ses->ses_status == SES_IN_SETUP) ses->ses_status = SES_NEED_RECON; + spin_lock(&ses->chan_lock); + cifs_chan_clear_in_reconnect(ses, server); + spin_unlock(&ses->chan_lock); spin_unlock(&cifs_tcp_ses_lock); } else { spin_lock(&cifs_tcp_ses_lock); if (ses->ses_status == SES_IN_SETUP) ses->ses_status = SES_GOOD; - spin_unlock(&cifs_tcp_ses_lock); - spin_lock(&ses->chan_lock); + cifs_chan_clear_in_reconnect(ses, server); cifs_chan_clear_need_reconnect(ses, server); spin_unlock(&ses->chan_lock); + spin_unlock(&cifs_tcp_ses_lock); } return rc; diff --git a/fs/cifs/sess.c b/fs/cifs/sess.c index 32f478c7a66d8..7c453f8701eb0 100644 --- a/fs/cifs/sess.c +++ b/fs/cifs/sess.c @@ -85,6 +85,33 @@ cifs_ses_get_chan_index(struct cifs_ses *ses, return 0; } +void +cifs_chan_set_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + ses->chans[chan_index].in_reconnect = true; +} + +void +cifs_chan_clear_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + ses->chans[chan_index].in_reconnect = false; +} + +bool +cifs_chan_in_reconnect(struct cifs_ses *ses, + struct TCP_Server_Info *server) +{ + unsigned int chan_index = cifs_ses_get_chan_index(ses, server); + + return CIFS_CHAN_IN_RECONNECT(ses, chan_index); +} + void cifs_chan_set_need_reconnect(struct cifs_ses *ses, struct TCP_Server_Info *server) -- 2.39.5