]> git.baikalelectronics.ru Git - kernel.git/commitdiff
cifs: track individual channel status using chans_need_reconnect
authorShyam Prasad N <sprasad@microsoft.com>
Mon, 19 Jul 2021 12:46:53 +0000 (12:46 +0000)
committerSteve French <stfrench@microsoft.com>
Mon, 3 Jan 2022 02:38:46 +0000 (20:38 -0600)
We needed a way to identify the channels under the smb session
which are in reconnect, so that the traffic to other channels
can continue. So I replaced the bool need_reconnect with
a bitmask identifying all the channels that need reconnection
(named chans_need_reconnect). When a channel needs reconnection,
the bit corresponding to the index of the server in ses->chans
is used to set this bitmask. Checking if no channels or all
the channels need reconnect then becomes very easy.

Also wrote some helper macros for checking and setting the bits.

Signed-off-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/connect.c
fs/cifs/sess.c
fs/cifs/smb2pdu.c

index be74606724c7939d0fe944ebf572d2090b62b09c..91878e84c637347c440968179effaface8be271b 100644 (file)
@@ -939,7 +939,6 @@ struct cifs_ses {
        struct ntlmssp_auth *ntlmssp; /* ciphertext, flags, server challenge */
        enum securityEnum sectype; /* what security flavor was specified? */
        bool sign;              /* is signing required? */
-       bool need_reconnect:1; /* connection reset, uid now invalid */
        bool domainAuto:1;
        bool binding:1; /* are we binding the session? */
        __u16 session_flags;
@@ -969,11 +968,25 @@ struct cifs_ses {
        spinlock_t chan_lock;
        /* ========= begin: protected by chan_lock ======== */
 #define CIFS_MAX_CHANNELS 16
+#define CIFS_ALL_CHANNELS_SET(ses)     \
+       ((1UL << (ses)->chan_count) - 1)
+#define CIFS_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)
+
        struct cifs_chan chans[CIFS_MAX_CHANNELS];
        struct cifs_chan *binding_chan;
        size_t chan_count;
        size_t chan_max;
        atomic_t chan_seq; /* round robin state */
+
+       /*
+        * chans_need_reconnect is a bitmap indicating which of the channels
+        * under this smb session needs to be reconnected.
+        * If not multichannel session, only one bit will be used.
+        */
+       unsigned long chans_need_reconnect;
        /* ========= end: protected by chan_lock ======== */
 };
 
index 4f5a3e857df4aea68fbcd56aff0e76f58b823c15..2a821a8801d2a5faa595d177ec56a5744e315e06 100644 (file)
@@ -601,6 +601,19 @@ bool is_server_using_iface(struct TCP_Server_Info *server,
 bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface);
 void cifs_ses_mark_for_reconnect(struct cifs_ses *ses);
 
+unsigned int
+cifs_ses_get_chan_index(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
+cifs_chan_clear_need_reconnect(struct cifs_ses *ses,
+                              struct TCP_Server_Info *server);
+bool
+cifs_chan_needs_reconnect(struct cifs_ses *ses,
+                         struct TCP_Server_Info *server);
+
 void extract_unc_hostname(const char *unc, const char **h, size_t *len);
 int copy_path_name(char *dst, const char *src);
 int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
index 243d17696f06199d5488b97bfdccdea218838ccc..cbb0d55c1267d6498674386d0e6e842012a87b43 100644 (file)
@@ -166,8 +166,12 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
                retries = server->nr_targets;
        }
 
-       if (!ses->need_reconnect && !tcon->need_reconnect)
+       spin_lock(&ses->chan_lock);
+       if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) {
+               spin_unlock(&ses->chan_lock);
                return 0;
+       }
+       spin_unlock(&ses->chan_lock);
 
        nls_codepage = load_nls_default();
 
@@ -188,8 +192,25 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
                goto out;
        }
 
+       /*
+        * need to prevent multiple threads trying to simultaneously
+        * reconnect the same SMB session
+        */
+       spin_lock(&ses->chan_lock);
+       if (!cifs_chan_needs_reconnect(ses, server)) {
+               spin_unlock(&ses->chan_lock);
+               /* this just means that we only need to tcon */
+               if (tcon->need_reconnect)
+                       goto skip_sess_setup;
+
+               rc = -EHOSTDOWN;
+               mutex_unlock(&ses->session_mutex);
+               goto out;
+       }
+       spin_unlock(&ses->chan_lock);
+
        rc = cifs_negotiate_protocol(0, ses);
-       if (rc == 0 && ses->need_reconnect)
+       if (!rc)
                rc = cifs_setup_session(0, ses, nls_codepage);
 
        /* do we need to reconnect tcon? */
@@ -198,6 +219,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
                goto out;
        }
 
+skip_sess_setup:
        cifs_mark_open_files_invalid(tcon);
        rc = cifs_tree_connect(0, tcon, nls_codepage);
        mutex_unlock(&ses->session_mutex);
@@ -337,8 +359,13 @@ static int
 smb_init_no_reconnect(int smb_command, int wct, struct cifs_tcon *tcon,
                        void **request_buf, void **response_buf)
 {
-       if (tcon->ses->need_reconnect || tcon->need_reconnect)
+       spin_lock(&tcon->ses->chan_lock);
+       if (cifs_chan_needs_reconnect(tcon->ses, tcon->ses->server) ||
+           tcon->need_reconnect) {
+               spin_unlock(&tcon->ses->chan_lock);
                return -EHOSTDOWN;
+       }
+       spin_unlock(&tcon->ses->chan_lock);
 
        return __smb_init(smb_command, wct, tcon, request_buf, response_buf);
 }
@@ -600,8 +627,12 @@ CIFSSMBTDis(const unsigned int xid, struct cifs_tcon *tcon)
         * the tcon is no longer on the list, so no need to take lock before
         * checking this.
         */
-       if ((tcon->need_reconnect) || (tcon->ses->need_reconnect))
-               return 0;
+       spin_lock(&tcon->ses->chan_lock);
+       if ((tcon->need_reconnect) || CIFS_ALL_CHANS_NEED_RECONNECT(tcon->ses)) {
+               spin_unlock(&tcon->ses->chan_lock);
+               return -EIO;
+       }
+       spin_unlock(&tcon->ses->chan_lock);
 
        rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon,
                            (void **)&smb_buffer);
@@ -696,9 +727,14 @@ CIFSSMBLogoff(const unsigned int xid, struct cifs_ses *ses)
                return -EIO;
 
        mutex_lock(&ses->session_mutex);
-       if (ses->need_reconnect)
+       spin_lock(&ses->chan_lock);
+       if (CIFS_ALL_CHANS_NEED_RECONNECT(ses)) {
+               spin_unlock(&ses->chan_lock);
                goto session_already_dead; /* no need to send SMBlogoff if uid
                                              already closed due to reconnect */
+       }
+       spin_unlock(&ses->chan_lock);
+
        rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB);
        if (rc) {
                mutex_unlock(&ses->session_mutex);
index 1060164b984a7459b5d2a05832e0b115490e8db9..fa80a23f9fcfa8680e54a2acaeebdcc7c9f3630f 100644 (file)
@@ -191,11 +191,23 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) {
-               ses->need_reconnect = true;
+               spin_lock(&ses->chan_lock);
+               if (cifs_chan_needs_reconnect(ses, server))
+                       goto next_session;
+
+               cifs_chan_set_need_reconnect(ses, server);
+
+               /* If all channels need reconnect, then tcon needs reconnect */
+               if (!CIFS_ALL_CHANS_NEED_RECONNECT(ses))
+                       goto next_session;
+
                list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
                        tcon->need_reconnect = true;
                if (ses->tcon_ipc)
                        ses->tcon_ipc->need_reconnect = true;
+
+next_session:
+               spin_unlock(&ses->chan_lock);
        }
        spin_unlock(&cifs_tcp_ses_lock);
 
@@ -1988,7 +2000,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                         ses->status);
 
                mutex_lock(&ses->session_mutex);
-               if (ses->need_reconnect) {
+               spin_lock(&ses->chan_lock);
+               if (cifs_chan_needs_reconnect(ses, server)) {
+                       spin_unlock(&ses->chan_lock);
                        cifs_dbg(FYI, "Session needs reconnect\n");
 
                        rc = cifs_negotiate_protocol(xid, ses);
@@ -2009,7 +2023,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                                free_xid(xid);
                                return ERR_PTR(rc);
                        }
+                       spin_lock(&ses->chan_lock);
                }
+               spin_unlock(&ses->chan_lock);
                mutex_unlock(&ses->session_mutex);
 
                /* existing SMB ses has a server reference already */
@@ -2067,6 +2083,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
        ses->chans[0].server = server;
        ses->chan_count = 1;
        ses->chan_max = ctx->multichannel ? ctx->max_channels:1;
+       ses->chans_need_reconnect = 1;
        spin_unlock(&ses->chan_lock);
 
        rc = cifs_negotiate_protocol(xid, ses);
@@ -2081,7 +2098,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
        if (rc)
                goto get_ses_fail;
 
-       /* success, put it on the list and add it as first channel */
+       /*
+        * success, put it on the list and add it as first channel
+        * note: the session becomes active soon after this. So you'll
+        * need to lock before changing something in the session.
+        */
        spin_lock(&cifs_tcp_ses_lock);
        list_add(&ses->smb_ses_list, &server->smb_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
@@ -2161,6 +2182,9 @@ cifs_put_tcon(struct cifs_tcon *tcon)
        /* tc_count can never go negative */
        WARN_ON(tcon->tc_count < 0);
 
+       list_del_init(&tcon->tcon_list);
+       spin_unlock(&cifs_tcp_ses_lock);
+
        if (tcon->use_witness) {
                int rc;
 
@@ -2171,9 +2195,6 @@ cifs_put_tcon(struct cifs_tcon *tcon)
                }
        }
 
-       list_del_init(&tcon->tcon_list);
-       spin_unlock(&cifs_tcp_ses_lock);
-
        xid = get_xid();
        if (ses->server->ops->tree_disconnect)
                ses->server->ops->tree_disconnect(xid, tcon);
index 035dc3e245dca9f569afeceed54f8d0212f62968..ba8543ccb29892a8019d79b46eb143c1f827f2af 100644 (file)
@@ -65,6 +65,53 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
        return false;
 }
 
+unsigned int
+cifs_ses_get_chan_index(struct cifs_ses *ses,
+                       struct TCP_Server_Info *server)
+{
+       unsigned int i;
+
+       for (i = 0; i < ses->chan_count; i++) {
+               if (ses->chans[i].server == server)
+                       return i;
+       }
+
+       /* If we didn't find the channel, it is likely a bug */
+       WARN_ON(1);
+       return 0;
+}
+
+void
+cifs_chan_set_need_reconnect(struct cifs_ses *ses,
+                            struct TCP_Server_Info *server)
+{
+       unsigned int chan_index = cifs_ses_get_chan_index(ses, server);
+
+       set_bit(chan_index, &ses->chans_need_reconnect);
+       cifs_dbg(FYI, "Set reconnect bitmask for chan %u; now 0x%lx\n",
+                chan_index, ses->chans_need_reconnect);
+}
+
+void
+cifs_chan_clear_need_reconnect(struct cifs_ses *ses,
+                              struct TCP_Server_Info *server)
+{
+       unsigned int chan_index = cifs_ses_get_chan_index(ses, server);
+
+       clear_bit(chan_index, &ses->chans_need_reconnect);
+       cifs_dbg(FYI, "Cleared reconnect bitmask for chan %u; now 0x%lx\n",
+                chan_index, ses->chans_need_reconnect);
+}
+
+bool
+cifs_chan_needs_reconnect(struct cifs_ses *ses,
+                         struct TCP_Server_Info *server)
+{
+       unsigned int chan_index = cifs_ses_get_chan_index(ses, server);
+
+       return CIFS_CHAN_NEEDS_RECONNECT(ses, chan_index);
+}
+
 /* returns number of channels added */
 int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
 {
@@ -306,11 +353,21 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
        spin_lock(&ses->chan_lock);
        ses->chan_count++;
        atomic_set(&ses->chan_seq, 0);
+
+       /* Mark this channel as needing connect/setup */
+       cifs_chan_set_need_reconnect(ses, chan->server);
        spin_unlock(&ses->chan_lock);
 
 out:
        ses->binding = false;
        ses->binding_chan = NULL;
+
+       if (rc && chan->server) {
+               /* we rely on all bits beyond chan_count to be clear */
+               cifs_chan_clear_need_reconnect(ses, chan->server);
+               ses->chan_count--;
+       }
+
        mutex_unlock(&ses->session_mutex);
 
        if (rc && chan->server)
@@ -998,9 +1055,15 @@ sess_establish_session(struct sess_data *sess_data)
        mutex_unlock(&ses->server->srv_mutex);
 
        cifs_dbg(FYI, "CIFS session established successfully\n");
+       if (ses->binding)
+               cifs_chan_clear_need_reconnect(ses, ses->binding_chan->server);
+       else
+               cifs_chan_clear_need_reconnect(ses, ses->server);
+
+       /* keep existing ses state if binding */
        spin_lock(&GlobalMid_Lock);
-       ses->status = CifsGood;
-       ses->need_reconnect = false;
+       if (!ses->binding)
+               ses->status = CifsGood;
        spin_unlock(&GlobalMid_Lock);
 
        return 0;
index 8b3670388cdaf682e23635b4336d4d5cdfce446c..f3b2eef36ec150d7a5d631939944f89f80412899 100644 (file)
@@ -232,8 +232,15 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                retries = server->nr_targets;
        }
 
-       if (!tcon->ses->need_reconnect && !tcon->need_reconnect)
+       spin_lock(&ses->chan_lock);
+       if (!cifs_chan_needs_reconnect(ses, server) && !tcon->need_reconnect) {
+               spin_unlock(&ses->chan_lock);
                return 0;
+       }
+       cifs_dbg(FYI, "sess reconnect mask: 0x%lx, tcon reconnect: %d",
+                tcon->ses->chans_need_reconnect,
+                tcon->need_reconnect);
+       spin_unlock(&ses->chan_lock);
 
        nls_codepage = load_nls_default();
 
@@ -262,8 +269,26 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                ses->binding_chan = cifs_ses_find_chan(ses, server);
        }
 
+       /*
+        * need to prevent multiple threads trying to simultaneously
+        * reconnect the same SMB session
+        */
+       spin_lock(&ses->chan_lock);
+       if (!cifs_chan_needs_reconnect(ses, server)) {
+               spin_unlock(&ses->chan_lock);
+
+               /* this just means that we only need to tcon */
+               if (tcon->need_reconnect)
+                       goto skip_sess_setup;
+
+               rc = -EHOSTDOWN;
+               mutex_unlock(&ses->session_mutex);
+               goto out;
+       }
+       spin_unlock(&ses->chan_lock);
+
        rc = cifs_negotiate_protocol(0, tcon->ses);
-       if (!rc && tcon->ses->need_reconnect) {
+       if (!rc) {
                rc = cifs_setup_session(0, tcon->ses, nls_codepage);
                if ((rc == -EACCES) && !tcon->retry) {
                        rc = -EHOSTDOWN;
@@ -284,6 +309,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                goto out;
        }
 
+skip_sess_setup:
        cifs_mark_open_files_invalid(tcon);
        if (tcon->use_persistent)
                tcon->need_reopen_files = true;
@@ -1359,13 +1385,19 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
        mutex_unlock(&server->srv_mutex);
 
        cifs_dbg(FYI, "SMB2/3 session established successfully\n");
+
+       spin_lock(&ses->chan_lock);
+       if (ses->binding)
+               cifs_chan_clear_need_reconnect(ses, ses->binding_chan->server);
+       else
+               cifs_chan_clear_need_reconnect(ses, ses->server);
+       spin_unlock(&ses->chan_lock);
+
        /* keep existing ses state if binding */
-       if (!ses->binding) {
-               spin_lock(&GlobalMid_Lock);
+       spin_lock(&GlobalMid_Lock);
+       if (!ses->binding)
                ses->status = CifsGood;
-               ses->need_reconnect = false;
-               spin_unlock(&GlobalMid_Lock);
-       }
+       spin_unlock(&GlobalMid_Lock);
 
        return rc;
 }
@@ -1704,8 +1736,12 @@ SMB2_logoff(const unsigned int xid, struct cifs_ses *ses)
                return -EIO;
 
        /* no need to send SMB logoff if uid already closed due to reconnect */
-       if (ses->need_reconnect)
+       spin_lock(&ses->chan_lock);
+       if (CIFS_ALL_CHANS_NEED_RECONNECT(ses)) {
+               spin_unlock(&ses->chan_lock);
                goto smb2_session_already_dead;
+       }
+       spin_unlock(&ses->chan_lock);
 
        rc = smb2_plain_req_init(SMB2_LOGOFF, NULL, ses->server,
                                 (void **) &req, &total_len);
@@ -1913,8 +1949,13 @@ SMB2_tdis(const unsigned int xid, struct cifs_tcon *tcon)
        if (!ses || !(ses->server))
                return -EIO;
 
-       if ((tcon->need_reconnect) || (tcon->ses->need_reconnect))
+       spin_lock(&ses->chan_lock);
+       if ((tcon->need_reconnect) ||
+           (CIFS_ALL_CHANS_NEED_RECONNECT(tcon->ses))) {
+               spin_unlock(&ses->chan_lock);
                return 0;
+       }
+       spin_unlock(&ses->chan_lock);
 
        close_cached_dir_lease(&tcon->crfid);