OSDN Git Service

Merge tag '5.17-rc-part1-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 17 Jan 2022 07:53:21 +0000 (09:53 +0200)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 17 Jan 2022 07:53:21 +0000 (09:53 +0200)
Pull cifs updates from Steve French:

 - multichannel patches mostly related to improving reconnect behavior

 - minor cleanup patches

* tag '5.17-rc-part1-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: fix FILE_BOTH_DIRECTORY_INFO definition
  cifs: move superblock magic defitions to magic.h
  cifs: Fix smb311_update_preauth_hash() kernel-doc comment
  cifs: avoid race during socket reconnect between send and recv
  cifs: maintain a state machine for tcp/smb/tcon sessions
  cifs: fix hang on cifs_get_next_mid()
  cifs: take cifs_tcp_ses_lock for status checks
  cifs: reconnect only the connection and not smb session where possible
  cifs: add WARN_ON for when chan_count goes below minimum
  cifs: adjust DebugData to use chans_need_reconnect for conn status
  cifs: use the chans_need_reconnect bitmap for reconnect status
  cifs: track individual channel status using chans_need_reconnect
  cifs: remove redundant assignment to pointer p

23 files changed:
fs/cifs/cifs_debug.c
fs/cifs/cifs_spnego.c
fs/cifs/cifs_spnego.h
fs/cifs/cifs_swn.c
fs/cifs/cifsencrypt.c
fs/cifs/cifsfs.c
fs/cifs/cifsglob.h
fs/cifs/cifspdu.h
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/connect.c
fs/cifs/netmisc.c
fs/cifs/ntlmssp.h
fs/cifs/sess.c
fs/cifs/smb1ops.c
fs/cifs/smb2glob.h
fs/cifs/smb2misc.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2proto.h
fs/cifs/smb2transport.c
fs/cifs/transport.c
include/uapi/linux/magic.h

index d282caf..ea00e1a 100644 (file)
@@ -416,11 +416,17 @@ skip_rdma:
                                   from_kuid(&init_user_ns, ses->cred_uid));
 
                        spin_lock(&ses->chan_lock);
+                       if (CIFS_CHAN_NEEDS_RECONNECT(ses, 0))
+                               seq_puts(m, "\tPrimary channel: DISCONNECTED ");
+
                        if (ses->chan_count > 1) {
                                seq_printf(m, "\n\n\tExtra Channels: %zu ",
                                           ses->chan_count-1);
-                               for (j = 1; j < ses->chan_count; j++)
+                               for (j = 1; j < ses->chan_count; j++) {
                                        cifs_dump_channel(m, j, &ses->chans[j]);
+                                       if (CIFS_CHAN_NEEDS_RECONNECT(ses, j))
+                                               seq_puts(m, "\tDISCONNECTED ");
+                               }
                        }
                        spin_unlock(&ses->chan_lock);
 
index 353bd0d..342717b 100644 (file)
@@ -84,9 +84,9 @@ struct key_type cifs_spnego_key_type = {
 
 /* get a key struct with a SPNEGO security blob, suitable for session setup */
 struct key *
-cifs_get_spnego_key(struct cifs_ses *sesInfo)
+cifs_get_spnego_key(struct cifs_ses *sesInfo,
+                   struct TCP_Server_Info *server)
 {
-       struct TCP_Server_Info *server = cifs_ses_server(sesInfo);
        struct sockaddr_in *sa = (struct sockaddr_in *) &server->dstaddr;
        struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *) &server->dstaddr;
        char *description, *dp;
index e6a0451..7f102ff 100644 (file)
@@ -29,7 +29,8 @@ struct cifs_spnego_msg {
 
 #ifdef __KERNEL__
 extern struct key_type cifs_spnego_key_type;
-extern struct key *cifs_get_spnego_key(struct cifs_ses *sesInfo);
+extern struct key *cifs_get_spnego_key(struct cifs_ses *sesInfo,
+                                      struct TCP_Server_Info *server);
 #endif /* KERNEL */
 
 #endif /* _CIFS_SPNEGO_H */
index 23a1ed2..8f386dd 100644 (file)
@@ -498,10 +498,10 @@ static int cifs_swn_reconnect(struct cifs_tcon *tcon, struct sockaddr_storage *a
                goto unlock;
        }
 
-       spin_lock(&GlobalMid_Lock);
+       spin_lock(&cifs_tcp_ses_lock);
        if (tcon->ses->server->tcpStatus != CifsExiting)
                tcon->ses->server->tcpStatus = CifsNeedReconnect;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
 
 unlock:
        mutex_unlock(&tcon->ses->server->srv_mutex);
index d118282..0912d8b 100644 (file)
@@ -141,9 +141,13 @@ int cifs_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server,
        if ((cifs_pdu == NULL) || (server == NULL))
                return -EINVAL;
 
+       spin_lock(&cifs_tcp_ses_lock);
        if (!(cifs_pdu->Flags2 & SMBFLG2_SECURITY_SIGNATURE) ||
-           server->tcpStatus == CifsNeedNegotiate)
+           server->tcpStatus == CifsNeedNegotiate) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return rc;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        if (!server->session_estab) {
                memcpy(cifs_pdu->Signature.SecuritySignature, "BSRSPYL", 8);
index dca42aa..36b2e0c 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/random.h>
 #include <linux/uuid.h>
 #include <linux/xattr.h>
+#include <uapi/linux/magic.h>
 #include <net/ipv6.h>
 #include "cifsfs.h"
 #include "cifspdu.h"
@@ -202,7 +203,7 @@ cifs_read_super(struct super_block *sb)
                sb->s_time_max = ts.tv_sec;
        }
 
-       sb->s_magic = CIFS_MAGIC_NUMBER;
+       sb->s_magic = CIFS_SUPER_MAGIC;
        sb->s_op = &cifs_super_ops;
        sb->s_xattr = cifs_xattr_handlers;
        rc = super_setup_bdi(sb);
@@ -773,7 +774,7 @@ cifs_get_root(struct smb3_fs_context *ctx, struct super_block *sb)
 
        sep = CIFS_DIR_SEP(cifs_sb);
        dentry = dget(sb->s_root);
-       p = s = full_path;
+       s = full_path;
 
        do {
                struct inode *dir = d_inode(dentry);
index be74606..f84978b 100644 (file)
@@ -24,8 +24,6 @@
 #include "../smbfs_common/smb2pdu.h"
 #include "smb2pdu.h"
 
-#define CIFS_MAGIC_NUMBER 0xFF534D42      /* the first four bytes of SMB PDUs */
-
 #define SMB_PATH_MAX 260
 #define CIFS_PORT 445
 #define RFC1001_PORT 139
@@ -113,7 +111,13 @@ enum statusEnum {
        CifsGood,
        CifsExiting,
        CifsNeedReconnect,
-       CifsNeedNegotiate
+       CifsNeedNegotiate,
+       CifsInNegotiate,
+       CifsNeedSessSetup,
+       CifsInSessSetup,
+       CifsNeedTcon,
+       CifsInTcon,
+       CifsInFilesInvalidate
 };
 
 enum securityEnum {
@@ -263,13 +267,16 @@ struct smb_version_operations {
        /* check if we need to negotiate */
        bool (*need_neg)(struct TCP_Server_Info *);
        /* negotiate to the server */
-       int (*negotiate)(const unsigned int, struct cifs_ses *);
+       int (*negotiate)(const unsigned int xid,
+                        struct cifs_ses *ses,
+                        struct TCP_Server_Info *server);
        /* set negotiated write size */
        unsigned int (*negotiate_wsize)(struct cifs_tcon *tcon, struct smb3_fs_context *ctx);
        /* set negotiated read size */
        unsigned int (*negotiate_rsize)(struct cifs_tcon *tcon, struct smb3_fs_context *ctx);
        /* setup smb sessionn */
        int (*sess_setup)(const unsigned int, struct cifs_ses *,
+                         struct TCP_Server_Info *server,
                          const struct nls_table *);
        /* close smb session */
        int (*logoff)(const unsigned int, struct cifs_ses *);
@@ -414,7 +421,8 @@ struct smb_version_operations {
        void (*set_lease_key)(struct inode *, struct cifs_fid *);
        /* generate new lease key */
        void (*new_lease_key)(struct cifs_fid *);
-       int (*generate_signingkey)(struct cifs_ses *);
+       int (*generate_signingkey)(struct cifs_ses *ses,
+                                  struct TCP_Server_Info *server);
        int (*calc_signature)(struct smb_rqst *, struct TCP_Server_Info *,
                                bool allocate_crypto);
        int (*set_integrity)(const unsigned int, struct cifs_tcon *tcon,
@@ -582,7 +590,7 @@ struct TCP_Server_Info {
        char server_RFC1001_name[RFC1001_NAME_LEN_WITH_NULL];
        struct smb_version_operations   *ops;
        struct smb_version_values       *vals;
-       /* updates to tcpStatus protected by GlobalMid_Lock */
+       /* updates to tcpStatus protected by cifs_tcp_ses_lock */
        enum statusEnum tcpStatus; /* what we think the status is */
        char *hostname; /* hostname portion of UNC string */
        struct socket *ssocket;
@@ -920,7 +928,7 @@ struct cifs_ses {
        struct mutex session_mutex;
        struct TCP_Server_Info *server; /* pointer to server info */
        int ses_count;          /* reference counter */
-       enum statusEnum status;  /* updates protected by GlobalMid_Lock */
+       enum statusEnum status;  /* updates protected by cifs_tcp_ses_lock */
        unsigned overrideSecFlg;  /* if non-zero override global sec flags */
        char *serverOS;         /* name of operating system underlying server */
        char *serverNOS;        /* name of network operating system of server */
@@ -939,17 +947,13 @@ 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;
        __u8 smb3signingkey[SMB3_SIGN_KEY_SIZE];
        __u8 smb3encryptionkey[SMB3_ENC_DEC_KEY_SIZE];
        __u8 smb3decryptionkey[SMB3_ENC_DEC_KEY_SIZE];
        __u8 preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE];
 
-       __u8 binding_preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE];
-
        /*
         * Network interfaces available on the server this session is
         * connected to.
@@ -969,45 +973,34 @@ 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_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)
+
        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.
+        *
+        * We will ask for sess and tcon reconnection only if all the
+        * channels are marked for needing reconnection. This will
+        * enable the sessions on top to continue to live till any
+        * of the channels below are active.
+        */
+       unsigned long chans_need_reconnect;
        /* ========= end: protected by chan_lock ======== */
 };
 
-/*
- * When binding a new channel, we need to access the channel which isn't fully
- * established yet.
- */
-
-static inline
-struct cifs_chan *cifs_ses_binding_channel(struct cifs_ses *ses)
-{
-       if (ses->binding)
-               return ses->binding_chan;
-       else
-               return NULL;
-}
-
-/*
- * Returns the server pointer of the session. When binding a new
- * channel this returns the last channel which isn't fully established
- * yet.
- *
- * This function should be use for negprot/sess.setup codepaths. For
- * the other requests see cifs_pick_channel().
- */
-static inline
-struct TCP_Server_Info *cifs_ses_server(struct cifs_ses *ses)
-{
-       if (ses->binding)
-               return ses->binding_chan->server;
-       else
-               return ses->server;
-}
-
 static inline bool
 cap_unix(struct cifs_ses *ses)
 {
index d2ff438..68b9a43 100644 (file)
@@ -2560,7 +2560,7 @@ typedef struct {
        __le32 EaSize; /* length of the xattrs */
        __u8   ShortNameLength;
        __u8   Reserved;
-       __u8   ShortName[12];
+       __u8   ShortName[24];
        char FileName[1];
 } __attribute__((packed)) FILE_BOTH_DIRECTORY_INFO; /* level 0x104 FFrsp data */
 
index 4f5a3e8..e0dc147 100644 (file)
@@ -131,7 +131,8 @@ extern int SendReceiveBlockingLock(const unsigned int xid,
                        struct smb_hdr *in_buf ,
                        struct smb_hdr *out_buf,
                        int *bytes_returned);
-extern int cifs_reconnect(struct TCP_Server_Info *server);
+extern int cifs_reconnect(struct TCP_Server_Info *server,
+                         bool mark_smb_session);
 extern int checkSMB(char *buf, unsigned int len, struct TCP_Server_Info *srvr);
 extern bool is_valid_oplock_break(char *, struct TCP_Server_Info *);
 extern bool backup_cred(struct cifs_sb_info *);
@@ -164,6 +165,7 @@ extern int small_smb_init_no_tc(const int smb_cmd, const int wct,
 extern enum securityEnum select_sectype(struct TCP_Server_Info *server,
                                enum securityEnum requested);
 extern int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
+                         struct TCP_Server_Info *server,
                          const struct nls_table *nls_cp);
 extern struct timespec64 cifs_NTtimeToUnix(__le64 utc_nanoseconds_since_1601);
 extern u64 cifs_UnixTimeToNT(struct timespec64);
@@ -293,11 +295,15 @@ extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon,
                             const struct nls_table *nlsc);
 
 extern int cifs_negotiate_protocol(const unsigned int xid,
-                                  struct cifs_ses *ses);
+                                  struct cifs_ses *ses,
+                                  struct TCP_Server_Info *server);
 extern int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
+                             struct TCP_Server_Info *server,
                              struct nls_table *nls_info);
 extern int cifs_enable_signing(struct TCP_Server_Info *server, bool mnt_sign_required);
-extern int CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses);
+extern int CIFSSMBNegotiate(const unsigned int xid,
+                           struct cifs_ses *ses,
+                           struct TCP_Server_Info *server);
 
 extern int CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
                    const char *tree, struct cifs_tcon *tcon,
@@ -504,8 +510,10 @@ extern int cifs_verify_signature(struct smb_rqst *rqst,
 extern int setup_ntlmv2_rsp(struct cifs_ses *, const struct nls_table *);
 extern void cifs_crypto_secmech_release(struct TCP_Server_Info *server);
 extern int calc_seckey(struct cifs_ses *);
-extern int generate_smb30signingkey(struct cifs_ses *);
-extern int generate_smb311signingkey(struct cifs_ses *);
+extern int generate_smb30signingkey(struct cifs_ses *ses,
+                                   struct TCP_Server_Info *server);
+extern int generate_smb311signingkey(struct cifs_ses *ses,
+                                    struct TCP_Server_Info *server);
 
 extern int CIFSSMBCopy(unsigned int xid,
                        struct cifs_tcon *source_tcon,
@@ -601,6 +609,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 243d176..071e2f2 100644 (file)
@@ -73,6 +73,16 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
        struct list_head *tmp;
        struct list_head *tmp1;
 
+       /* only send once per connect */
+       spin_lock(&cifs_tcp_ses_lock);
+       if (tcon->ses->status != CifsGood ||
+           tcon->tidStatus != CifsNeedReconnect) {
+               spin_unlock(&cifs_tcp_ses_lock);
+               return;
+       }
+       tcon->tidStatus = CifsInFilesInvalidate;
+       spin_unlock(&cifs_tcp_ses_lock);
+
        /* list all files open on tree connection and mark them invalid */
        spin_lock(&tcon->open_file_lock);
        list_for_each_safe(tmp, tmp1, &tcon->openFileList) {
@@ -89,6 +99,11 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
        memset(tcon->crfid.fid, 0, sizeof(struct cifs_fid));
        mutex_unlock(&tcon->crfid.fid_mutex);
 
+       spin_lock(&cifs_tcp_ses_lock);
+       if (tcon->tidStatus == CifsInFilesInvalidate)
+               tcon->tidStatus = CifsNeedTcon;
+       spin_unlock(&cifs_tcp_ses_lock);
+
        /*
         * BB Add call to invalidate_inodes(sb) for all superblocks mounted
         * to this tcon.
@@ -120,15 +135,18 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
         * only tree disconnect, open, and write, (and ulogoff which does not
         * have tcon) are allowed as we start force umount
         */
+       spin_lock(&cifs_tcp_ses_lock);
        if (tcon->tidStatus == CifsExiting) {
                if (smb_command != SMB_COM_WRITE_ANDX &&
                    smb_command != SMB_COM_OPEN_ANDX &&
                    smb_command != SMB_COM_TREE_DISCONNECT) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        cifs_dbg(FYI, "can not send cmd %d while umounting\n",
                                 smb_command);
                        return -ENODEV;
                }
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        retries = server->nr_targets;
 
@@ -148,8 +166,12 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
                }
 
                /* are we still trying to reconnect? */
-               if (server->tcpStatus != CifsNeedReconnect)
+               spin_lock(&cifs_tcp_ses_lock);
+               if (server->tcpStatus != CifsNeedReconnect) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        break;
+               }
+               spin_unlock(&cifs_tcp_ses_lock);
 
                if (retries && --retries)
                        continue;
@@ -166,31 +188,49 @@ 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();
 
        /*
-        * need to prevent multiple threads trying to simultaneously
-        * reconnect the same SMB session
-        */
-       mutex_lock(&ses->session_mutex);
-
-       /*
         * 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(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedReconnect) {
+               spin_unlock(&cifs_tcp_ses_lock);
                rc = -EHOSTDOWN;
-               mutex_unlock(&ses->session_mutex);
                goto out;
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
-       rc = cifs_negotiate_protocol(0, ses);
-       if (rc == 0 && ses->need_reconnect)
-               rc = cifs_setup_session(0, ses, nls_codepage);
+       /*
+        * 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 means that we only need to tree connect */
+               if (tcon->need_reconnect)
+                       goto skip_sess_setup;
+
+               rc = -EHOSTDOWN;
+               goto out;
+       }
+       spin_unlock(&ses->chan_lock);
+
+       mutex_lock(&ses->session_mutex);
+       rc = cifs_negotiate_protocol(0, ses, server);
+       if (!rc)
+               rc = cifs_setup_session(0, ses, server, nls_codepage);
 
        /* do we need to reconnect tcon? */
        if (rc || !tcon->need_reconnect) {
@@ -198,6 +238,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 +378,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);
 }
@@ -476,14 +522,15 @@ should_set_ext_sec_flag(enum securityEnum sectype)
 }
 
 int
-CIFSSMBNegotiate(const unsigned int xid, struct cifs_ses *ses)
+CIFSSMBNegotiate(const unsigned int xid,
+                struct cifs_ses *ses,
+                struct TCP_Server_Info *server)
 {
        NEGOTIATE_REQ *pSMB;
        NEGOTIATE_RSP *pSMBr;
        int rc = 0;
        int bytes_returned;
        int i;
-       struct TCP_Server_Info *server = ses->server;
        u16 count;
 
        if (!server) {
@@ -600,8 +647,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 +747,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);
@@ -1401,7 +1457,7 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
 
        if (server->ops->is_session_expired &&
            server->ops->is_session_expired(buf)) {
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                return -1;
        }
 
index 68ed29c..0f36def 100644 (file)
@@ -166,14 +166,17 @@ static void cifs_resolve_server(struct work_struct *work)
  * Mark all sessions and tcons for reconnect.
  *
  * @server needs to be previously set to CifsNeedReconnect.
+ *
  */
-static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server)
+static void
+cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server,
+                                     bool mark_smb_session)
 {
+       struct TCP_Server_Info *pserver;
        struct cifs_ses *ses;
        struct cifs_tcon *tcon;
        struct mid_q_entry *mid, *nmid;
        struct list_head retry_list;
-       struct TCP_Server_Info *pserver;
 
        server->maxBuf = 0;
        server->max_read = 0;
@@ -191,16 +194,37 @@ 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;
-               list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
+               spin_lock(&ses->chan_lock);
+               if (!mark_smb_session && 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 (!mark_smb_session && !CIFS_ALL_CHANS_NEED_RECONNECT(ses))
+                       goto next_session;
+
+               ses->status = CifsNeedReconnect;
+
+               list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
                        tcon->need_reconnect = true;
+                       tcon->tidStatus = CifsNeedReconnect;
+               }
                if (ses->tcon_ipc)
                        ses->tcon_ipc->need_reconnect = true;
+
+next_session:
+               spin_unlock(&ses->chan_lock);
        }
        spin_unlock(&cifs_tcp_ses_lock);
 
+       /*
+        * before reconnecting the tcp session, mark the smb session (uid)
+        * and the tid bad so they are not used until reconnected
+        */
+       cifs_dbg(FYI, "%s: marking sessions and tcons for reconnect and tearing down socket\n",
+                __func__);
        /* do not want to be sending data on a socket we are freeing */
-       cifs_dbg(FYI, "%s: tearing down socket\n", __func__);
        mutex_lock(&server->srv_mutex);
        if (server->ssocket) {
                cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", server->ssocket->state,
@@ -248,16 +272,16 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server
 
 static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets)
 {
-       spin_lock(&GlobalMid_Lock);
+       spin_lock(&cifs_tcp_ses_lock);
        server->nr_targets = num_targets;
        if (server->tcpStatus == CifsExiting) {
                /* the demux thread will exit normally next time through the loop */
-               spin_unlock(&GlobalMid_Lock);
+               spin_unlock(&cifs_tcp_ses_lock);
                wake_up(&server->response_q);
                return false;
        }
        server->tcpStatus = CifsNeedReconnect;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
        return true;
 }
 
@@ -268,15 +292,21 @@ static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num
  * mark all smb sessions as reconnecting for tcp session
  * reconnect tcp session
  * wake up waiters on reconnection? - (not needed currently)
+ *
+ * if mark_smb_session is passed as true, unconditionally mark
+ * the smb session (and tcon) for reconnect as well. This value
+ * doesn't really matter for non-multichannel scenario.
+ *
  */
-static int __cifs_reconnect(struct TCP_Server_Info *server)
+static int __cifs_reconnect(struct TCP_Server_Info *server,
+                           bool mark_smb_session)
 {
        int rc = 0;
 
        if (!cifs_tcp_ses_needs_reconnect(server, 1))
                return 0;
 
-       cifs_mark_tcp_ses_conns_for_reconnect(server);
+       cifs_mark_tcp_ses_conns_for_reconnect(server, mark_smb_session);
 
        do {
                try_to_freeze();
@@ -299,10 +329,10 @@ static int __cifs_reconnect(struct TCP_Server_Info *server)
                } else {
                        atomic_inc(&tcpSesReconnectCount);
                        set_credits(server, 1);
-                       spin_lock(&GlobalMid_Lock);
+                       spin_lock(&cifs_tcp_ses_lock);
                        if (server->tcpStatus != CifsExiting)
                                server->tcpStatus = CifsNeedNegotiate;
-                       spin_unlock(&GlobalMid_Lock);
+                       spin_unlock(&cifs_tcp_ses_lock);
                        cifs_swn_reset_server_dstaddr(server);
                        mutex_unlock(&server->srv_mutex);
                }
@@ -371,7 +401,9 @@ static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_
        return rc;
 }
 
-static int reconnect_dfs_server(struct TCP_Server_Info *server)
+static int
+reconnect_dfs_server(struct TCP_Server_Info *server,
+                    bool mark_smb_session)
 {
        int rc = 0;
        const char *refpath = server->current_fullpath + 1;
@@ -395,7 +427,7 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
        if (!cifs_tcp_ses_needs_reconnect(server, num_targets))
                return 0;
 
-       cifs_mark_tcp_ses_conns_for_reconnect(server);
+       cifs_mark_tcp_ses_conns_for_reconnect(server, mark_smb_session);
 
        do {
                try_to_freeze();
@@ -416,10 +448,10 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
                 */
                atomic_inc(&tcpSesReconnectCount);
                set_credits(server, 1);
-               spin_lock(&GlobalMid_Lock);
+               spin_lock(&cifs_tcp_ses_lock);
                if (server->tcpStatus != CifsExiting)
                        server->tcpStatus = CifsNeedNegotiate;
-               spin_unlock(&GlobalMid_Lock);
+               spin_unlock(&cifs_tcp_ses_lock);
                cifs_swn_reset_server_dstaddr(server);
                mutex_unlock(&server->srv_mutex);
        } while (server->tcpStatus == CifsNeedReconnect);
@@ -430,29 +462,32 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server)
        dfs_cache_free_tgts(&tl);
 
        /* Need to set up echo worker again once connection has been established */
+       spin_lock(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedNegotiate)
                mod_delayed_work(cifsiod_wq, &server->echo, 0);
 
+       spin_unlock(&cifs_tcp_ses_lock);
+
        wake_up(&server->response_q);
        return rc;
 }
 
-int cifs_reconnect(struct TCP_Server_Info *server)
+int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
 {
        /* If tcp session is not an dfs connection, then reconnect to last target server */
        spin_lock(&cifs_tcp_ses_lock);
        if (!server->is_dfs_conn || !server->origin_fullpath || !server->leaf_fullpath) {
                spin_unlock(&cifs_tcp_ses_lock);
-               return __cifs_reconnect(server);
+               return __cifs_reconnect(server, mark_smb_session);
        }
        spin_unlock(&cifs_tcp_ses_lock);
 
-       return reconnect_dfs_server(server);
+       return reconnect_dfs_server(server, mark_smb_session);
 }
 #else
-int cifs_reconnect(struct TCP_Server_Info *server)
+int cifs_reconnect(struct TCP_Server_Info *server, bool mark_smb_session)
 {
-       return __cifs_reconnect(server);
+       return __cifs_reconnect(server, mark_smb_session);
 }
 #endif
 
@@ -534,15 +569,18 @@ server_unresponsive(struct TCP_Server_Info *server)
         * 65s kernel_recvmsg times out, and we see that we haven't gotten
         *     a response in >60s.
         */
+       spin_lock(&cifs_tcp_ses_lock);
        if ((server->tcpStatus == CifsGood ||
            server->tcpStatus == CifsNeedNegotiate) &&
            (!server->ops->can_echo || server->ops->can_echo(server)) &&
            time_after(jiffies, server->lstrp + 3 * server->echo_interval)) {
+               spin_unlock(&cifs_tcp_ses_lock);
                cifs_server_dbg(VFS, "has not responded in %lu seconds. Reconnecting...\n",
                         (3 * server->echo_interval) / HZ);
-               cifs_reconnect(server);
+               cifs_reconnect(server, false);
                return true;
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        return false;
 }
@@ -576,7 +614,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
 
                /* reconnect if no credits and no requests in flight */
                if (zero_credits(server)) {
-                       cifs_reconnect(server);
+                       cifs_reconnect(server, false);
                        return -ECONNABORTED;
                }
 
@@ -587,13 +625,18 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
                else
                        length = sock_recvmsg(server->ssocket, smb_msg, 0);
 
-               if (server->tcpStatus == CifsExiting)
+               spin_lock(&cifs_tcp_ses_lock);
+               if (server->tcpStatus == CifsExiting) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        return -ESHUTDOWN;
+               }
 
                if (server->tcpStatus == CifsNeedReconnect) {
-                       cifs_reconnect(server);
+                       spin_unlock(&cifs_tcp_ses_lock);
+                       cifs_reconnect(server, false);
                        return -ECONNABORTED;
                }
+               spin_unlock(&cifs_tcp_ses_lock);
 
                if (length == -ERESTARTSYS ||
                    length == -EAGAIN ||
@@ -610,7 +653,7 @@ cifs_readv_from_socket(struct TCP_Server_Info *server, struct msghdr *smb_msg)
 
                if (length <= 0) {
                        cifs_dbg(FYI, "Received no data or error: %d\n", length);
-                       cifs_reconnect(server);
+                       cifs_reconnect(server, false);
                        return -ECONNABORTED;
                }
        }
@@ -689,11 +732,11 @@ is_smb_response(struct TCP_Server_Info *server, unsigned char type)
                 * initialize frame).
                 */
                cifs_set_port((struct sockaddr *)&server->dstaddr, CIFS_PORT);
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                break;
        default:
                cifs_server_dbg(VFS, "RFC 1002 unknown response type 0x%x\n", type);
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
        }
 
        return false;
@@ -771,9 +814,9 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server)
        cancel_delayed_work_sync(&server->echo);
        cancel_delayed_work_sync(&server->resolve);
 
-       spin_lock(&GlobalMid_Lock);
+       spin_lock(&cifs_tcp_ses_lock);
        server->tcpStatus = CifsExiting;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
        wake_up_all(&server->response_q);
 
        /* check if we have blocked requests that need to free */
@@ -866,7 +909,7 @@ standard_receive3(struct TCP_Server_Info *server, struct mid_q_entry *mid)
        if (pdu_length > CIFSMaxBufSize + MAX_HEADER_SIZE(server) -
                server->vals->header_preamble_size) {
                cifs_server_dbg(VFS, "SMB response too long (%u bytes)\n", pdu_length);
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                return -ECONNABORTED;
        }
 
@@ -913,7 +956,7 @@ cifs_handle_standard(struct TCP_Server_Info *server, struct mid_q_entry *mid)
 
        if (server->ops->is_session_expired &&
            server->ops->is_session_expired(buf)) {
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                return -1;
        }
 
@@ -1017,7 +1060,7 @@ next_pdu:
                    server->vals->header_preamble_size) {
                        cifs_server_dbg(VFS, "SMB response too short (%u bytes)\n",
                                 server->pdu_size);
-                       cifs_reconnect(server);
+                       cifs_reconnect(server, true);
                        continue;
                }
 
@@ -1069,7 +1112,7 @@ next_pdu:
                    server->ops->is_status_io_timeout(buf)) {
                        num_io_timeout++;
                        if (num_io_timeout > NUM_STATUS_IO_TIMEOUT) {
-                               cifs_reconnect(server);
+                               cifs_reconnect(server, false);
                                num_io_timeout = 0;
                                continue;
                        }
@@ -1390,9 +1433,9 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
        else
                cancel_delayed_work_sync(&server->reconnect);
 
-       spin_lock(&GlobalMid_Lock);
+       spin_lock(&cifs_tcp_ses_lock);
        server->tcpStatus = CifsExiting;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
 
        cifs_crypto_secmech_release(server);
 
@@ -1545,7 +1588,9 @@ smbd_connected:
         * to the struct since the kernel thread not created yet
         * no need to spinlock this update of tcpStatus
         */
+       spin_lock(&cifs_tcp_ses_lock);
        tcp_ses->tcpStatus = CifsNeedNegotiate;
+       spin_unlock(&cifs_tcp_ses_lock);
 
        if ((ctx->max_credits < 20) || (ctx->max_credits > 60000))
                tcp_ses->max_credits = SMB2_MAX_CREDITS_AVAILABLE;
@@ -1762,15 +1807,13 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
                spin_unlock(&cifs_tcp_ses_lock);
                return;
        }
-       spin_unlock(&cifs_tcp_ses_lock);
 
        /* ses_count can never go negative */
        WARN_ON(ses->ses_count < 0);
 
-       spin_lock(&GlobalMid_Lock);
        if (ses->status == CifsGood)
                ses->status = CifsExiting;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
 
        cifs_free_ipc(ses);
 
@@ -1987,11 +2030,13 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                cifs_dbg(FYI, "Existing smb sess found (status=%d)\n",
                         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);
+                       mutex_lock(&ses->session_mutex);
+                       rc = cifs_negotiate_protocol(xid, ses, server);
                        if (rc) {
                                mutex_unlock(&ses->session_mutex);
                                /* problem -- put our ses reference */
@@ -2000,7 +2045,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                                return ERR_PTR(rc);
                        }
 
-                       rc = cifs_setup_session(xid, ses,
+                       rc = cifs_setup_session(xid, ses, server,
                                                ctx->local_nls);
                        if (rc) {
                                mutex_unlock(&ses->session_mutex);
@@ -2009,8 +2054,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                                free_xid(xid);
                                return ERR_PTR(rc);
                        }
+                       mutex_unlock(&ses->session_mutex);
+
+                       spin_lock(&ses->chan_lock);
                }
-               mutex_unlock(&ses->session_mutex);
+               spin_unlock(&ses->chan_lock);
 
                /* existing SMB ses has a server reference already */
                cifs_put_tcp_session(server, 0);
@@ -2060,28 +2108,33 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 
        ses->sectype = ctx->sectype;
        ses->sign = ctx->sign;
-       mutex_lock(&ses->session_mutex);
 
        /* add server as first channel */
        spin_lock(&ses->chan_lock);
        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);
+       mutex_lock(&ses->session_mutex);
+       rc = cifs_negotiate_protocol(xid, ses, server);
        if (!rc)
-               rc = cifs_setup_session(xid, ses, ctx->local_nls);
+               rc = cifs_setup_session(xid, ses, server, ctx->local_nls);
+       mutex_unlock(&ses->session_mutex);
 
        /* each channel uses a different signing key */
        memcpy(ses->chans[0].signkey, ses->smb3signingkey,
               sizeof(ses->smb3signingkey));
 
-       mutex_unlock(&ses->session_mutex);
        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 +2214,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 +2227,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);
@@ -2290,10 +2343,6 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
                }
        }
 
-       /*
-        * BB Do we need to wrap session_mutex around this TCon call and Unix
-        * SetFS as we do on SessSetup and reconnect?
-        */
        xid = get_xid();
        rc = ses->server->ops->tree_connect(xid, ses, ctx->UNC, tcon,
                                            ctx->local_nls);
@@ -3029,12 +3078,15 @@ static int mount_get_conns(struct mount_ctx *mnt_ctx)
                 * for just this mount.
                 */
                reset_cifs_unix_caps(xid, tcon, cifs_sb, ctx);
+               spin_lock(&cifs_tcp_ses_lock);
                if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) &&
                    (le64_to_cpu(tcon->fsUnixInfo.Capability) &
                     CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        rc = -EACCES;
                        goto out;
                }
+               spin_unlock(&cifs_tcp_ses_lock);
        } else
                tcon->unix_ext = 0; /* server does not support them */
 
@@ -3709,7 +3761,9 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
        if (rc == 0) {
                bool is_unicode;
 
+               spin_lock(&cifs_tcp_ses_lock);
                tcon->tidStatus = CifsGood;
+               spin_unlock(&cifs_tcp_ses_lock);
                tcon->need_reconnect = false;
                tcon->tid = smb_buffer_response->Tid;
                bcc_ptr = pByteArea(smb_buffer_response);
@@ -3799,26 +3853,32 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
 }
 
 int
-cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses)
+cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses,
+                       struct TCP_Server_Info *server)
 {
        int rc = 0;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
 
        if (!server->ops->need_neg || !server->ops->negotiate)
                return -ENOSYS;
 
        /* only send once per connect */
-       if (!server->ops->need_neg(server))
+       spin_lock(&cifs_tcp_ses_lock);
+       if (!server->ops->need_neg(server) ||
+           server->tcpStatus != CifsNeedNegotiate) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return 0;
+       }
+       server->tcpStatus = CifsInNegotiate;
+       spin_unlock(&cifs_tcp_ses_lock);
 
-       rc = server->ops->negotiate(xid, ses);
+       rc = server->ops->negotiate(xid, ses, server);
        if (rc == 0) {
-               spin_lock(&GlobalMid_Lock);
-               if (server->tcpStatus == CifsNeedNegotiate)
-                       server->tcpStatus = CifsGood;
+               spin_lock(&cifs_tcp_ses_lock);
+               if (server->tcpStatus == CifsInNegotiate)
+                       server->tcpStatus = CifsNeedSessSetup;
                else
                        rc = -EHOSTDOWN;
-               spin_unlock(&GlobalMid_Lock);
+               spin_unlock(&cifs_tcp_ses_lock);
        }
 
        return rc;
@@ -3826,12 +3886,26 @@ cifs_negotiate_protocol(const unsigned int xid, struct cifs_ses *ses)
 
 int
 cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
+                  struct TCP_Server_Info *server,
                   struct nls_table *nls_info)
 {
        int rc = -ENOSYS;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
+       bool is_binding = false;
 
-       if (!ses->binding) {
+       /* only send once per connect */
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus != CifsNeedSessSetup) {
+               spin_unlock(&cifs_tcp_ses_lock);
+               return 0;
+       }
+       ses->status = CifsInSessSetup;
+       spin_unlock(&cifs_tcp_ses_lock);
+
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       spin_unlock(&ses->chan_lock);
+
+       if (!is_binding) {
                ses->capabilities = server->capabilities;
                if (!linuxExtEnabled)
                        ses->capabilities &= (~server->vals->cap_unix);
@@ -3849,7 +3923,7 @@ cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
                 server->sec_mode, server->capabilities, server->timeAdj);
 
        if (server->ops->sess_setup)
-               rc = server->ops->sess_setup(xid, ses, nls_info);
+               rc = server->ops->sess_setup(xid, ses, server, nls_info);
 
        if (rc)
                cifs_server_dbg(VFS, "Send error in SessSetup = %d\n", rc);
@@ -4197,6 +4271,17 @@ static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *t
        struct dfs_cache_tgt_iterator *tit;
        bool target_match;
 
+       /* only send once per connect */
+       spin_lock(&cifs_tcp_ses_lock);
+       if (tcon->ses->status != CifsGood ||
+           (tcon->tidStatus != CifsNew &&
+           tcon->tidStatus != CifsNeedTcon)) {
+               spin_unlock(&cifs_tcp_ses_lock);
+               return 0;
+       }
+       tcon->tidStatus = CifsInTcon;
+       spin_unlock(&cifs_tcp_ses_lock);
+
        extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
 
        tit = dfs_cache_get_tgt_iterator(tl);
@@ -4355,6 +4440,17 @@ int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const stru
 {
        const struct smb_version_operations *ops = tcon->ses->server->ops;
 
+       /* only send once per connect */
+       spin_lock(&cifs_tcp_ses_lock);
+       if (tcon->ses->status != CifsGood ||
+           (tcon->tidStatus != CifsNew &&
+           tcon->tidStatus != CifsNeedTcon)) {
+               spin_unlock(&cifs_tcp_ses_lock);
+               return 0;
+       }
+       tcon->tidStatus = CifsInTcon;
+       spin_unlock(&cifs_tcp_ses_lock);
+
        return ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc);
 }
 #endif
index fa9fbd6..43b16b6 100644 (file)
@@ -896,10 +896,10 @@ map_and_check_smb_error(struct mid_q_entry *mid, bool logErr)
                if (class == ERRSRV && code == ERRbaduid) {
                        cifs_dbg(FYI, "Server returned 0x%x, reconnecting session...\n",
                                code);
-                       spin_lock(&GlobalMid_Lock);
+                       spin_lock(&cifs_tcp_ses_lock);
                        if (mid->server->tcpStatus != CifsExiting)
                                mid->server->tcpStatus = CifsNeedReconnect;
-                       spin_unlock(&GlobalMid_Lock);
+                       spin_unlock(&cifs_tcp_ses_lock);
                }
        }
 
index fe707f4..6d242af 100644 (file)
@@ -121,7 +121,9 @@ typedef struct _AUTHENTICATE_MESSAGE {
 int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, struct cifs_ses *ses);
 int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen,
                                 struct cifs_ses *ses,
+                                struct TCP_Server_Info *server,
                                 const struct nls_table *nls_cp);
 int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen,
                        struct cifs_ses *ses,
+                       struct TCP_Server_Info *server,
                        const struct nls_table *nls_cp);
index 035dc3e..d12490e 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)
 {
@@ -261,9 +308,8 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
 
        chan_server = cifs_get_tcp_session(&ctx, ses->server);
 
-       mutex_lock(&ses->session_mutex);
        spin_lock(&ses->chan_lock);
-       chan = ses->binding_chan = &ses->chans[ses->chan_count];
+       chan = &ses->chans[ses->chan_count];
        chan->server = chan_server;
        if (IS_ERR(chan->server)) {
                rc = PTR_ERR(chan->server);
@@ -271,8 +317,15 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
                spin_unlock(&ses->chan_lock);
                goto out;
        }
+       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);
 
+       mutex_lock(&ses->session_mutex);
        /*
         * We need to allocate the server crypto now as we will need
         * to sign packets before we generate the channel signing key
@@ -281,37 +334,29 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
        rc = smb311_crypto_shash_allocate(chan->server);
        if (rc) {
                cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__);
+               mutex_unlock(&ses->session_mutex);
                goto out;
        }
 
-       ses->binding = true;
-       rc = cifs_negotiate_protocol(xid, ses);
-       if (rc)
-               goto out;
-
-       rc = cifs_setup_session(xid, ses, cifs_sb->local_nls);
-       if (rc)
-               goto out;
-
-       /* success, put it on the list
-        * XXX: sharing ses between 2 tcp servers is not possible, the
-        * way "internal" linked lists works in linux makes element
-        * only able to belong to one list
-        *
-        * the binding session is already established so the rest of
-        * the code should be able to look it up, no need to add the
-        * ses to the new server.
-        */
+       rc = cifs_negotiate_protocol(xid, ses, chan->server);
+       if (!rc)
+               rc = cifs_setup_session(xid, ses, chan->server, cifs_sb->local_nls);
 
-       spin_lock(&ses->chan_lock);
-       ses->chan_count++;
-       atomic_set(&ses->chan_seq, 0);
-       spin_unlock(&ses->chan_lock);
+       mutex_unlock(&ses->session_mutex);
 
 out:
-       ses->binding = false;
-       ses->binding_chan = NULL;
-       mutex_unlock(&ses->session_mutex);
+       if (rc && chan->server) {
+               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--;
+               /*
+                * chan_count should never reach 0 as at least the primary
+                * channel is always allocated
+                */
+               WARN_ON(ses->chan_count < 1);
+               spin_unlock(&ses->chan_lock);
+       }
 
        if (rc && chan->server)
                cifs_put_tcp_session(chan->server, 0);
@@ -325,14 +370,16 @@ void cifs_ses_mark_for_reconnect(struct cifs_ses *ses)
        int i;
 
        for (i = 0; i < ses->chan_count; i++) {
-               spin_lock(&GlobalMid_Lock);
+               spin_lock(&cifs_tcp_ses_lock);
                if (ses->chans[i].server->tcpStatus != CifsExiting)
                        ses->chans[i].server->tcpStatus = CifsNeedReconnect;
-               spin_unlock(&GlobalMid_Lock);
+               spin_unlock(&cifs_tcp_ses_lock);
        }
 }
 
-static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB)
+static __u32 cifs_ssetup_hdr(struct cifs_ses *ses,
+                            struct TCP_Server_Info *server,
+                            SESSION_SETUP_ANDX *pSMB)
 {
        __u32 capabilities = 0;
 
@@ -345,7 +392,7 @@ static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB)
        pSMB->req.MaxBufferSize = cpu_to_le16(min_t(u32,
                                        CIFSMaxBufSize + MAX_CIFS_HDR_SIZE - 4,
                                        USHRT_MAX));
-       pSMB->req.MaxMpxCount = cpu_to_le16(ses->server->maxReq);
+       pSMB->req.MaxMpxCount = cpu_to_le16(server->maxReq);
        pSMB->req.VcNumber = cpu_to_le16(1);
 
        /* Now no need to set SMBFLG_CASELESS or obsolete CANONICAL PATH */
@@ -356,7 +403,7 @@ static __u32 cifs_ssetup_hdr(struct cifs_ses *ses, SESSION_SETUP_ANDX *pSMB)
        capabilities = CAP_LARGE_FILES | CAP_NT_SMBS | CAP_LEVEL_II_OPLOCKS |
                        CAP_LARGE_WRITE_X | CAP_LARGE_READ_X;
 
-       if (ses->server->sign)
+       if (server->sign)
                pSMB->req.hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE;
 
        if (ses->capabilities & CAP_UNICODE) {
@@ -719,10 +766,10 @@ static inline void cifs_security_buffer_from_str(SECURITY_BUFFER *pbuf,
 int build_ntlmssp_negotiate_blob(unsigned char **pbuffer,
                                 u16 *buflen,
                                 struct cifs_ses *ses,
+                                struct TCP_Server_Info *server,
                                 const struct nls_table *nls_cp)
 {
        int rc = 0;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
        NEGOTIATE_MESSAGE *sec_blob;
        __u32 flags;
        unsigned char *tmp;
@@ -776,6 +823,7 @@ setup_ntlm_neg_ret:
 int build_ntlmssp_auth_blob(unsigned char **pbuffer,
                                        u16 *buflen,
                                   struct cifs_ses *ses,
+                                  struct TCP_Server_Info *server,
                                   const struct nls_table *nls_cp)
 {
        int rc;
@@ -912,6 +960,7 @@ cifs_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested)
 struct sess_data {
        unsigned int xid;
        struct cifs_ses *ses;
+       struct TCP_Server_Info *server;
        struct nls_table *nls_cp;
        void (*func)(struct sess_data *);
        int result;
@@ -978,30 +1027,36 @@ static int
 sess_establish_session(struct sess_data *sess_data)
 {
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
 
-       mutex_lock(&ses->server->srv_mutex);
-       if (!ses->server->session_estab) {
-               if (ses->server->sign) {
-                       ses->server->session_key.response =
+       mutex_lock(&server->srv_mutex);
+       if (!server->session_estab) {
+               if (server->sign) {
+                       server->session_key.response =
                                kmemdup(ses->auth_key.response,
                                ses->auth_key.len, GFP_KERNEL);
-                       if (!ses->server->session_key.response) {
-                               mutex_unlock(&ses->server->srv_mutex);
+                       if (!server->session_key.response) {
+                               mutex_unlock(&server->srv_mutex);
                                return -ENOMEM;
                        }
-                       ses->server->session_key.len =
+                       server->session_key.len =
                                                ses->auth_key.len;
                }
-               ses->server->sequence_number = 0x2;
-               ses->server->session_estab = true;
+               server->sequence_number = 0x2;
+               server->session_estab = true;
        }
-       mutex_unlock(&ses->server->srv_mutex);
+       mutex_unlock(&server->srv_mutex);
 
        cifs_dbg(FYI, "CIFS session established successfully\n");
-       spin_lock(&GlobalMid_Lock);
+       spin_lock(&ses->chan_lock);
+       cifs_chan_clear_need_reconnect(ses, server);
+       spin_unlock(&ses->chan_lock);
+
+       /* Even if one channel is active, session is in good state */
+       spin_lock(&cifs_tcp_ses_lock);
+       server->tcpStatus = CifsGood;
        ses->status = CifsGood;
-       ses->need_reconnect = false;
-       spin_unlock(&GlobalMid_Lock);
+       spin_unlock(&cifs_tcp_ses_lock);
 
        return 0;
 }
@@ -1036,6 +1091,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data)
        SESSION_SETUP_ANDX *pSMB;
        char *bcc_ptr;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        __u32 capabilities;
        __u16 bytes_remaining;
 
@@ -1047,7 +1103,7 @@ sess_auth_ntlmv2(struct sess_data *sess_data)
 
        pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
        bcc_ptr = sess_data->iov[2].iov_base;
-       capabilities = cifs_ssetup_hdr(ses, pSMB);
+       capabilities = cifs_ssetup_hdr(ses, server, pSMB);
 
        pSMB->req_no_secext.Capabilities = cpu_to_le32(capabilities);
 
@@ -1145,6 +1201,7 @@ sess_auth_kerberos(struct sess_data *sess_data)
        SESSION_SETUP_ANDX *pSMB;
        char *bcc_ptr;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        __u32 capabilities;
        __u16 bytes_remaining;
        struct key *spnego_key = NULL;
@@ -1159,9 +1216,9 @@ sess_auth_kerberos(struct sess_data *sess_data)
 
        pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
        bcc_ptr = sess_data->iov[2].iov_base;
-       capabilities = cifs_ssetup_hdr(ses, pSMB);
+       capabilities = cifs_ssetup_hdr(ses, server, pSMB);
 
-       spnego_key = cifs_get_spnego_key(ses);
+       spnego_key = cifs_get_spnego_key(ses, server);
        if (IS_ERR(spnego_key)) {
                rc = PTR_ERR(spnego_key);
                spnego_key = NULL;
@@ -1285,12 +1342,13 @@ _sess_auth_rawntlmssp_assemble_req(struct sess_data *sess_data)
 {
        SESSION_SETUP_ANDX *pSMB;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        __u32 capabilities;
        char *bcc_ptr;
 
        pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
 
-       capabilities = cifs_ssetup_hdr(ses, pSMB);
+       capabilities = cifs_ssetup_hdr(ses, server, pSMB);
        if ((pSMB->req.hdr.Flags2 & SMBFLG2_UNICODE) == 0) {
                cifs_dbg(VFS, "NTLMSSP requires Unicode support\n");
                return -ENOSYS;
@@ -1324,6 +1382,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
        struct smb_hdr *smb_buf;
        SESSION_SETUP_ANDX *pSMB;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        __u16 bytes_remaining;
        char *bcc_ptr;
        unsigned char *ntlmsspblob = NULL;
@@ -1351,7 +1410,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
 
        /* Build security blob before we assemble the request */
        rc = build_ntlmssp_negotiate_blob(&ntlmsspblob,
-                                    &blob_len, ses,
+                                    &blob_len, ses, server,
                                     sess_data->nls_cp);
        if (rc)
                goto out;
@@ -1426,6 +1485,7 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data)
        struct smb_hdr *smb_buf;
        SESSION_SETUP_ANDX *pSMB;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        __u16 bytes_remaining;
        char *bcc_ptr;
        unsigned char *ntlmsspblob = NULL;
@@ -1442,7 +1502,8 @@ sess_auth_rawntlmssp_authenticate(struct sess_data *sess_data)
        pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
        smb_buf = (struct smb_hdr *)pSMB;
        rc = build_ntlmssp_auth_blob(&ntlmsspblob,
-                                       &blob_len, ses, sess_data->nls_cp);
+                                       &blob_len, ses, server,
+                                       sess_data->nls_cp);
        if (rc)
                goto out_free_ntlmsspblob;
        sess_data->iov[1].iov_len = blob_len;
@@ -1526,11 +1587,13 @@ out:
        sess_data->result = rc;
 }
 
-static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data)
+static int select_sec(struct sess_data *sess_data)
 {
        int type;
+       struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
 
-       type = cifs_select_sectype(ses->server, ses->sectype);
+       type = cifs_select_sectype(server, ses->sectype);
        cifs_dbg(FYI, "sess setup type %d\n", type);
        if (type == Unspecified) {
                cifs_dbg(VFS, "Unable to select appropriate authentication method!\n");
@@ -1561,7 +1624,8 @@ static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data)
 }
 
 int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
-                   const struct nls_table *nls_cp)
+                  struct TCP_Server_Info *server,
+                  const struct nls_table *nls_cp)
 {
        int rc = 0;
        struct sess_data *sess_data;
@@ -1575,15 +1639,16 @@ int CIFS_SessSetup(const unsigned int xid, struct cifs_ses *ses,
        if (!sess_data)
                return -ENOMEM;
 
-       rc = select_sec(ses, sess_data);
-       if (rc)
-               goto out;
-
        sess_data->xid = xid;
        sess_data->ses = ses;
+       sess_data->server = server;
        sess_data->buf0_type = CIFS_NO_BUFFER;
        sess_data->nls_cp = (struct nls_table *) nls_cp;
 
+       rc = select_sec(sess_data);
+       if (rc)
+               goto out;
+
        while (sess_data->func)
                sess_data->func(sess_data);
 
index 3b83839..8272c91 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <linux/pagemap.h>
 #include <linux/vfs.h>
+#include <uapi/linux/magic.h>
 #include "cifsglob.h"
 #include "cifsproto.h"
 #include "cifs_debug.h"
@@ -163,7 +164,7 @@ cifs_get_next_mid(struct TCP_Server_Info *server)
 {
        __u64 mid = 0;
        __u16 last_mid, cur_mid;
-       bool collision;
+       bool collision, reconnect = false;
 
        spin_lock(&GlobalMid_Lock);
 
@@ -215,7 +216,7 @@ cifs_get_next_mid(struct TCP_Server_Info *server)
                 * an eventual reconnect to clean out the pending_mid_q.
                 */
                if (num_mids > 32768)
-                       server->tcpStatus = CifsNeedReconnect;
+                       reconnect = true;
 
                if (!collision) {
                        mid = (__u64)cur_mid;
@@ -225,6 +226,13 @@ cifs_get_next_mid(struct TCP_Server_Info *server)
                cur_mid++;
        }
        spin_unlock(&GlobalMid_Lock);
+
+       if (reconnect) {
+               spin_lock(&cifs_tcp_ses_lock);
+               server->tcpStatus = CifsNeedReconnect;
+               spin_unlock(&cifs_tcp_ses_lock);
+       }
+
        return mid;
 }
 
@@ -414,14 +422,16 @@ cifs_need_neg(struct TCP_Server_Info *server)
 }
 
 static int
-cifs_negotiate(const unsigned int xid, struct cifs_ses *ses)
+cifs_negotiate(const unsigned int xid,
+              struct cifs_ses *ses,
+              struct TCP_Server_Info *server)
 {
        int rc;
-       rc = CIFSSMBNegotiate(xid, ses);
+       rc = CIFSSMBNegotiate(xid, ses, server);
        if (rc == -EAGAIN) {
                /* retry only once on 1st time connection */
-               set_credits(ses->server, 1);
-               rc = CIFSSMBNegotiate(xid, ses);
+               set_credits(server, 1);
+               rc = CIFSSMBNegotiate(xid, ses, server);
                if (rc == -EAGAIN)
                        rc = -EHOSTDOWN;
        }
@@ -878,7 +888,7 @@ cifs_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 {
        int rc = -EOPNOTSUPP;
 
-       buf->f_type = CIFS_MAGIC_NUMBER;
+       buf->f_type = CIFS_SUPER_MAGIC;
 
        /*
         * We could add a second check for a QFS Unix capability bit
index ca692b2..4125fd1 100644 (file)
@@ -13,8 +13,6 @@
 #ifndef _SMB2_GLOB_H
 #define _SMB2_GLOB_H
 
-#define SMB2_MAGIC_NUMBER 0xFE534D42
-
 /*
  *****************************************************************
  * Constants go here
index cdcdef3..b25623e 100644 (file)
@@ -847,16 +847,17 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve
  * SMB2 header.
  *
  * @ses:       server session structure
+ * @server:    pointer to server info
  * @iov:       array containing the SMB request we will send to the server
  * @nvec:      number of array entries for the iov
  */
 int
-smb311_update_preauth_hash(struct cifs_ses *ses, struct kvec *iov, int nvec)
+smb311_update_preauth_hash(struct cifs_ses *ses, struct TCP_Server_Info *server,
+                          struct kvec *iov, int nvec)
 {
        int i, rc;
        struct sdesc *d;
        struct smb2_hdr *hdr;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
 
        hdr = (struct smb2_hdr *)iov[0].iov_base;
        /* neg prot are always taken */
index c5b1dea..af5d083 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/sort.h>
 #include <crypto/aead.h>
 #include <linux/fiemap.h>
+#include <uapi/linux/magic.h>
 #include "cifsfs.h"
 #include "cifsglob.h"
 #include "smb2pdu.h"
@@ -121,9 +122,13 @@ smb2_add_credits(struct TCP_Server_Info *server,
                         optype, scredits, add);
        }
 
+       spin_lock(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedReconnect
-           || server->tcpStatus == CifsExiting)
+           || server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        switch (rc) {
        case -1:
@@ -208,11 +213,15 @@ smb2_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
                                return rc;
                        spin_lock(&server->req_lock);
                } else {
+                       spin_unlock(&server->req_lock);
+                       spin_lock(&cifs_tcp_ses_lock);
                        if (server->tcpStatus == CifsExiting) {
-                               spin_unlock(&server->req_lock);
+                               spin_unlock(&cifs_tcp_ses_lock);
                                return -ENOENT;
                        }
+                       spin_unlock(&cifs_tcp_ses_lock);
 
+                       spin_lock(&server->req_lock);
                        scredits = server->credits;
                        /* can deadlock with reopen */
                        if (scredits <= 8) {
@@ -384,14 +393,16 @@ smb2_need_neg(struct TCP_Server_Info *server)
 }
 
 static int
-smb2_negotiate(const unsigned int xid, struct cifs_ses *ses)
+smb2_negotiate(const unsigned int xid,
+              struct cifs_ses *ses,
+              struct TCP_Server_Info *server)
 {
        int rc;
 
        spin_lock(&GlobalMid_Lock);
-       cifs_ses_server(ses)->CurrentMid = 0;
+       server->CurrentMid = 0;
        spin_unlock(&GlobalMid_Lock);
-       rc = SMB2_negotiate(xid, ses);
+       rc = SMB2_negotiate(xid, ses, server);
        /* BB we probably don't need to retry with modern servers */
        if (rc == -EAGAIN)
                rc = -EHOSTDOWN;
@@ -2747,7 +2758,7 @@ smb2_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
                goto qfs_exit;
 
        rsp = (struct smb2_query_info_rsp *)rsp_iov.iov_base;
-       buf->f_type = SMB2_MAGIC_NUMBER;
+       buf->f_type = SMB2_SUPER_MAGIC;
        info = (struct smb2_fs_full_size_info *)(
                le16_to_cpu(rsp->OutputBufferOffset) + (char *)rsp);
        rc = smb2_validate_iov(le16_to_cpu(rsp->OutputBufferOffset),
@@ -2789,7 +2800,7 @@ smb311_queryfs(const unsigned int xid, struct cifs_tcon *tcon,
 
        rc = SMB311_posix_qfs_info(xid, tcon, fid.persistent_fid,
                                   fid.volatile_fid, buf);
-       buf->f_type = SMB2_MAGIC_NUMBER;
+       buf->f_type = SMB2_SUPER_MAGIC;
        SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
        return rc;
 }
@@ -4808,7 +4819,7 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
        if (server->ops->is_session_expired &&
            server->ops->is_session_expired(buf)) {
                if (!is_offloaded)
-                       cifs_reconnect(server);
+                       cifs_reconnect(server, true);
                return -1;
        }
 
@@ -4981,10 +4992,12 @@ static void smb2_decrypt_offload(struct work_struct *work)
 
                        mid->callback(mid);
                } else {
+                       spin_lock(&cifs_tcp_ses_lock);
                        spin_lock(&GlobalMid_Lock);
                        if (dw->server->tcpStatus == CifsNeedReconnect) {
                                mid->mid_state = MID_RETRY_NEEDED;
                                spin_unlock(&GlobalMid_Lock);
+                               spin_unlock(&cifs_tcp_ses_lock);
                                mid->callback(mid);
                        } else {
                                mid->mid_state = MID_REQUEST_SUBMITTED;
@@ -4992,6 +5005,7 @@ static void smb2_decrypt_offload(struct work_struct *work)
                                list_add_tail(&mid->qhead,
                                        &dw->server->pending_mid_q);
                                spin_unlock(&GlobalMid_Lock);
+                               spin_unlock(&cifs_tcp_ses_lock);
                        }
                }
                cifs_mid_q_entry_release(mid);
@@ -5221,13 +5235,13 @@ smb3_receive_transform(struct TCP_Server_Info *server,
                                                sizeof(struct smb2_hdr)) {
                cifs_server_dbg(VFS, "Transform message is too small (%u)\n",
                         pdu_length);
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                return -ECONNABORTED;
        }
 
        if (pdu_length < orig_len + sizeof(struct smb2_transform_hdr)) {
                cifs_server_dbg(VFS, "Transform message is broken\n");
-               cifs_reconnect(server);
+               cifs_reconnect(server, true);
                return -ECONNABORTED;
        }
 
index 8b36703..8d471df 100644 (file)
@@ -162,6 +162,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL)
                return 0;
 
+       spin_lock(&cifs_tcp_ses_lock);
        if (tcon->tidStatus == CifsExiting) {
                /*
                 * only tree disconnect, open, and write,
@@ -171,11 +172,13 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                if ((smb2_command != SMB2_WRITE) &&
                   (smb2_command != SMB2_CREATE) &&
                   (smb2_command != SMB2_TREE_DISCONNECT)) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        cifs_dbg(FYI, "can not send cmd %d while umounting\n",
                                 smb2_command);
                        return -ENODEV;
                }
        }
+       spin_unlock(&cifs_tcp_ses_lock);
        if ((!tcon->ses) || (tcon->ses->status == CifsExiting) ||
            (!tcon->ses->server) || !server)
                return -EIO;
@@ -214,8 +217,12 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
                }
 
                /* are we still trying to reconnect? */
-               if (server->tcpStatus != CifsNeedReconnect)
+               spin_lock(&cifs_tcp_ses_lock);
+               if (server->tcpStatus != CifsNeedReconnect) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        break;
+               }
+               spin_unlock(&cifs_tcp_ses_lock);
 
                if (retries && --retries)
                        continue;
@@ -232,64 +239,70 @@ 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();
 
        /*
-        * need to prevent multiple threads trying to simultaneously reconnect
-        * the same SMB session
-        */
-       mutex_lock(&tcon->ses->session_mutex);
-
-       /*
         * 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(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedReconnect) {
+               spin_unlock(&cifs_tcp_ses_lock);
                rc = -EHOSTDOWN;
-               mutex_unlock(&tcon->ses->session_mutex);
                goto out;
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        /*
-        * If we are reconnecting an extra channel, bind
+        * need to prevent multiple threads trying to simultaneously
+        * reconnect the same SMB session
         */
-       if (CIFS_SERVER_IS_CHAN(server)) {
-               ses->binding = true;
-               ses->binding_chan = cifs_ses_find_chan(ses, server);
+       spin_lock(&ses->chan_lock);
+       if (!cifs_chan_needs_reconnect(ses, server)) {
+               spin_unlock(&ses->chan_lock);
+
+               /* this means that we only need to tree connect */
+               if (tcon->need_reconnect)
+                       goto skip_sess_setup;
+
+               goto out;
        }
+       spin_unlock(&ses->chan_lock);
 
-       rc = cifs_negotiate_protocol(0, tcon->ses);
-       if (!rc && tcon->ses->need_reconnect) {
-               rc = cifs_setup_session(0, tcon->ses, nls_codepage);
+       mutex_lock(&ses->session_mutex);
+       rc = cifs_negotiate_protocol(0, ses, server);
+       if (!rc) {
+               rc = cifs_setup_session(0, ses, server, nls_codepage);
                if ((rc == -EACCES) && !tcon->retry) {
+                       mutex_unlock(&ses->session_mutex);
                        rc = -EHOSTDOWN;
-                       ses->binding = false;
-                       ses->binding_chan = NULL;
-                       mutex_unlock(&tcon->ses->session_mutex);
                        goto failed;
                }
        }
-       /*
-        * End of channel binding
-        */
-       ses->binding = false;
-       ses->binding_chan = NULL;
 
        if (rc || !tcon->need_reconnect) {
-               mutex_unlock(&tcon->ses->session_mutex);
+               mutex_unlock(&ses->session_mutex);
                goto out;
        }
 
+skip_sess_setup:
        cifs_mark_open_files_invalid(tcon);
        if (tcon->use_persistent)
                tcon->need_reopen_files = true;
 
        rc = cifs_tree_connect(0, tcon, nls_codepage);
-       mutex_unlock(&tcon->ses->session_mutex);
+       mutex_unlock(&ses->session_mutex);
 
        cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
        if (rc) {
@@ -833,7 +846,9 @@ add_posix_context(struct kvec *iov, unsigned int *num_iovec, umode_t mode)
  */
 
 int
-SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
+SMB2_negotiate(const unsigned int xid,
+              struct cifs_ses *ses,
+              struct TCP_Server_Info *server)
 {
        struct smb_rqst rqst;
        struct smb2_negotiate_req *req;
@@ -842,7 +857,6 @@ SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses)
        struct kvec rsp_iov;
        int rc = 0;
        int resp_buftype;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
        int blob_offset, blob_length;
        char *security_blob;
        int flags = CIFS_NEG_OP;
@@ -1221,6 +1235,7 @@ smb2_select_sectype(struct TCP_Server_Info *server, enum securityEnum requested)
 struct SMB2_sess_data {
        unsigned int xid;
        struct cifs_ses *ses;
+       struct TCP_Server_Info *server;
        struct nls_table *nls_cp;
        void (*func)(struct SMB2_sess_data *);
        int result;
@@ -1242,9 +1257,10 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
 {
        int rc;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        struct smb2_sess_setup_req *req;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
        unsigned int total_len;
+       bool is_binding = false;
 
        rc = smb2_plain_req_init(SMB2_SESSION_SETUP, NULL, server,
                                 (void **) &req,
@@ -1252,11 +1268,16 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
        if (rc)
                return rc;
 
-       if (sess_data->ses->binding) {
-               req->hdr.SessionId = cpu_to_le64(sess_data->ses->Suid);
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       spin_unlock(&ses->chan_lock);
+
+       if (is_binding) {
+               req->hdr.SessionId = cpu_to_le64(ses->Suid);
                req->hdr.Flags |= SMB2_FLAGS_SIGNED;
                req->PreviousSessionId = 0;
                req->Flags = SMB2_SESSION_REQ_FLAG_BINDING;
+               cifs_dbg(FYI, "Binding to sess id: %llx\n", ses->Suid);
        } else {
                /* First session, not a reauthenticate */
                req->hdr.SessionId = 0;
@@ -1266,6 +1287,8 @@ SMB2_sess_alloc_buffer(struct SMB2_sess_data *sess_data)
                 */
                req->PreviousSessionId = cpu_to_le64(sess_data->previous_session);
                req->Flags = 0; /* MBZ */
+               cifs_dbg(FYI, "Fresh session. Previous: %llx\n",
+                        sess_data->previous_session);
        }
 
        /* enough to enable echos and oplocks and one max size write */
@@ -1325,7 +1348,7 @@ SMB2_sess_sendreceive(struct SMB2_sess_data *sess_data)
 
        /* BB add code to build os and lm fields */
        rc = cifs_send_recv(sess_data->xid, sess_data->ses,
-                           cifs_ses_server(sess_data->ses),
+                           sess_data->server,
                            &rqst,
                            &sess_data->buf0_type,
                            CIFS_LOG_ERROR | CIFS_SESS_OP, &rsp_iov);
@@ -1340,11 +1363,11 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
 {
        int rc = 0;
        struct cifs_ses *ses = sess_data->ses;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
+       struct TCP_Server_Info *server = sess_data->server;
 
        mutex_lock(&server->srv_mutex);
        if (server->ops->generate_signingkey) {
-               rc = server->ops->generate_signingkey(ses);
+               rc = server->ops->generate_signingkey(ses, server);
                if (rc) {
                        cifs_dbg(FYI,
                                "SMB3 session key generation failed\n");
@@ -1359,13 +1382,16 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
        mutex_unlock(&server->srv_mutex);
 
        cifs_dbg(FYI, "SMB2/3 session established successfully\n");
-       /* keep existing ses state if binding */
-       if (!ses->binding) {
-               spin_lock(&GlobalMid_Lock);
-               ses->status = CifsGood;
-               ses->need_reconnect = false;
-               spin_unlock(&GlobalMid_Lock);
-       }
+
+       spin_lock(&ses->chan_lock);
+       cifs_chan_clear_need_reconnect(ses, server);
+       spin_unlock(&ses->chan_lock);
+
+       /* Even if one channel is active, session is in good state */
+       spin_lock(&cifs_tcp_ses_lock);
+       server->tcpStatus = CifsGood;
+       ses->status = CifsGood;
+       spin_unlock(&cifs_tcp_ses_lock);
 
        return rc;
 }
@@ -1376,15 +1402,17 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
 {
        int rc;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        struct cifs_spnego_msg *msg;
        struct key *spnego_key = NULL;
        struct smb2_sess_setup_rsp *rsp = NULL;
+       bool is_binding = false;
 
        rc = SMB2_sess_alloc_buffer(sess_data);
        if (rc)
                goto out;
 
-       spnego_key = cifs_get_spnego_key(ses);
+       spnego_key = cifs_get_spnego_key(ses, server);
        if (IS_ERR(spnego_key)) {
                rc = PTR_ERR(spnego_key);
                if (rc == -ENOKEY)
@@ -1405,8 +1433,12 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
                goto out_put_spnego_key;
        }
 
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       spin_unlock(&ses->chan_lock);
+
        /* keep session key if binding */
-       if (!ses->binding) {
+       if (!is_binding) {
                ses->auth_key.response = kmemdup(msg->data, msg->sesskey_len,
                                                 GFP_KERNEL);
                if (!ses->auth_key.response) {
@@ -1427,7 +1459,7 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
 
        rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
        /* keep session id and flags if binding */
-       if (!ses->binding) {
+       if (!is_binding) {
                ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
                ses->session_flags = le16_to_cpu(rsp->SessionFlags);
        }
@@ -1459,10 +1491,12 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
 {
        int rc;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        struct smb2_sess_setup_rsp *rsp = NULL;
        unsigned char *ntlmssp_blob = NULL;
        bool use_spnego = false; /* else use raw ntlmssp */
        u16 blob_length = 0;
+       bool is_binding = false;
 
        /*
         * If memory allocation is successful, caller of this function
@@ -1480,7 +1514,7 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
                goto out_err;
 
        rc = build_ntlmssp_negotiate_blob(&ntlmssp_blob,
-                                         &blob_length, ses,
+                                         &blob_length, ses, server,
                                          sess_data->nls_cp);
        if (rc)
                goto out_err;
@@ -1519,8 +1553,12 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
 
        cifs_dbg(FYI, "rawntlmssp session setup challenge phase\n");
 
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       spin_unlock(&ses->chan_lock);
+
        /* keep existing ses id and flags if binding */
-       if (!ses->binding) {
+       if (!is_binding) {
                ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
                ses->session_flags = le16_to_cpu(rsp->SessionFlags);
        }
@@ -1545,11 +1583,13 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data)
 {
        int rc;
        struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
        struct smb2_sess_setup_req *req;
        struct smb2_sess_setup_rsp *rsp = NULL;
        unsigned char *ntlmssp_blob = NULL;
        bool use_spnego = false; /* else use raw ntlmssp */
        u16 blob_length = 0;
+       bool is_binding = false;
 
        rc = SMB2_sess_alloc_buffer(sess_data);
        if (rc)
@@ -1558,8 +1598,9 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data)
        req = (struct smb2_sess_setup_req *) sess_data->iov[0].iov_base;
        req->hdr.SessionId = cpu_to_le64(ses->Suid);
 
-       rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length, ses,
-                                       sess_data->nls_cp);
+       rc = build_ntlmssp_auth_blob(&ntlmssp_blob, &blob_length,
+                                    ses, server,
+                                    sess_data->nls_cp);
        if (rc) {
                cifs_dbg(FYI, "build_ntlmssp_auth_blob failed %d\n", rc);
                goto out;
@@ -1580,8 +1621,12 @@ SMB2_sess_auth_rawntlmssp_authenticate(struct SMB2_sess_data *sess_data)
 
        rsp = (struct smb2_sess_setup_rsp *)sess_data->iov[0].iov_base;
 
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       spin_unlock(&ses->chan_lock);
+
        /* keep existing ses id and flags if binding */
-       if (!ses->binding) {
+       if (!is_binding) {
                ses->Suid = le64_to_cpu(rsp->hdr.SessionId);
                ses->session_flags = le16_to_cpu(rsp->SessionFlags);
        }
@@ -1612,11 +1657,13 @@ out:
 }
 
 static int
-SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data)
+SMB2_select_sec(struct SMB2_sess_data *sess_data)
 {
        int type;
+       struct cifs_ses *ses = sess_data->ses;
+       struct TCP_Server_Info *server = sess_data->server;
 
-       type = smb2_select_sectype(cifs_ses_server(ses), ses->sectype);
+       type = smb2_select_sectype(server, ses->sectype);
        cifs_dbg(FYI, "sess setup type %d\n", type);
        if (type == Unspecified) {
                cifs_dbg(VFS, "Unable to select appropriate authentication method!\n");
@@ -1640,10 +1687,10 @@ SMB2_select_sec(struct cifs_ses *ses, struct SMB2_sess_data *sess_data)
 
 int
 SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
+               struct TCP_Server_Info *server,
                const struct nls_table *nls_cp)
 {
        int rc = 0;
-       struct TCP_Server_Info *server = cifs_ses_server(ses);
        struct SMB2_sess_data *sess_data;
 
        cifs_dbg(FYI, "Session Setup\n");
@@ -1657,15 +1704,17 @@ SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
        if (!sess_data)
                return -ENOMEM;
 
-       rc = SMB2_select_sec(ses, sess_data);
-       if (rc)
-               goto out;
        sess_data->xid = xid;
        sess_data->ses = ses;
+       sess_data->server = server;
        sess_data->buf0_type = CIFS_NO_BUFFER;
        sess_data->nls_cp = (struct nls_table *) nls_cp;
        sess_data->previous_session = ses->Suid;
 
+       rc = SMB2_select_sec(sess_data);
+       if (rc)
+               goto out;
+
        /*
         * Initialize the session hash with the server one.
         */
@@ -1704,8 +1753,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);
@@ -1867,7 +1920,9 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
        tcon->share_flags = le32_to_cpu(rsp->ShareFlags);
        tcon->capabilities = rsp->Capabilities; /* we keep caps little endian */
        tcon->maximal_access = le32_to_cpu(rsp->MaximalAccess);
+       spin_lock(&cifs_tcp_ses_lock);
        tcon->tidStatus = CifsGood;
+       spin_unlock(&cifs_tcp_ses_lock);
        tcon->need_reconnect = false;
        tcon->tid = le32_to_cpu(rsp->hdr.Id.SyncId.TreeId);
        strlcpy(tcon->treeName, tree, sizeof(tcon->treeName));
@@ -1913,8 +1968,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);
 
@@ -3797,13 +3857,16 @@ SMB2_echo(struct TCP_Server_Info *server)
                                 .rq_nvec = 1 };
        unsigned int total_len;
 
-       cifs_dbg(FYI, "In echo request\n");
+       cifs_dbg(FYI, "In echo request for conn_id %lld\n", server->conn_id);
 
+       spin_lock(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedNegotiate) {
+               spin_unlock(&cifs_tcp_ses_lock);
                /* No need to send echo on newly established connections */
                mod_delayed_work(cifsiod_wq, &server->reconnect, 0);
                return rc;
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        rc = smb2_plain_req_init(SMB2_ECHO, NULL, server,
                                 (void **)&req, &total_len);
index 096fada..4a7062f 100644 (file)
@@ -123,8 +123,11 @@ extern void smb2_set_related(struct smb_rqst *rqst);
  * SMB2 Worker functions - most of protocol specific implementation details
  * are contained within these calls.
  */
-extern int SMB2_negotiate(const unsigned int xid, struct cifs_ses *ses);
+extern int SMB2_negotiate(const unsigned int xid,
+                         struct cifs_ses *ses,
+                         struct TCP_Server_Info *server);
 extern int SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
+                          struct TCP_Server_Info *server,
                           const struct nls_table *nls_cp);
 extern int SMB2_logoff(const unsigned int xid, struct cifs_ses *ses);
 extern int SMB2_tcon(const unsigned int xid, struct cifs_ses *ses,
@@ -276,6 +279,7 @@ extern void smb2_copy_fs_info_to_kstatfs(
         struct kstatfs *kst);
 extern int smb311_crypto_shash_allocate(struct TCP_Server_Info *server);
 extern int smb311_update_preauth_hash(struct cifs_ses *ses,
+                                     struct TCP_Server_Info *server,
                                      struct kvec *iov, int nvec);
 extern int smb2_query_info_compound(const unsigned int xid,
                                    struct cifs_tcon *tcon,
index 2bf047b..b70a49b 100644 (file)
@@ -100,7 +100,8 @@ int smb2_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key)
        goto out;
 
 found:
-       if (ses->binding) {
+       if (cifs_chan_needs_reconnect(ses, server) &&
+           !CIFS_ALL_CHANS_NEED_RECONNECT(ses)) {
                /*
                 * If we are in the process of binding a new channel
                 * to an existing session, use the master connection
@@ -390,12 +391,18 @@ struct derivation_triplet {
 
 static int
 generate_smb3signingkey(struct cifs_ses *ses,
+                       struct TCP_Server_Info *server,
                        const struct derivation_triplet *ptriplet)
 {
        int rc;
-#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS
-       struct TCP_Server_Info *server = ses->server;
-#endif
+       bool is_binding = false;
+       int chan_index = 0;
+
+       spin_lock(&ses->chan_lock);
+       is_binding = !CIFS_ALL_CHANS_NEED_RECONNECT(ses);
+       chan_index = cifs_ses_get_chan_index(ses, server);
+       /* TODO: introduce ref counting for channels when the can be freed */
+       spin_unlock(&ses->chan_lock);
 
        /*
         * All channels use the same encryption/decryption keys but
@@ -407,10 +414,10 @@ generate_smb3signingkey(struct cifs_ses *ses,
         * master connection signing key stored in the session
         */
 
-       if (ses->binding) {
+       if (is_binding) {
                rc = generate_key(ses, ptriplet->signing.label,
                                  ptriplet->signing.context,
-                                 cifs_ses_binding_channel(ses)->signkey,
+                                 ses->chans[chan_index].signkey,
                                  SMB3_SIGN_KEY_SIZE);
                if (rc)
                        return rc;
@@ -422,6 +429,7 @@ generate_smb3signingkey(struct cifs_ses *ses,
                if (rc)
                        return rc;
 
+               /* safe to access primary channel, since it will never go away */
                memcpy(ses->chans[0].signkey, ses->smb3signingkey,
                       SMB3_SIGN_KEY_SIZE);
 
@@ -470,7 +478,8 @@ generate_smb3signingkey(struct cifs_ses *ses,
 }
 
 int
-generate_smb30signingkey(struct cifs_ses *ses)
+generate_smb30signingkey(struct cifs_ses *ses,
+                        struct TCP_Server_Info *server)
 
 {
        struct derivation_triplet triplet;
@@ -494,11 +503,12 @@ generate_smb30signingkey(struct cifs_ses *ses)
        d->context.iov_base = "ServerOut";
        d->context.iov_len = 10;
 
-       return generate_smb3signingkey(ses, &triplet);
+       return generate_smb3signingkey(ses, server, &triplet);
 }
 
 int
-generate_smb311signingkey(struct cifs_ses *ses)
+generate_smb311signingkey(struct cifs_ses *ses,
+                         struct TCP_Server_Info *server)
 
 {
        struct derivation_triplet triplet;
@@ -522,7 +532,7 @@ generate_smb311signingkey(struct cifs_ses *ses)
        d->context.iov_base = ses->preauth_sha_hash;
        d->context.iov_len = 64;
 
-       return generate_smb3signingkey(ses, &triplet);
+       return generate_smb3signingkey(ses, server, &triplet);
 }
 
 int
@@ -624,8 +634,12 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
 
        if (!is_signed)
                return 0;
-       if (server->tcpStatus == CifsNeedNegotiate)
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus == CifsNeedNegotiate) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return 0;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
        if (!is_binding && !server->session_estab) {
                strncpy(shdr->Signature, "BSRSPYL", 8);
                return 0;
@@ -741,30 +755,41 @@ static int
 smb2_get_mid_entry(struct cifs_ses *ses, struct TCP_Server_Info *server,
                   struct smb2_hdr *shdr, struct mid_q_entry **mid)
 {
-       if (server->tcpStatus == CifsExiting)
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -ENOENT;
+       }
 
        if (server->tcpStatus == CifsNeedReconnect) {
+               spin_unlock(&cifs_tcp_ses_lock);
                cifs_dbg(FYI, "tcp session dead - return to caller to retry\n");
                return -EAGAIN;
        }
 
        if (server->tcpStatus == CifsNeedNegotiate &&
-          shdr->Command != SMB2_NEGOTIATE)
+          shdr->Command != SMB2_NEGOTIATE) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -EAGAIN;
+       }
 
        if (ses->status == CifsNew) {
                if ((shdr->Command != SMB2_SESSION_SETUP) &&
-                   (shdr->Command != SMB2_NEGOTIATE))
+                   (shdr->Command != SMB2_NEGOTIATE)) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        return -EAGAIN;
+               }
                /* else ok - we are setting up session */
        }
 
        if (ses->status == CifsExiting) {
-               if (shdr->Command != SMB2_LOGOFF)
+               if (shdr->Command != SMB2_LOGOFF) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        return -EAGAIN;
+               }
                /* else ok - we are shutting down the session */
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        *mid = smb2_mid_entry_alloc(shdr, server);
        if (*mid == NULL)
@@ -837,9 +862,13 @@ smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst)
                        (struct smb2_hdr *)rqst->rq_iov[0].iov_base;
        struct mid_q_entry *mid;
 
+       spin_lock(&cifs_tcp_ses_lock);
        if (server->tcpStatus == CifsNeedNegotiate &&
-          shdr->Command != SMB2_NEGOTIATE)
+          shdr->Command != SMB2_NEGOTIATE) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return ERR_PTR(-EAGAIN);
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        smb2_seq_num_into_buf(server, shdr);
 
index 61ea3d3..93f0e8c 100644 (file)
@@ -430,9 +430,9 @@ unmask:
                 * be taken as the remainder of this one. We need to kill the
                 * socket so the server throws away the partial SMB
                 */
-               spin_lock(&GlobalMid_Lock);
+               spin_lock(&cifs_tcp_ses_lock);
                server->tcpStatus = CifsNeedReconnect;
-               spin_unlock(&GlobalMid_Lock);
+               spin_unlock(&cifs_tcp_ses_lock);
                trace_smb3_partial_send_reconnect(server->CurrentMid,
                                                  server->conn_id, server->hostname);
        }
@@ -578,10 +578,14 @@ wait_for_free_credits(struct TCP_Server_Info *server, const int num_credits,
                                return -ERESTARTSYS;
                        spin_lock(&server->req_lock);
                } else {
+                       spin_unlock(&server->req_lock);
+
+                       spin_lock(&cifs_tcp_ses_lock);
                        if (server->tcpStatus == CifsExiting) {
-                               spin_unlock(&server->req_lock);
+                               spin_unlock(&cifs_tcp_ses_lock);
                                return -ENOENT;
                        }
+                       spin_unlock(&cifs_tcp_ses_lock);
 
                        /*
                         * For normal commands, reserve the last MAX_COMPOUND
@@ -596,6 +600,7 @@ wait_for_free_credits(struct TCP_Server_Info *server, const int num_credits,
                         * for servers that are slow to hand out credits on
                         * new sessions.
                         */
+                       spin_lock(&server->req_lock);
                        if (!optype && num_credits == 1 &&
                            server->in_flight > 2 * MAX_COMPOUND &&
                            *credits <= MAX_COMPOUND) {
@@ -723,28 +728,36 @@ cifs_wait_mtu_credits(struct TCP_Server_Info *server, unsigned int size,
 static int allocate_mid(struct cifs_ses *ses, struct smb_hdr *in_buf,
                        struct mid_q_entry **ppmidQ)
 {
+       spin_lock(&cifs_tcp_ses_lock);
        if (ses->server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -ENOENT;
        }
 
        if (ses->server->tcpStatus == CifsNeedReconnect) {
+               spin_unlock(&cifs_tcp_ses_lock);
                cifs_dbg(FYI, "tcp session dead - return to caller to retry\n");
                return -EAGAIN;
        }
 
        if (ses->status == CifsNew) {
                if ((in_buf->Command != SMB_COM_SESSION_SETUP_ANDX) &&
-                       (in_buf->Command != SMB_COM_NEGOTIATE))
+                       (in_buf->Command != SMB_COM_NEGOTIATE)) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        return -EAGAIN;
+               }
                /* else ok - we are setting up session */
        }
 
        if (ses->status == CifsExiting) {
                /* check if SMB session is bad because we are setting it up */
-               if (in_buf->Command != SMB_COM_LOGOFF_ANDX)
+               if (in_buf->Command != SMB_COM_LOGOFF_ANDX) {
+                       spin_unlock(&cifs_tcp_ses_lock);
                        return -EAGAIN;
+               }
                /* else ok - we are shutting down session */
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        *ppmidQ = AllocMidQEntry(in_buf, ses->server);
        if (*ppmidQ == NULL)
@@ -1044,19 +1057,11 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)
        if (!ses)
                return NULL;
 
-       spin_lock(&ses->chan_lock);
-       if (!ses->binding) {
-               /* round robin */
-               if (ses->chan_count > 1) {
-                       index = (uint)atomic_inc_return(&ses->chan_seq);
-                       index %= ses->chan_count;
-               }
-               spin_unlock(&ses->chan_lock);
-               return ses->chans[index].server;
-       } else {
-               spin_unlock(&ses->chan_lock);
-               return cifs_ses_server(ses);
-       }
+       /* round robin */
+       index = (uint)atomic_inc_return(&ses->chan_seq);
+       index %= ses->chan_count;
+
+       return ses->chans[index].server;
 }
 
 int
@@ -1084,8 +1089,12 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses,
                return -EIO;
        }
 
-       if (server->tcpStatus == CifsExiting)
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -ENOENT;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        /*
         * Wait for all the requests to become available.
@@ -1188,12 +1197,17 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses,
        /*
         * Compounding is never used during session establish.
         */
+       spin_lock(&cifs_tcp_ses_lock);
        if ((ses->status == CifsNew) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) {
+               spin_unlock(&cifs_tcp_ses_lock);
+
                mutex_lock(&server->srv_mutex);
-               smb311_update_preauth_hash(ses, rqst[0].rq_iov,
-                                          rqst[0].rq_nvec);
+               smb311_update_preauth_hash(ses, server, rqst[0].rq_iov, rqst[0].rq_nvec);
                mutex_unlock(&server->srv_mutex);
+
+               spin_lock(&cifs_tcp_ses_lock);
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        for (i = 0; i < num_rqst; i++) {
                rc = wait_for_response(server, midQ[i]);
@@ -1256,15 +1270,19 @@ compound_send_recv(const unsigned int xid, struct cifs_ses *ses,
        /*
         * Compounding is never used during session establish.
         */
+       spin_lock(&cifs_tcp_ses_lock);
        if ((ses->status == CifsNew) || (optype & CIFS_NEG_OP) || (optype & CIFS_SESS_OP)) {
                struct kvec iov = {
                        .iov_base = resp_iov[0].iov_base,
                        .iov_len = resp_iov[0].iov_len
                };
+               spin_unlock(&cifs_tcp_ses_lock);
                mutex_lock(&server->srv_mutex);
-               smb311_update_preauth_hash(ses, &iov, 1);
+               smb311_update_preauth_hash(ses, server, &iov, 1);
                mutex_unlock(&server->srv_mutex);
+               spin_lock(&cifs_tcp_ses_lock);
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
 out:
        /*
@@ -1353,8 +1371,12 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses,
                return -EIO;
        }
 
-       if (server->tcpStatus == CifsExiting)
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -ENOENT;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        /* Ensure that we do not send more than 50 overlapping requests
           to the same server. We may make this configurable later or
@@ -1494,8 +1516,12 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon,
                return -EIO;
        }
 
-       if (server->tcpStatus == CifsExiting)
+       spin_lock(&cifs_tcp_ses_lock);
+       if (server->tcpStatus == CifsExiting) {
+               spin_unlock(&cifs_tcp_ses_lock);
                return -ENOENT;
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        /* Ensure that we do not send more than 50 overlapping requests
           to the same server. We may make this configurable later or
@@ -1553,10 +1579,12 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon,
                 (server->tcpStatus != CifsNew)));
 
        /* Were we interrupted by a signal ? */
+       spin_lock(&cifs_tcp_ses_lock);
        if ((rc == -ERESTARTSYS) &&
                (midQ->mid_state == MID_REQUEST_SUBMITTED) &&
                ((server->tcpStatus == CifsGood) ||
                 (server->tcpStatus == CifsNew))) {
+               spin_unlock(&cifs_tcp_ses_lock);
 
                if (in_buf->Command == SMB_COM_TRANSACTION2) {
                        /* POSIX lock. We send a NT_CANCEL SMB to cause the
@@ -1595,7 +1623,9 @@ SendReceiveBlockingLock(const unsigned int xid, struct cifs_tcon *tcon,
 
                /* We got the response - restart system call. */
                rstart = 1;
+               spin_lock(&cifs_tcp_ses_lock);
        }
+       spin_unlock(&cifs_tcp_ses_lock);
 
        rc = cifs_sync_mid_result(midQ, server);
        if (rc != 0)
index 8ab81ea..dae5df8 100644 (file)
@@ -52,6 +52,7 @@
 #define QNX6_SUPER_MAGIC       0x68191122      /* qnx6 fs detection */
 #define AFS_FS_MAGIC           0x6B414653
 
+
 #define REISERFS_SUPER_MAGIC   0x52654973      /* used by gcc */
                                        /* used by file system utilities that
                                           look at the superblock, etc.  */
@@ -60,6 +61,9 @@
 #define REISER2FS_JR_SUPER_MAGIC_STRING        "ReIsEr3Fs"
 
 #define SMB_SUPER_MAGIC                0x517B
+#define CIFS_SUPER_MAGIC       0xFF534D42      /* the first four bytes of SMB PDUs */
+#define SMB2_SUPER_MAGIC       0xFE534D42
+
 #define CGROUP_SUPER_MAGIC     0x27e0eb
 #define CGROUP2_SUPER_MAGIC    0x63677270