Merge tag '5.16-rc-part2-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 13 Nov 2021 20:24:19 +0000 (12:24 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 13 Nov 2021 20:24:19 +0000 (12:24 -0800)
Pull more cifs updates from Steve French:

 - improvements to reconnect and multichannel

 - a performance improvement (additional use of SMB3 compounding)

 - DFS code cleanup and improvements

 - various trivial Coverity fixes

 - two fscache fixes

 - an fsync fix

* tag '5.16-rc-part2-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6: (23 commits)
  cifs: do not duplicate fscache cookie for secondary channels
  cifs: connect individual channel servers to primary channel server
  cifs: protect session channel fields with chan_lock
  cifs: do not negotiate session if session already exists
  smb3: do not setup the fscache_super_cookie until fsinfo initialized
  cifs: fix potential use-after-free bugs
  cifs: fix memory leak of smb3_fs_context_dup::server_hostname
  smb3: add additional null check in SMB311_posix_mkdir
  cifs: release lock earlier in dequeue_mid error case
  smb3: add additional null check in SMB2_tcon
  smb3: add additional null check in SMB2_open
  smb3: add additional null check in SMB2_ioctl
  smb3: remove trivial dfs compile warning
  cifs: support nested dfs links over reconnect
  smb3: do not error on fsync when readonly
  cifs: for compound requests, use open handle if possible
  cifs: set a minimum of 120s for next dns resolution
  cifs: split out dfs code from cifs_reconnect()
  cifs: convert list_for_each to entry variant
  cifs: introduce new helper for cifs_reconnect()
  ...

18 files changed:
fs/cifs/cifs_debug.c
fs/cifs/cifs_dfs_ref.c
fs/cifs/cifs_fs_sb.h
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/connect.c
fs/cifs/dfs_cache.c
fs/cifs/file.c
fs/cifs/fs_context.c
fs/cifs/fs_context.h
fs/cifs/fscache.c
fs/cifs/misc.c
fs/cifs/ntlmssp.h
fs/cifs/sess.c
fs/cifs/smb2inode.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/transport.c

index de2c12b..d282caf 100644 (file)
@@ -271,7 +271,8 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
        c = 0;
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
-               if (server->is_channel)
+               /* channel info will be printed as a part of sessions below */
+               if (CIFS_SERVER_IS_CHAN(server))
                        continue;
 
                c++;
@@ -358,6 +359,8 @@ skip_rdma:
                        seq_printf(m, " signed");
                if (server->posix_ext_supported)
                        seq_printf(m, " posix");
+               if (server->nosharesock)
+                       seq_printf(m, " nosharesock");
 
                if (server->rdma)
                        seq_printf(m, "\nRDMA ");
@@ -412,12 +415,14 @@ skip_rdma:
                                   from_kuid(&init_user_ns, ses->linux_uid),
                                   from_kuid(&init_user_ns, ses->cred_uid));
 
+                       spin_lock(&ses->chan_lock);
                        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++)
                                        cifs_dump_channel(m, j, &ses->chans[j]);
                        }
+                       spin_unlock(&ses->chan_lock);
 
                        seq_puts(m, "\n\n\tShares: ");
                        j = 0;
index 007427b..b0864da 100644 (file)
@@ -307,12 +307,8 @@ static struct vfsmount *cifs_dfs_do_mount(struct dentry *mntpt,
 static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
 {
        struct cifs_sb_info *cifs_sb;
-       struct cifs_ses *ses;
-       struct cifs_tcon *tcon;
        void *page;
-       char *full_path, *root_path;
-       unsigned int xid;
-       int rc;
+       char *full_path;
        struct vfsmount *mnt;
 
        cifs_dbg(FYI, "in %s\n", __func__);
@@ -324,8 +320,6 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
         * the double backslashes usually used in the UNC. This function
         * gives us the latter, so we must adjust the result.
         */
-       mnt = ERR_PTR(-ENOMEM);
-
        cifs_sb = CIFS_SB(mntpt->d_sb);
        if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) {
                mnt = ERR_PTR(-EREMOTE);
@@ -341,60 +335,11 @@ static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
        }
 
        convert_delimiter(full_path, '\\');
-
        cifs_dbg(FYI, "%s: full_path: %s\n", __func__, full_path);
 
-       if (!cifs_sb_master_tlink(cifs_sb)) {
-               cifs_dbg(FYI, "%s: master tlink is NULL\n", __func__);
-               goto free_full_path;
-       }
-
-       tcon = cifs_sb_master_tcon(cifs_sb);
-       if (!tcon) {
-               cifs_dbg(FYI, "%s: master tcon is NULL\n", __func__);
-               goto free_full_path;
-       }
-
-       root_path = kstrdup(tcon->treeName, GFP_KERNEL);
-       if (!root_path) {
-               mnt = ERR_PTR(-ENOMEM);
-               goto free_full_path;
-       }
-       cifs_dbg(FYI, "%s: root path: %s\n", __func__, root_path);
-
-       ses = tcon->ses;
-       xid = get_xid();
-
-       /*
-        * If DFS root has been expired, then unconditionally fetch it again to
-        * refresh DFS referral cache.
-        */
-       rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
-                           root_path + 1, NULL, NULL);
-       if (!rc) {
-               rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
-                                   cifs_remap(cifs_sb), full_path + 1,
-                                   NULL, NULL);
-       }
-
-       free_xid(xid);
-
-       if (rc) {
-               mnt = ERR_PTR(rc);
-               goto free_root_path;
-       }
-       /*
-        * OK - we were able to get and cache a referral for @full_path.
-        *
-        * Now, pass it down to cifs_mount() and it will retry every available
-        * node server in case of failures - no need to do it here.
-        */
        mnt = cifs_dfs_do_mount(mntpt, cifs_sb, full_path);
-       cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__,
-                full_path + 1, mnt);
+       cifs_dbg(FYI, "%s: cifs_dfs_do_mount:%s , mnt:%p\n", __func__, full_path + 1, mnt);
 
-free_root_path:
-       kfree(root_path);
 free_full_path:
        free_dentry_path(page);
 cdda_exit:
index f974075..013a4bd 100644 (file)
@@ -61,11 +61,6 @@ struct cifs_sb_info {
        /* only used when CIFS_MOUNT_USE_PREFIX_PATH is set */
        char *prepath;
 
-       /*
-        * Canonical DFS path initially provided by the mount call. We might connect to something
-        * different via DFS but we want to keep it to do failover properly.
-        */
-       char *origin_fullpath; /* \\HOST\SHARE\[OPTIONAL PATH] */
        /* randomly generated 128-bit number for indexing dfs mount groups in referral cache */
        uuid_t dfs_mount_id;
        /*
index abff31d..be74606 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/slab.h>
 #include <linux/mempool.h>
 #include <linux/workqueue.h>
+#include <linux/utsname.h>
 #include "cifs_fs_sb.h"
 #include "cifsacl.h"
 #include <crypto/internal/hash.h>
@@ -75,7 +76,8 @@
 #define SMB_ECHO_INTERVAL_MAX 600
 #define SMB_ECHO_INTERVAL_DEFAULT 60
 
-/* dns resolution interval in seconds */
+/* dns resolution intervals in seconds */
+#define SMB_DNS_RESOLVE_INTERVAL_MIN     120
 #define SMB_DNS_RESOLVE_INTERVAL_DEFAULT 600
 
 /* maximum number of PDUs in one compound */
 #define XATTR_DOS_ATTRIB "user.DOSATTRIB"
 #endif
 
+#define CIFS_MAX_WORKSTATION_LEN  (__NEW_UTS_LEN + 1)  /* reasonable max for client */
+
 /*
  * CIFS vfs client Status information (based on what we know.)
  */
@@ -592,6 +596,7 @@ struct TCP_Server_Info {
        struct list_head pending_mid_q;
        bool noblocksnd;                /* use blocking sendmsg */
        bool noautotune;                /* do not autotune send buf sizes */
+       bool nosharesock;
        bool tcp_nodelay;
        unsigned int credits;  /* send no more requests at once */
        unsigned int max_credits; /* can override large 32000 default at mnt */
@@ -685,13 +690,34 @@ struct TCP_Server_Info {
         */
        int nr_targets;
        bool noblockcnt; /* use non-blocking connect() */
-       bool is_channel; /* if a session channel */
+
+       /*
+        * If this is a session channel,
+        * primary_server holds the ref-counted
+        * pointer to primary channel connection for the session.
+        */
+#define CIFS_SERVER_IS_CHAN(server)    (!!(server)->primary_server)
+       struct TCP_Server_Info *primary_server;
+
 #ifdef CONFIG_CIFS_SWN_UPCALL
        bool use_swn_dstaddr;
        struct sockaddr_storage swn_dstaddr;
 #endif
 #ifdef CONFIG_CIFS_DFS_UPCALL
        bool is_dfs_conn; /* if a dfs connection */
+       struct mutex refpath_lock; /* protects leaf_fullpath */
+       /*
+        * Canonical DFS full paths that were used to chase referrals in mount and reconnect.
+        *
+        * origin_fullpath: first or original referral path
+        * leaf_fullpath: last referral path (might be changed due to nested links in reconnect)
+        *
+        * current_fullpath: pointer to either origin_fullpath or leaf_fullpath
+        * NOTE: cannot be accessed outside cifs_reconnect() and smb2_reconnect()
+        *
+        * format: \\HOST\SHARE\[OPTIONAL PATH]
+        */
+       char *origin_fullpath, *leaf_fullpath, *current_fullpath;
 #endif
 };
 
@@ -908,6 +934,7 @@ struct cifs_ses {
                                   and after mount option parsing we fill it */
        char *domainName;
        char *password;
+       char *workstation_name;
        struct session_key auth_key;
        struct ntlmssp_auth *ntlmssp; /* ciphertext, flags, server challenge */
        enum securityEnum sectype; /* what security flavor was specified? */
@@ -933,16 +960,21 @@ struct cifs_ses {
         * iface_lock should be taken when accessing any of these fields
         */
        spinlock_t iface_lock;
+       /* ========= begin: protected by iface_lock ======== */
        struct cifs_server_iface *iface_list;
        size_t iface_count;
        unsigned long iface_last_update; /* jiffies */
+       /* ========= end: protected by iface_lock ======== */
 
+       spinlock_t chan_lock;
+       /* ========= begin: protected by chan_lock ======== */
 #define CIFS_MAX_CHANNELS 16
        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 */
+       /* ========= end: protected by chan_lock ======== */
 };
 
 /*
@@ -1091,7 +1123,6 @@ struct cifs_tcon {
        struct cached_fid crfid; /* Cached root fid */
        /* BB add field for back pointer to sb struct(s)? */
 #ifdef CONFIG_CIFS_DFS_UPCALL
-       char *dfs_path; /* canonical DFS path */
        struct list_head ulist; /* cache update list */
 #endif
 };
@@ -1942,4 +1973,14 @@ static inline bool is_tcon_dfs(struct cifs_tcon *tcon)
                tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT);
 }
 
+static inline bool cifs_is_referral_server(struct cifs_tcon *tcon,
+                                          const struct dfs_info3_param *ref)
+{
+       /*
+        * Check if all targets are capable of handling DFS referrals as per
+        * MS-DFSC 2.2.4 RESP_GET_DFS_REFERRAL.
+        */
+       return is_tcon_dfs(tcon) || (ref && (ref->flags & DFSREF_REFERRAL_SERVER));
+}
+
 #endif /* _CIFS_GLOB_H */
index d0f85b6..f3073a6 100644 (file)
@@ -269,8 +269,9 @@ extern void cifs_close_all_deferred_files(struct cifs_tcon *cifs_tcon);
 
 extern void cifs_close_deferred_file_under_dentry(struct cifs_tcon *cifs_tcon,
                                const char *path);
-
-extern struct TCP_Server_Info *cifs_get_tcp_session(struct smb3_fs_context *ctx);
+extern struct TCP_Server_Info *
+cifs_get_tcp_session(struct smb3_fs_context *ctx,
+                    struct TCP_Server_Info *primary_server);
 extern void cifs_put_tcp_session(struct TCP_Server_Info *server,
                                 int from_reconnect);
 extern void cifs_put_tcon(struct cifs_tcon *tcon);
@@ -607,7 +608,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
 
 struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server);
 void cifs_put_tcp_super(struct super_block *sb);
-int update_super_prepath(struct cifs_tcon *tcon, char *prefix);
+int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix);
 char *extract_hostname(const char *unc);
 char *extract_sharename(const char *unc);
 
@@ -634,4 +635,7 @@ static inline int cifs_create_options(struct cifs_sb_info *cifs_sb, int options)
                return options;
 }
 
+struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon);
+void cifs_put_tcon_super(struct super_block *sb);
+
 #endif                 /* _CIFSPROTO_H */
index 0abbff4..82577a7 100644 (file)
@@ -61,6 +61,20 @@ extern bool disable_legacy_dialects;
 /* Drop the connection to not overload the server */
 #define NUM_STATUS_IO_TIMEOUT   5
 
+struct mount_ctx {
+       struct cifs_sb_info *cifs_sb;
+       struct smb3_fs_context *fs_ctx;
+       unsigned int xid;
+       struct TCP_Server_Info *server;
+       struct cifs_ses *ses;
+       struct cifs_tcon *tcon;
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       struct cifs_ses *root_ses;
+       uuid_t mount_id;
+       char *origin_fullpath, *leaf_fullpath;
+#endif
+};
+
 static int ip_connect(struct TCP_Server_Info *server);
 static int generic_ip_connect(struct TCP_Server_Info *server);
 static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
@@ -115,7 +129,7 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
                         * To make sure we don't use the cached entry, retry 1s
                         * after expiry.
                         */
-                       ttl = (expiry - now + 1);
+                       ttl = max_t(unsigned long, expiry - now, SMB_DNS_RESOLVE_INTERVAL_MIN) + 1;
        }
        rc = !rc ? -1 : 0;
 
@@ -148,139 +162,38 @@ static void cifs_resolve_server(struct work_struct *work)
        mutex_unlock(&server->srv_mutex);
 }
 
-#ifdef CONFIG_CIFS_DFS_UPCALL
-/* These functions must be called with server->srv_mutex held */
-static void reconn_set_next_dfs_target(struct TCP_Server_Info *server,
-                                      struct cifs_sb_info *cifs_sb,
-                                      struct dfs_cache_tgt_list *tgt_list,
-                                      struct dfs_cache_tgt_iterator **tgt_it)
-{
-       const char *name;
-       int rc;
-
-       if (!cifs_sb || !cifs_sb->origin_fullpath)
-               return;
-
-       if (!*tgt_it) {
-               *tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
-       } else {
-               *tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
-               if (!*tgt_it)
-                       *tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
-       }
-
-       cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath);
-
-       name = dfs_cache_get_tgt_name(*tgt_it);
-
-       kfree(server->hostname);
-
-       server->hostname = extract_hostname(name);
-       if (IS_ERR(server->hostname)) {
-               cifs_dbg(FYI,
-                        "%s: failed to extract hostname from target: %ld\n",
-                        __func__, PTR_ERR(server->hostname));
-               return;
-       }
-
-       rc = reconn_set_ipaddr_from_hostname(server);
-       if (rc) {
-               cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
-                        __func__, rc);
-       }
-}
-
-static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb,
-                                          struct dfs_cache_tgt_list *tl)
-{
-       if (!cifs_sb->origin_fullpath)
-               return -EOPNOTSUPP;
-       return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl);
-}
-#endif
-
-/*
- * cifs tcp session reconnection
+/**
+ * Mark all sessions and tcons for reconnect.
  *
- * mark tcp session as reconnecting so temporarily locked
- * mark all smb sessions as reconnecting for tcp session
- * reconnect tcp session
- * wake up waiters on reconnection? - (not needed currently)
+ * @server needs to be previously set to CifsNeedReconnect.
  */
-int
-cifs_reconnect(struct TCP_Server_Info *server)
+static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server)
 {
-       int rc = 0;
-       struct list_head *tmp, *tmp2;
        struct cifs_ses *ses;
        struct cifs_tcon *tcon;
-       struct mid_q_entry *mid_entry;
+       struct mid_q_entry *mid, *nmid;
        struct list_head retry_list;
-#ifdef CONFIG_CIFS_DFS_UPCALL
-       struct super_block *sb = NULL;
-       struct cifs_sb_info *cifs_sb = NULL;
-       struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
-       struct dfs_cache_tgt_iterator *tgt_it = NULL;
-#endif
+       struct TCP_Server_Info *pserver;
 
-       spin_lock(&GlobalMid_Lock);
-       server->nr_targets = 1;
-#ifdef CONFIG_CIFS_DFS_UPCALL
-       spin_unlock(&GlobalMid_Lock);
-       sb = cifs_get_tcp_super(server);
-       if (IS_ERR(sb)) {
-               rc = PTR_ERR(sb);
-               cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n",
-                        __func__, rc);
-               sb = NULL;
-       } else {
-               cifs_sb = CIFS_SB(sb);
-               rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list);
-               if (rc) {
-                       cifs_sb = NULL;
-                       if (rc != -EOPNOTSUPP) {
-                               cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n",
-                                               __func__);
-                       }
-               } else {
-                       server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
-               }
-       }
-       cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
-                server->nr_targets);
-       spin_lock(&GlobalMid_Lock);
-#endif
-       if (server->tcpStatus == CifsExiting) {
-               /* the demux thread will exit normally
-               next time through the loop */
-               spin_unlock(&GlobalMid_Lock);
-#ifdef CONFIG_CIFS_DFS_UPCALL
-               dfs_cache_free_tgts(&tgt_list);
-               cifs_put_tcp_super(sb);
-#endif
-               wake_up(&server->response_q);
-               return rc;
-       } else
-               server->tcpStatus = CifsNeedReconnect;
-       spin_unlock(&GlobalMid_Lock);
        server->maxBuf = 0;
        server->max_read = 0;
 
        cifs_dbg(FYI, "Mark tcp session as need reconnect\n");
        trace_smb3_reconnect(server->CurrentMid, server->conn_id, server->hostname);
+       /*
+        * 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\n", __func__);
+
+       /* If server is a channel, select the primary channel */
+       pserver = CIFS_SERVER_IS_CHAN(server) ? server->primary_server : server;
 
-       /* 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\n",
-                __func__);
        spin_lock(&cifs_tcp_ses_lock);
-       list_for_each(tmp, &server->smb_ses_list) {
-               ses = list_entry(tmp, struct cifs_ses, smb_ses_list);
+       list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) {
                ses->need_reconnect = true;
-               list_for_each(tmp2, &ses->tcon_list) {
-                       tcon = list_entry(tmp2, struct cifs_tcon, tcon_list);
+               list_for_each_entry(tcon, &ses->tcon_list, tcon_list)
                        tcon->need_reconnect = true;
-               }
                if (ses->tcon_ipc)
                        ses->tcon_ipc->need_reconnect = true;
        }
@@ -290,11 +203,11 @@ cifs_reconnect(struct TCP_Server_Info *server)
        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, server->ssocket->flags);
+               cifs_dbg(FYI, "State: 0x%x Flags: 0x%lx\n", server->ssocket->state,
+                        server->ssocket->flags);
                kernel_sock_shutdown(server->ssocket, SHUT_WR);
-               cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n",
-                        server->ssocket->state, server->ssocket->flags);
+               cifs_dbg(FYI, "Post shutdown state: 0x%x Flags: 0x%lx\n", server->ssocket->state,
+                        server->ssocket->flags);
                sock_release(server->ssocket);
                server->ssocket = NULL;
        }
@@ -309,23 +222,21 @@ cifs_reconnect(struct TCP_Server_Info *server)
        INIT_LIST_HEAD(&retry_list);
        cifs_dbg(FYI, "%s: moving mids to private list\n", __func__);
        spin_lock(&GlobalMid_Lock);
-       list_for_each_safe(tmp, tmp2, &server->pending_mid_q) {
-               mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
-               kref_get(&mid_entry->refcount);
-               if (mid_entry->mid_state == MID_REQUEST_SUBMITTED)
-                       mid_entry->mid_state = MID_RETRY_NEEDED;
-               list_move(&mid_entry->qhead, &retry_list);
-               mid_entry->mid_flags |= MID_DELETED;
+       list_for_each_entry_safe(mid, nmid, &server->pending_mid_q, qhead) {
+               kref_get(&mid->refcount);
+               if (mid->mid_state == MID_REQUEST_SUBMITTED)
+                       mid->mid_state = MID_RETRY_NEEDED;
+               list_move(&mid->qhead, &retry_list);
+               mid->mid_flags |= MID_DELETED;
        }
        spin_unlock(&GlobalMid_Lock);
        mutex_unlock(&server->srv_mutex);
 
        cifs_dbg(FYI, "%s: issuing mid callbacks\n", __func__);
-       list_for_each_safe(tmp, tmp2, &retry_list) {
-               mid_entry = list_entry(tmp, struct mid_q_entry, qhead);
-               list_del_init(&mid_entry->qhead);
-               mid_entry->callback(mid_entry);
-               cifs_mid_q_entry_release(mid_entry);
+       list_for_each_entry_safe(mid, nmid, &retry_list, qhead) {
+               list_del_init(&mid->qhead);
+               mid->callback(mid);
+               cifs_mid_q_entry_release(mid);
        }
 
        if (cifs_rdma_enabled(server)) {
@@ -333,38 +244,48 @@ cifs_reconnect(struct TCP_Server_Info *server)
                smbd_destroy(server);
                mutex_unlock(&server->srv_mutex);
        }
+}
+
+static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets)
+{
+       spin_lock(&GlobalMid_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);
+               wake_up(&server->response_q);
+               return false;
+       }
+       server->tcpStatus = CifsNeedReconnect;
+       spin_unlock(&GlobalMid_Lock);
+       return true;
+}
+
+/*
+ * cifs tcp session reconnection
+ *
+ * mark tcp session as reconnecting so temporarily locked
+ * mark all smb sessions as reconnecting for tcp session
+ * reconnect tcp session
+ * wake up waiters on reconnection? - (not needed currently)
+ */
+static int __cifs_reconnect(struct TCP_Server_Info *server)
+{
+       int rc = 0;
+
+       if (!cifs_tcp_ses_needs_reconnect(server, 1))
+               return 0;
+
+       cifs_mark_tcp_ses_conns_for_reconnect(server);
 
        do {
                try_to_freeze();
-
                mutex_lock(&server->srv_mutex);
 
-
                if (!cifs_swn_set_server_dstaddr(server)) {
-#ifdef CONFIG_CIFS_DFS_UPCALL
-               if (cifs_sb && cifs_sb->origin_fullpath)
-                       /*
-                        * Set up next DFS target server (if any) for reconnect. If DFS
-                        * feature is disabled, then we will retry last server we
-                        * connected to before.
-                        */
-                       reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
-               else {
-#endif
-                       /*
-                        * Resolve the hostname again to make sure that IP address is up-to-date.
-                        */
+                       /* resolve the hostname again to make sure that IP address is up-to-date */
                        rc = reconn_set_ipaddr_from_hostname(server);
-                       if (rc) {
-                               cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
-                                               __func__, rc);
-                       }
-
-#ifdef CONFIG_CIFS_DFS_UPCALL
-               }
-#endif
-
-
+                       cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
                }
 
                if (cifs_rdma_enabled(server))
@@ -372,8 +293,8 @@ cifs_reconnect(struct TCP_Server_Info *server)
                else
                        rc = generic_ip_connect(server);
                if (rc) {
-                       cifs_dbg(FYI, "reconnect error %d\n", rc);
                        mutex_unlock(&server->srv_mutex);
+                       cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
                        msleep(3000);
                } else {
                        atomic_inc(&tcpSesReconnectCount);
@@ -387,19 +308,128 @@ cifs_reconnect(struct TCP_Server_Info *server)
                }
        } while (server->tcpStatus == CifsNeedReconnect);
 
+       if (server->tcpStatus == CifsNeedNegotiate)
+               mod_delayed_work(cifsiod_wq, &server->echo, 0);
+
+       wake_up(&server->response_q);
+       return rc;
+}
+
 #ifdef CONFIG_CIFS_DFS_UPCALL
-       if (tgt_it) {
-               rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
-                                                   tgt_it);
-               if (rc) {
-                       cifs_server_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
-                                __func__, rc);
+static int __reconnect_target_unlocked(struct TCP_Server_Info *server, const char *target)
+{
+       int rc;
+       char *hostname;
+
+       if (!cifs_swn_set_server_dstaddr(server)) {
+               if (server->hostname != target) {
+                       hostname = extract_hostname(target);
+                       if (!IS_ERR(hostname)) {
+                               kfree(server->hostname);
+                               server->hostname = hostname;
+                       } else {
+                               cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %ld\n",
+                                        __func__, PTR_ERR(hostname));
+                               cifs_dbg(FYI, "%s: default to last target server: %s\n", __func__,
+                                        server->hostname);
+                       }
                }
-               dfs_cache_free_tgts(&tgt_list);
+               /* resolve the hostname again to make sure that IP address is up-to-date. */
+               rc = reconn_set_ipaddr_from_hostname(server);
+               cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
        }
+       /* Reconnect the socket */
+       if (cifs_rdma_enabled(server))
+               rc = smbd_reconnect(server);
+       else
+               rc = generic_ip_connect(server);
 
-       cifs_put_tcp_super(sb);
-#endif
+       return rc;
+}
+
+static int reconnect_target_unlocked(struct TCP_Server_Info *server, struct dfs_cache_tgt_list *tl,
+                                    struct dfs_cache_tgt_iterator **target_hint)
+{
+       int rc;
+       struct dfs_cache_tgt_iterator *tit;
+
+       *target_hint = NULL;
+
+       /* If dfs target list is empty, then reconnect to last server */
+       tit = dfs_cache_get_tgt_iterator(tl);
+       if (!tit)
+               return __reconnect_target_unlocked(server, server->hostname);
+
+       /* Otherwise, try every dfs target in @tl */
+       for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
+               rc = __reconnect_target_unlocked(server, dfs_cache_get_tgt_name(tit));
+               if (!rc) {
+                       *target_hint = tit;
+                       break;
+               }
+       }
+       return rc;
+}
+
+static int reconnect_dfs_server(struct TCP_Server_Info *server)
+{
+       int rc = 0;
+       const char *refpath = server->current_fullpath + 1;
+       struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
+       struct dfs_cache_tgt_iterator *target_hint = NULL;
+       int num_targets = 0;
+
+       /*
+        * Determine the number of dfs targets the referral path in @cifs_sb resolves to.
+        *
+        * smb2_reconnect() needs to know how long it should wait based upon the number of dfs
+        * targets (server->nr_targets).  It's also possible that the cached referral was cleared
+        * through /proc/fs/cifs/dfscache or the target list is empty due to server settings after
+        * refreshing the referral, so, in this case, default it to 1.
+        */
+       if (!dfs_cache_noreq_find(refpath, NULL, &tl))
+               num_targets = dfs_cache_get_nr_tgts(&tl);
+       if (!num_targets)
+               num_targets = 1;
+
+       if (!cifs_tcp_ses_needs_reconnect(server, num_targets))
+               return 0;
+
+       cifs_mark_tcp_ses_conns_for_reconnect(server);
+
+       do {
+               try_to_freeze();
+               mutex_lock(&server->srv_mutex);
+
+               rc = reconnect_target_unlocked(server, &tl, &target_hint);
+               if (rc) {
+                       /* Failed to reconnect socket */
+                       mutex_unlock(&server->srv_mutex);
+                       cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
+                       msleep(3000);
+                       continue;
+               }
+               /*
+                * Socket was created.  Update tcp session status to CifsNeedNegotiate so that a
+                * process waiting for reconnect will know it needs to re-establish session and tcon
+                * through the reconnected target server.
+                */
+               atomic_inc(&tcpSesReconnectCount);
+               set_credits(server, 1);
+               spin_lock(&GlobalMid_Lock);
+               if (server->tcpStatus != CifsExiting)
+                       server->tcpStatus = CifsNeedNegotiate;
+               spin_unlock(&GlobalMid_Lock);
+               cifs_swn_reset_server_dstaddr(server);
+               mutex_unlock(&server->srv_mutex);
+       } while (server->tcpStatus == CifsNeedReconnect);
+
+       if (target_hint)
+               dfs_cache_noreq_update_tgthint(refpath, target_hint);
+
+       dfs_cache_free_tgts(&tl);
+
+       /* Need to set up echo worker again once connection has been established */
        if (server->tcpStatus == CifsNeedNegotiate)
                mod_delayed_work(cifsiod_wq, &server->echo, 0);
 
@@ -407,6 +437,25 @@ cifs_reconnect(struct TCP_Server_Info *server)
        return rc;
 }
 
+int cifs_reconnect(struct TCP_Server_Info *server)
+{
+       /* 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);
+       }
+       spin_unlock(&cifs_tcp_ses_lock);
+
+       return reconnect_dfs_server(server);
+}
+#else
+int cifs_reconnect(struct TCP_Server_Info *server)
+{
+       return __cifs_reconnect(server);
+}
+#endif
+
 static void
 cifs_echo_request(struct work_struct *work)
 {
@@ -665,13 +714,14 @@ dequeue_mid(struct mid_q_entry *mid, bool malformed)
         * Trying to handle/dequeue a mid after the send_recv()
         * function has finished processing it is a bug.
         */
-       if (mid->mid_flags & MID_DELETED)
+       if (mid->mid_flags & MID_DELETED) {
+               spin_unlock(&GlobalMid_Lock);
                pr_warn_once("trying to dequeue a deleted mid\n");
-       else {
+       else {
                list_del_init(&mid->qhead);
                mid->mid_flags |= MID_DELETED;
+               spin_unlock(&GlobalMid_Lock);
        }
-       spin_unlock(&GlobalMid_Lock);
 }
 
 static unsigned int
@@ -794,6 +844,10 @@ static void clean_demultiplex_info(struct TCP_Server_Info *server)
                 */
        }
 
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       kfree(server->origin_fullpath);
+       kfree(server->leaf_fullpath);
+#endif
        kfree(server);
 
        length = atomic_dec_return(&tcpSesAllocCount);
@@ -1217,7 +1271,13 @@ static int match_server(struct TCP_Server_Info *server, struct smb3_fs_context *
 {
        struct sockaddr *addr = (struct sockaddr *)&ctx->dstaddr;
 
-       if (ctx->nosharesock)
+       if (ctx->nosharesock) {
+               server->nosharesock = true;
+               return 0;
+       }
+
+       /* this server does not share socket */
+       if (server->nosharesock)
                return 0;
 
        /* If multidialect negotiation see if existing sessions match one */
@@ -1283,7 +1343,7 @@ cifs_find_tcp_session(struct smb3_fs_context *ctx)
                 * Skip ses channels since they're only handled in lower layers
                 * (e.g. cifs_send_recv).
                 */
-               if (server->is_channel || !match_server(server, ctx))
+               if (CIFS_SERVER_IS_CHAN(server) || !match_server(server, ctx))
                        continue;
 
                ++server->srv_count;
@@ -1314,6 +1374,10 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
        list_del_init(&server->tcp_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
 
+       /* For secondary channels, we pick up ref-count on the primary server */
+       if (CIFS_SERVER_IS_CHAN(server))
+               cifs_put_tcp_session(server->primary_server, from_reconnect);
+
        cancel_delayed_work_sync(&server->echo);
        cancel_delayed_work_sync(&server->resolve);
 
@@ -1333,7 +1397,10 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
        spin_unlock(&GlobalMid_Lock);
 
        cifs_crypto_secmech_release(server);
-       cifs_fscache_release_client_cookie(server);
+
+       /* fscache server cookies are based on primary channel only */
+       if (!CIFS_SERVER_IS_CHAN(server))
+               cifs_fscache_release_client_cookie(server);
 
        kfree(server->session_key.response);
        server->session_key.response = NULL;
@@ -1346,7 +1413,8 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
 }
 
 struct TCP_Server_Info *
-cifs_get_tcp_session(struct smb3_fs_context *ctx)
+cifs_get_tcp_session(struct smb3_fs_context *ctx,
+                    struct TCP_Server_Info *primary_server)
 {
        struct TCP_Server_Info *tcp_ses = NULL;
        int rc;
@@ -1383,6 +1451,10 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx)
        tcp_ses->in_flight = 0;
        tcp_ses->max_in_flight = 0;
        tcp_ses->credits = 1;
+       if (primary_server) {
+               ++primary_server->srv_count;
+               tcp_ses->primary_server = primary_server;
+       }
        init_waitqueue_head(&tcp_ses->response_q);
        init_waitqueue_head(&tcp_ses->request_q);
        INIT_LIST_HEAD(&tcp_ses->pending_mid_q);
@@ -1403,6 +1475,9 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx)
        INIT_DELAYED_WORK(&tcp_ses->resolve, cifs_resolve_server);
        INIT_DELAYED_WORK(&tcp_ses->reconnect, smb2_reconnect_server);
        mutex_init(&tcp_ses->reconnect_mutex);
+#ifdef CONFIG_CIFS_DFS_UPCALL
+       mutex_init(&tcp_ses->refpath_lock);
+#endif
        memcpy(&tcp_ses->srcaddr, &ctx->srcaddr,
               sizeof(tcp_ses->srcaddr));
        memcpy(&tcp_ses->dstaddr, &ctx->dstaddr,
@@ -1481,7 +1556,9 @@ smbd_connected:
        list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
 
-       cifs_fscache_get_client_cookie(tcp_ses);
+       /* fscache server cookies are based on primary channel only */
+       if (!CIFS_SERVER_IS_CHAN(tcp_ses))
+               cifs_fscache_get_client_cookie(tcp_ses);
 
        /* queue echo request delayed work */
        queue_delayed_work(cifsiod_wq, &tcp_ses->echo, tcp_ses->echo_interval);
@@ -1501,6 +1578,8 @@ out_err_crypto_release:
 
 out_err:
        if (tcp_ses) {
+               if (CIFS_SERVER_IS_CHAN(tcp_ses))
+                       cifs_put_tcp_session(tcp_ses->primary_server, false);
                kfree(tcp_ses->hostname);
                if (tcp_ses->ssocket)
                        sock_release(tcp_ses->ssocket);
@@ -1519,8 +1598,12 @@ static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
         * If an existing session is limited to less channels than
         * requested, it should not be reused
         */
-       if (ses->chan_max < ctx->max_channels)
+       spin_lock(&ses->chan_lock);
+       if (ses->chan_max < ctx->max_channels) {
+               spin_unlock(&ses->chan_lock);
                return 0;
+       }
+       spin_unlock(&ses->chan_lock);
 
        switch (ses->sectype) {
        case Kerberos:
@@ -1655,6 +1738,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
 void cifs_put_smb_ses(struct cifs_ses *ses)
 {
        unsigned int rc, xid;
+       unsigned int chan_count;
        struct TCP_Server_Info *server = ses->server;
        cifs_dbg(FYI, "%s: ses_count=%d\n", __func__, ses->ses_count);
 
@@ -1696,12 +1780,24 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
        list_del_init(&ses->smb_ses_list);
        spin_unlock(&cifs_tcp_ses_lock);
 
+       spin_lock(&ses->chan_lock);
+       chan_count = ses->chan_count;
+       spin_unlock(&ses->chan_lock);
+
        /* close any extra channels */
-       if (ses->chan_count > 1) {
+       if (chan_count > 1) {
                int i;
 
-               for (i = 1; i < ses->chan_count; i++)
+               for (i = 1; i < chan_count; i++) {
+                       /*
+                        * note: for now, we're okay accessing ses->chans
+                        * without chan_lock. But when chans can go away, we'll
+                        * need to introduce ref counting to make sure that chan
+                        * is not freed from under us.
+                        */
                        cifs_put_tcp_session(ses->chans[i].server, 0);
+                       ses->chans[i].server = NULL;
+               }
        }
 
        sesInfoFree(ses);
@@ -1885,16 +1981,18 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                         ses->status);
 
                mutex_lock(&ses->session_mutex);
-               rc = cifs_negotiate_protocol(xid, ses);
-               if (rc) {
-                       mutex_unlock(&ses->session_mutex);
-                       /* problem -- put our ses reference */
-                       cifs_put_smb_ses(ses);
-                       free_xid(xid);
-                       return ERR_PTR(rc);
-               }
                if (ses->need_reconnect) {
                        cifs_dbg(FYI, "Session needs reconnect\n");
+
+                       rc = cifs_negotiate_protocol(xid, ses);
+                       if (rc) {
+                               mutex_unlock(&ses->session_mutex);
+                               /* problem -- put our ses reference */
+                               cifs_put_smb_ses(ses);
+                               free_xid(xid);
+                               return ERR_PTR(rc);
+                       }
+
                        rc = cifs_setup_session(xid, ses,
                                                ctx->local_nls);
                        if (rc) {
@@ -1942,6 +2040,12 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
                if (!ses->domainName)
                        goto get_ses_fail;
        }
+       if (ctx->workstation_name) {
+               ses->workstation_name = kstrdup(ctx->workstation_name,
+                                               GFP_KERNEL);
+               if (!ses->workstation_name)
+                       goto get_ses_fail;
+       }
        if (ctx->domainauto)
                ses->domainAuto = ctx->domainauto;
        ses->cred_uid = ctx->cred_uid;
@@ -1952,9 +2056,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
        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;
+       spin_unlock(&ses->chan_lock);
 
        rc = cifs_negotiate_protocol(xid, ses);
        if (!rc)
@@ -2286,8 +2392,6 @@ cifs_get_tcon(struct cifs_ses *ses, struct smb3_fs_context *ctx)
        list_add(&tcon->tcon_list, &ses->tcon_list);
        spin_unlock(&cifs_tcp_ses_lock);
 
-       cifs_fscache_get_super_cookie(tcon);
-
        return tcon;
 
 out_fail:
@@ -2849,73 +2953,64 @@ int cifs_setup_cifs_sb(struct cifs_sb_info *cifs_sb)
 }
 
 /* Release all succeed connections */
-static inline void mount_put_conns(struct cifs_sb_info *cifs_sb,
-                                  unsigned int xid,
-                                  struct TCP_Server_Info *server,
-                                  struct cifs_ses *ses, struct cifs_tcon *tcon)
+static inline void mount_put_conns(struct mount_ctx *mnt_ctx)
 {
        int rc = 0;
 
-       if (tcon)
-               cifs_put_tcon(tcon);
-       else if (ses)
-               cifs_put_smb_ses(ses);
-       else if (server)
-               cifs_put_tcp_session(server, 0);
-       cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
-       free_xid(xid);
+       if (mnt_ctx->tcon)
+               cifs_put_tcon(mnt_ctx->tcon);
+       else if (mnt_ctx->ses)
+               cifs_put_smb_ses(mnt_ctx->ses);
+       else if (mnt_ctx->server)
+               cifs_put_tcp_session(mnt_ctx->server, 0);
+       mnt_ctx->cifs_sb->mnt_cifs_flags &= ~CIFS_MOUNT_POSIX_PATHS;
+       free_xid(mnt_ctx->xid);
 }
 
 /* Get connections for tcp, ses and tcon */
-static int mount_get_conns(struct smb3_fs_context *ctx, struct cifs_sb_info *cifs_sb,
-                          unsigned int *xid,
-                          struct TCP_Server_Info **nserver,
-                          struct cifs_ses **nses, struct cifs_tcon **ntcon)
+static int mount_get_conns(struct mount_ctx *mnt_ctx)
 {
        int rc = 0;
-       struct TCP_Server_Info *server;
-       struct cifs_ses *ses;
-       struct cifs_tcon *tcon;
-
-       *nserver = NULL;
-       *nses = NULL;
-       *ntcon = NULL;
+       struct TCP_Server_Info *server = NULL;
+       struct cifs_ses *ses = NULL;
+       struct cifs_tcon *tcon = NULL;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       unsigned int xid;
 
-       *xid = get_xid();
+       xid = get_xid();
 
        /* get a reference to a tcp session */
-       server = cifs_get_tcp_session(ctx);
+       server = cifs_get_tcp_session(ctx, NULL);
        if (IS_ERR(server)) {
                rc = PTR_ERR(server);
-               return rc;
+               server = NULL;
+               goto out;
        }
 
-       *nserver = server;
-
        /* get a reference to a SMB session */
        ses = cifs_get_smb_ses(server, ctx);
        if (IS_ERR(ses)) {
                rc = PTR_ERR(ses);
-               return rc;
+               ses = NULL;
+               goto out;
        }
 
-       *nses = ses;
-
        if ((ctx->persistent == true) && (!(ses->server->capabilities &
                                            SMB2_GLOBAL_CAP_PERSISTENT_HANDLES))) {
                cifs_server_dbg(VFS, "persistent handles not supported by server\n");
-               return -EOPNOTSUPP;
+               rc = -EOPNOTSUPP;
+               goto out;
        }
 
        /* search for existing tcon to this server share */
        tcon = cifs_get_tcon(ses, ctx);
        if (IS_ERR(tcon)) {
                rc = PTR_ERR(tcon);
-               return rc;
+               tcon = NULL;
+               goto out;
        }
 
-       *ntcon = tcon;
-
        /* if new SMB3.11 POSIX extensions are supported do not remap / and \ */
        if (tcon->posix_extensions)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS;
@@ -2926,17 +3021,19 @@ static int mount_get_conns(struct smb3_fs_context *ctx, struct cifs_sb_info *cif
                 * reset of caps checks mount to see if unix extensions disabled
                 * for just this mount.
                 */
-               reset_cifs_unix_caps(*xid, tcon, cifs_sb, ctx);
+               reset_cifs_unix_caps(xid, tcon, cifs_sb, ctx);
                if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) &&
                    (le64_to_cpu(tcon->fsUnixInfo.Capability) &
-                    CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP))
-                       return -EACCES;
+                    CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) {
+                       rc = -EACCES;
+                       goto out;
+               }
        } else
                tcon->unix_ext = 0; /* server does not support them */
 
        /* do not care if a following call succeed - informational */
        if (!tcon->pipe && server->ops->qfs_tcon) {
-               server->ops->qfs_tcon(*xid, tcon, cifs_sb);
+               server->ops->qfs_tcon(xid, tcon, cifs_sb);
                if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_RO_CACHE) {
                        if (tcon->fsDevInfo.DeviceCharacteristics &
                            cpu_to_le32(FILE_READ_ONLY_DEVICE))
@@ -2946,6 +3043,12 @@ static int mount_get_conns(struct smb3_fs_context *ctx, struct cifs_sb_info *cif
                                cifs_dbg(VFS, "read only mount of RW share\n");
                        /* no need to log a RW mount of a typical RW share */
                }
+               /*
+                * The cookie is initialized from volume info returned above.
+                * Inside cifs_fscache_get_super_cookie it checks
+                * that we do not get super cookie twice.
+                */
+               cifs_fscache_get_super_cookie(tcon);
        }
 
        /*
@@ -2960,7 +3063,13 @@ static int mount_get_conns(struct smb3_fs_context *ctx, struct cifs_sb_info *cif
            (cifs_sb->ctx->rsize > server->ops->negotiate_rsize(tcon, ctx)))
                cifs_sb->ctx->rsize = server->ops->negotiate_rsize(tcon, ctx);
 
-       return 0;
+out:
+       mnt_ctx->server = server;
+       mnt_ctx->ses = ses;
+       mnt_ctx->tcon = tcon;
+       mnt_ctx->xid = xid;
+
+       return rc;
 }
 
 static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
@@ -2990,18 +3099,17 @@ static int mount_setup_tlink(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
-static int mount_get_dfs_conns(struct smb3_fs_context *ctx, struct cifs_sb_info *cifs_sb,
-                              unsigned int *xid, struct TCP_Server_Info **nserver,
-                              struct cifs_ses **nses, struct cifs_tcon **ntcon)
+/* Get unique dfs connections */
+static int mount_get_dfs_conns(struct mount_ctx *mnt_ctx)
 {
        int rc;
 
-       ctx->nosharesock = true;
-       rc = mount_get_conns(ctx, cifs_sb, xid, nserver, nses, ntcon);
-       if (*nserver) {
+       mnt_ctx->fs_ctx->nosharesock = true;
+       rc = mount_get_conns(mnt_ctx);
+       if (mnt_ctx->server) {
                cifs_dbg(FYI, "%s: marking tcp session as a dfs connection\n", __func__);
                spin_lock(&cifs_tcp_ses_lock);
-               (*nserver)->is_dfs_conn = true;
+               mnt_ctx->server->is_dfs_conn = true;
                spin_unlock(&cifs_tcp_ses_lock);
        }
        return rc;
@@ -3043,190 +3151,38 @@ build_unc_path_to_root(const struct smb3_fs_context *ctx,
 }
 
 /*
- * expand_dfs_referral - Perform a dfs referral query and update the cifs_sb
- *
- * If a referral is found, cifs_sb->ctx->mount_options will be (re-)allocated
- * to a string containing updated options for the submount.  Otherwise it
- * will be left untouched.
+ * expand_dfs_referral - Update cifs_sb from dfs referral path
  *
- * Returns the rc from get_dfs_path to the caller, which can be used to
- * determine whether there were referrals.
+ * cifs_sb->ctx->mount_options will be (re-)allocated to a string containing updated options for the
+ * submount.  Otherwise it will be left untouched.
  */
-static int
-expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
-                   struct smb3_fs_context *ctx, struct cifs_sb_info *cifs_sb,
-                   char *ref_path)
+static int expand_dfs_referral(struct mount_ctx *mnt_ctx, const char *full_path,
+                              struct dfs_info3_param *referral)
 {
        int rc;
-       struct dfs_info3_param referral = {0};
-       char *full_path = NULL, *mdata = NULL;
-
-       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
-               return -EREMOTE;
-
-       full_path = build_unc_path_to_root(ctx, cifs_sb, true);
-       if (IS_ERR(full_path))
-               return PTR_ERR(full_path);
-
-       rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
-                           ref_path, &referral, NULL);
-       if (!rc) {
-               char *fake_devname = NULL;
-
-               mdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options,
-                                                  full_path + 1, &referral,
-                                                  &fake_devname);
-               free_dfs_info_param(&referral);
-
-               if (IS_ERR(mdata)) {
-                       rc = PTR_ERR(mdata);
-                       mdata = NULL;
-               } else {
-                       /*
-                        * We can not clear out the whole structure since we
-                        * no longer have an explicit function to parse
-                        * a mount-string. Instead we need to clear out the
-                        * individual fields that are no longer valid.
-                        */
-                       kfree(ctx->prepath);
-                       ctx->prepath = NULL;
-                       rc = cifs_setup_volume_info(ctx, mdata, fake_devname);
-               }
-               kfree(fake_devname);
-               kfree(cifs_sb->ctx->mount_options);
-               cifs_sb->ctx->mount_options = mdata;
-       }
-       kfree(full_path);
-       return rc;
-}
-
-static int get_next_dfs_tgt(struct dfs_cache_tgt_list *tgt_list,
-                           struct dfs_cache_tgt_iterator **tgt_it)
-{
-       if (!*tgt_it)
-               *tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
-       else
-               *tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
-       return !*tgt_it ? -EHOSTDOWN : 0;
-}
-
-static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it,
-                          struct smb3_fs_context *fake_ctx, struct smb3_fs_context *ctx)
-{
-       const char *tgt = dfs_cache_get_tgt_name(tgt_it);
-       int len = strlen(tgt) + 2;
-       char *new_unc;
-
-       new_unc = kmalloc(len, GFP_KERNEL);
-       if (!new_unc)
-               return -ENOMEM;
-       scnprintf(new_unc, len, "\\%s", tgt);
-
-       kfree(ctx->UNC);
-       ctx->UNC = new_unc;
-
-       if (fake_ctx->prepath) {
-               kfree(ctx->prepath);
-               ctx->prepath = fake_ctx->prepath;
-               fake_ctx->prepath = NULL;
-       }
-       memcpy(&ctx->dstaddr, &fake_ctx->dstaddr, sizeof(ctx->dstaddr));
-
-       return 0;
-}
-
-static int do_dfs_failover(const char *path, const char *full_path, struct cifs_sb_info *cifs_sb,
-                          struct smb3_fs_context *ctx, struct cifs_ses *root_ses,
-                          unsigned int *xid, struct TCP_Server_Info **server,
-                          struct cifs_ses **ses, struct cifs_tcon **tcon)
-{
-       int rc;
-       char *npath = NULL;
-       struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
-       struct dfs_cache_tgt_iterator *tgt_it = NULL;
-       struct smb3_fs_context tmp_ctx = {NULL};
-
-       if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
-               return -EOPNOTSUPP;
-
-       npath = dfs_cache_canonical_path(path, cifs_sb->local_nls, cifs_remap(cifs_sb));
-       if (IS_ERR(npath))
-               return PTR_ERR(npath);
-
-       cifs_dbg(FYI, "%s: path=%s full_path=%s\n", __func__, npath, full_path);
-
-       rc = dfs_cache_noreq_find(npath, NULL, &tgt_list);
-       if (rc)
-               goto out;
-       /*
-        * We use a 'tmp_ctx' here because we need pass it down to the mount_{get,put} functions to
-        * test connection against new DFS targets.
-        */
-       rc = smb3_fs_context_dup(&tmp_ctx, ctx);
-       if (rc)
-               goto out;
-
-       for (;;) {
-               struct dfs_info3_param ref = {0};
-               char *fake_devname = NULL, *mdata = NULL;
-
-               /* Get next DFS target server - if any */
-               rc = get_next_dfs_tgt(&tgt_list, &tgt_it);
-               if (rc)
-                       break;
-
-               rc = dfs_cache_get_tgt_referral(npath, tgt_it, &ref);
-               if (rc)
-                       break;
-
-               cifs_dbg(FYI, "%s: old ctx: UNC=%s prepath=%s\n", __func__, tmp_ctx.UNC,
-                        tmp_ctx.prepath);
-
-               mdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options, full_path + 1, &ref,
-                                                  &fake_devname);
-               free_dfs_info_param(&ref);
-
-               if (IS_ERR(mdata)) {
-                       rc = PTR_ERR(mdata);
-                       mdata = NULL;
-               } else
-                       rc = cifs_setup_volume_info(&tmp_ctx, mdata, fake_devname);
-
-               kfree(mdata);
-               kfree(fake_devname);
-
-               if (rc)
-                       break;
-
-               cifs_dbg(FYI, "%s: new ctx: UNC=%s prepath=%s\n", __func__, tmp_ctx.UNC,
-                        tmp_ctx.prepath);
-
-               mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
-               rc = mount_get_dfs_conns(&tmp_ctx, cifs_sb, xid, server, ses, tcon);
-               if (!rc || (*server && *ses)) {
-                       /*
-                        * We were able to connect to new target server. Update current context with
-                        * new target server.
-                        */
-                       rc = update_vol_info(tgt_it, &tmp_ctx, ctx);
-                       break;
-               }
-       }
-       if (!rc) {
-               cifs_dbg(FYI, "%s: final ctx: UNC=%s prepath=%s\n", __func__, tmp_ctx.UNC,
-                        tmp_ctx.prepath);
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       char *fake_devname = NULL, *mdata = NULL;
+
+       mdata = cifs_compose_mount_options(cifs_sb->ctx->mount_options, full_path + 1, referral,
+                                          &fake_devname);
+       if (IS_ERR(mdata)) {
+               rc = PTR_ERR(mdata);
+               mdata = NULL;
+       } else {
                /*
-                * Update DFS target hint in DFS referral cache with the target server we
-                * successfully reconnected to.
+                * We can not clear out the whole structure since we no longer have an explicit
+                * function to parse a mount-string. Instead we need to clear out the individual
+                * fields that are no longer valid.
                 */
-               rc = dfs_cache_update_tgthint(*xid, root_ses ? root_ses : *ses, cifs_sb->local_nls,
-                                             cifs_remap(cifs_sb), path, tgt_it);
+               kfree(ctx->prepath);
+               ctx->prepath = NULL;
+               rc = cifs_setup_volume_info(ctx, mdata, fake_devname);
        }
+       kfree(fake_devname);
+       kfree(cifs_sb->ctx->mount_options);
+       cifs_sb->ctx->mount_options = mdata;
 
-out:
-       kfree(npath);
-       smb3_cleanup_fs_context_contents(&tmp_ctx);
-       dfs_cache_free_tgts(&tgt_list);
        return rc;
 }
 #endif
@@ -3333,12 +3289,14 @@ cifs_are_all_path_components_accessible(struct TCP_Server_Info *server,
  * Check if path is remote (e.g. a DFS share). Return -EREMOTE if it is,
  * otherwise 0.
  */
-static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx,
-                         const unsigned int xid,
-                         struct TCP_Server_Info *server,
-                         struct cifs_tcon *tcon)
+static int is_path_remote(struct mount_ctx *mnt_ctx)
 {
        int rc;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct TCP_Server_Info *server = mnt_ctx->server;
+       unsigned int xid = mnt_ctx->xid;
+       struct cifs_tcon *tcon = mnt_ctx->tcon;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
        char *full_path;
 
        if (!server->ops->is_path_accessible)
@@ -3376,280 +3334,289 @@ static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
-static void set_root_ses(struct cifs_sb_info *cifs_sb, const uuid_t *mount_id, struct cifs_ses *ses,
-                        struct cifs_ses **root_ses)
+static void set_root_ses(struct mount_ctx *mnt_ctx)
 {
-       if (ses) {
+       if (mnt_ctx->ses) {
                spin_lock(&cifs_tcp_ses_lock);
-               ses->ses_count++;
+               mnt_ctx->ses->ses_count++;
                spin_unlock(&cifs_tcp_ses_lock);
-               dfs_cache_add_refsrv_session(mount_id, ses);
+               dfs_cache_add_refsrv_session(&mnt_ctx->mount_id, mnt_ctx->ses);
        }
-       *root_ses = ses;
+       mnt_ctx->root_ses = mnt_ctx->ses;
 }
 
-/* Set up next dfs prefix path in @dfs_path */
-static int next_dfs_prepath(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx,
-                           const unsigned int xid, struct TCP_Server_Info *server,
-                           struct cifs_tcon *tcon, char **dfs_path)
+static int is_dfs_mount(struct mount_ctx *mnt_ctx, bool *isdfs, struct dfs_cache_tgt_list *root_tl)
 {
-       char *path, *npath;
-       int added_treename = is_tcon_dfs(tcon);
        int rc;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
 
-       path = cifs_build_path_to_root(ctx, cifs_sb, tcon, added_treename);
-       if (!path)
-               return -ENOMEM;
+       *isdfs = true;
 
-       rc = is_path_remote(cifs_sb, ctx, xid, server, tcon);
-       if (rc == -EREMOTE) {
-               struct smb3_fs_context v = {NULL};
-               /* if @path contains a tree name, skip it in the prefix path */
-               if (added_treename) {
-                       rc = smb3_parse_devname(path, &v);
-                       if (rc)
-                               goto out;
-                       npath = build_unc_path_to_root(&v, cifs_sb, true);
-                       smb3_cleanup_fs_context_contents(&v);
-               } else {
-                       v.UNC = ctx->UNC;
-                       v.prepath = path + 1;
-                       npath = build_unc_path_to_root(&v, cifs_sb, true);
-               }
+       rc = mount_get_conns(mnt_ctx);
+       /*
+        * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
+        * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
+        *
+        * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
+        * to respond with PATH_NOT_COVERED to requests that include the prefix.
+        */
+       if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
+           dfs_cache_find(mnt_ctx->xid, mnt_ctx->ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
+                          ctx->UNC + 1, NULL, root_tl)) {
+               if (rc)
+                       return rc;
+               /* Check if it is fully accessible and then mount it */
+               rc = is_path_remote(mnt_ctx);
+               if (!rc)
+                       *isdfs = false;
+               else if (rc != -EREMOTE)
+                       return rc;
+       }
+       return 0;
+}
 
-               if (IS_ERR(npath)) {
-                       rc = PTR_ERR(npath);
-                       goto out;
-               }
+static int connect_dfs_target(struct mount_ctx *mnt_ctx, const char *full_path,
+                             const char *ref_path, struct dfs_cache_tgt_iterator *tit)
+{
+       int rc;
+       struct dfs_info3_param ref = {};
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       char *oldmnt = cifs_sb->ctx->mount_options;
+
+       rc = dfs_cache_get_tgt_referral(ref_path, tit, &ref);
+       if (rc)
+               goto out;
+
+       rc = expand_dfs_referral(mnt_ctx, full_path, &ref);
+       if (rc)
+               goto out;
 
-               kfree(*dfs_path);
-               *dfs_path = npath;
-               rc = -EREMOTE;
+       /* Connect to new target only if we were redirected (e.g. mount options changed) */
+       if (oldmnt != cifs_sb->ctx->mount_options) {
+               mount_put_conns(mnt_ctx);
+               rc = mount_get_dfs_conns(mnt_ctx);
+       }
+       if (!rc) {
+               if (cifs_is_referral_server(mnt_ctx->tcon, &ref))
+                       set_root_ses(mnt_ctx);
+               rc = dfs_cache_update_tgthint(mnt_ctx->xid, mnt_ctx->root_ses, cifs_sb->local_nls,
+                                             cifs_remap(cifs_sb), ref_path, tit);
        }
 
 out:
-       kfree(path);
+       free_dfs_info_param(&ref);
        return rc;
 }
 
-/* Check if resolved targets can handle any DFS referrals */
-static int is_referral_server(const char *ref_path, struct cifs_sb_info *cifs_sb,
-                             struct cifs_tcon *tcon, bool *ref_server)
+static int connect_dfs_root(struct mount_ctx *mnt_ctx, struct dfs_cache_tgt_list *root_tl)
 {
        int rc;
-       struct dfs_info3_param ref = {0};
+       char *full_path;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       struct dfs_cache_tgt_iterator *tit;
 
-       cifs_dbg(FYI, "%s: ref_path=%s\n", __func__, ref_path);
+       /* Put initial connections as they might be shared with other mounts.  We need unique dfs
+        * connections per mount to properly failover, so mount_get_dfs_conns() must be used from
+        * now on.
+        */
+       mount_put_conns(mnt_ctx);
+       mount_get_dfs_conns(mnt_ctx);
 
-       if (is_tcon_dfs(tcon)) {
-               *ref_server = true;
-       } else {
-               char *npath;
+       full_path = build_unc_path_to_root(ctx, cifs_sb, true);
+       if (IS_ERR(full_path))
+               return PTR_ERR(full_path);
 
-               npath = dfs_cache_canonical_path(ref_path, cifs_sb->local_nls, cifs_remap(cifs_sb));
-               if (IS_ERR(npath))
-                       return PTR_ERR(npath);
+       mnt_ctx->origin_fullpath = dfs_cache_canonical_path(ctx->UNC, cifs_sb->local_nls,
+                                                           cifs_remap(cifs_sb));
+       if (IS_ERR(mnt_ctx->origin_fullpath)) {
+               rc = PTR_ERR(mnt_ctx->origin_fullpath);
+               mnt_ctx->origin_fullpath = NULL;
+               goto out;
+       }
 
-               rc = dfs_cache_noreq_find(npath, &ref, NULL);
-               kfree(npath);
-               if (rc) {
-                       cifs_dbg(VFS, "%s: dfs_cache_noreq_find: failed (rc=%d)\n", __func__, rc);
-                       return rc;
+       /* Try all dfs root targets */
+       for (rc = -ENOENT, tit = dfs_cache_get_tgt_iterator(root_tl);
+            tit; tit = dfs_cache_get_next_tgt(root_tl, tit)) {
+               rc = connect_dfs_target(mnt_ctx, full_path, mnt_ctx->origin_fullpath + 1, tit);
+               if (!rc) {
+                       mnt_ctx->leaf_fullpath = kstrdup(mnt_ctx->origin_fullpath, GFP_KERNEL);
+                       if (!mnt_ctx->leaf_fullpath)
+                               rc = -ENOMEM;
+                       break;
                }
-               cifs_dbg(FYI, "%s: ref.flags=0x%x\n", __func__, ref.flags);
-               /*
-                * Check if all targets are capable of handling DFS referrals as per
-                * MS-DFSC 2.2.4 RESP_GET_DFS_REFERRAL.
-                */
-               *ref_server = !!(ref.flags & DFSREF_REFERRAL_SERVER);
-               free_dfs_info_param(&ref);
        }
-       return 0;
+
+out:
+       kfree(full_path);
+       return rc;
 }
 
-int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
+static int __follow_dfs_link(struct mount_ctx *mnt_ctx)
 {
-       int rc = 0;
-       unsigned int xid;
-       struct TCP_Server_Info *server = NULL;
-       struct cifs_ses *ses = NULL, *root_ses = NULL;
-       struct cifs_tcon *tcon = NULL;
-       int count = 0;
-       uuid_t mount_id = {0};
-       char *ref_path = NULL, *full_path = NULL;
-       char *oldmnt = NULL;
-       bool ref_server = false;
+       int rc;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       char *full_path;
+       struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
+       struct dfs_cache_tgt_iterator *tit;
 
-       rc = mount_get_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
-       /*
-        * If called with 'nodfs' mount option, then skip DFS resolving.  Otherwise unconditionally
-        * try to get an DFS referral (even cached) to determine whether it is an DFS mount.
-        *
-        * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
-        * to respond with PATH_NOT_COVERED to requests that include the prefix.
-        */
-       if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) ||
-           dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), ctx->UNC + 1, NULL,
-                          NULL)) {
-               if (rc)
-                       goto error;
-               /* Check if it is fully accessible and then mount it */
-               rc = is_path_remote(cifs_sb, ctx, xid, server, tcon);
-               if (!rc)
-                       goto out;
-               if (rc != -EREMOTE)
-                       goto error;
+       full_path = build_unc_path_to_root(ctx, cifs_sb, true);
+       if (IS_ERR(full_path))
+               return PTR_ERR(full_path);
+
+       kfree(mnt_ctx->leaf_fullpath);
+       mnt_ctx->leaf_fullpath = dfs_cache_canonical_path(full_path, cifs_sb->local_nls,
+                                                         cifs_remap(cifs_sb));
+       if (IS_ERR(mnt_ctx->leaf_fullpath)) {
+               rc = PTR_ERR(mnt_ctx->leaf_fullpath);
+               mnt_ctx->leaf_fullpath = NULL;
+               goto out;
        }
 
-       mount_put_conns(cifs_sb, xid, server, ses, tcon);
-       /*
-        * Ignore error check here because we may failover to other targets from cached a
-        * referral.
-        */
-       (void)mount_get_dfs_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
+       /* Get referral from dfs link */
+       rc = dfs_cache_find(mnt_ctx->xid, mnt_ctx->root_ses, cifs_sb->local_nls,
+                           cifs_remap(cifs_sb), mnt_ctx->leaf_fullpath + 1, NULL, &tl);
+       if (rc)
+               goto out;
 
-       /* Get path of DFS root */
-       ref_path = build_unc_path_to_root(ctx, cifs_sb, false);
-       if (IS_ERR(ref_path)) {
-               rc = PTR_ERR(ref_path);
-               ref_path = NULL;
-               goto error;
+       /* Try all dfs link targets */
+       for (rc = -ENOENT, tit = dfs_cache_get_tgt_iterator(&tl);
+            tit; tit = dfs_cache_get_next_tgt(&tl, tit)) {
+               rc = connect_dfs_target(mnt_ctx, full_path, mnt_ctx->leaf_fullpath + 1, tit);
+               if (!rc) {
+                       rc = is_path_remote(mnt_ctx);
+                       break;
+               }
+       }
+
+out:
+       kfree(full_path);
+       dfs_cache_free_tgts(&tl);
+       return rc;
+}
+
+static int follow_dfs_link(struct mount_ctx *mnt_ctx)
+{
+       int rc;
+       struct cifs_sb_info *cifs_sb = mnt_ctx->cifs_sb;
+       struct smb3_fs_context *ctx = mnt_ctx->fs_ctx;
+       char *full_path;
+       int num_links = 0;
+
+       full_path = build_unc_path_to_root(ctx, cifs_sb, true);
+       if (IS_ERR(full_path))
+               return PTR_ERR(full_path);
+
+       kfree(mnt_ctx->origin_fullpath);
+       mnt_ctx->origin_fullpath = dfs_cache_canonical_path(full_path, cifs_sb->local_nls,
+                                                           cifs_remap(cifs_sb));
+       kfree(full_path);
+
+       if (IS_ERR(mnt_ctx->origin_fullpath)) {
+               rc = PTR_ERR(mnt_ctx->origin_fullpath);
+               mnt_ctx->origin_fullpath = NULL;
+               return rc;
        }
 
-       uuid_gen(&mount_id);
-       set_root_ses(cifs_sb, &mount_id, ses, &root_ses);
        do {
-               /* Save full path of last DFS path we used to resolve final target server */
-               kfree(full_path);
-               full_path = build_unc_path_to_root(ctx, cifs_sb, !!count);
-               if (IS_ERR(full_path)) {
-                       rc = PTR_ERR(full_path);
-                       full_path = NULL;
-                       break;
-               }
-               /* Chase referral */
-               oldmnt = cifs_sb->ctx->mount_options;
-               rc = expand_dfs_referral(xid, root_ses, ctx, cifs_sb, ref_path + 1);
-               if (rc)
-                       break;
-               /* Connect to new DFS target only if we were redirected */
-               if (oldmnt != cifs_sb->ctx->mount_options) {
-                       mount_put_conns(cifs_sb, xid, server, ses, tcon);
-                       rc = mount_get_dfs_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
-               }
-               if (rc && !server && !ses) {
-                       /* Failed to connect. Try to connect to other targets in the referral. */
-                       rc = do_dfs_failover(ref_path + 1, full_path, cifs_sb, ctx, root_ses, &xid,
-                                            &server, &ses, &tcon);
-               }
-               if (rc == -EACCES || rc == -EOPNOTSUPP || !server || !ses)
+               rc = __follow_dfs_link(mnt_ctx);
+               if (!rc || rc != -EREMOTE)
                        break;
-               if (!tcon)
-                       continue;
+       } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
 
-               /* Make sure that requests go through new root servers */
-               rc = is_referral_server(ref_path + 1, cifs_sb, tcon, &ref_server);
-               if (rc)
-                       break;
-               if (ref_server)
-                       set_root_ses(cifs_sb, &mount_id, ses, &root_ses);
+       return rc;
+}
+
+/* Set up DFS referral paths for failover */
+static void setup_server_referral_paths(struct mount_ctx *mnt_ctx)
+{
+       struct TCP_Server_Info *server = mnt_ctx->server;
 
-               /* Get next dfs path and then continue chasing them if -EREMOTE */
-               rc = next_dfs_prepath(cifs_sb, ctx, xid, server, tcon, &ref_path);
-               /* Prevent recursion on broken link referrals */
-               if (rc == -EREMOTE && ++count > MAX_NESTED_LINKS)
-                       rc = -ELOOP;
-       } while (rc == -EREMOTE);
+       server->origin_fullpath = mnt_ctx->origin_fullpath;
+       server->leaf_fullpath = mnt_ctx->leaf_fullpath;
+       server->current_fullpath = mnt_ctx->leaf_fullpath;
+       mnt_ctx->origin_fullpath = mnt_ctx->leaf_fullpath = NULL;
+}
 
-       if (rc || !tcon || !ses)
+int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
+{
+       int rc;
+       struct mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };
+       struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
+       bool isdfs;
+
+       rc = is_dfs_mount(&mnt_ctx, &isdfs, &tl);
+       if (rc)
                goto error;
+       if (!isdfs)
+               goto out;
 
-       kfree(ref_path);
-       /*
-        * Store DFS full path in both superblock and tree connect structures.
-        *
-        * For DFS root mounts, the prefix path (cifs_sb->prepath) is preserved during reconnect so
-        * only the root path is set in cifs_sb->origin_fullpath and tcon->dfs_path. And for DFS
-        * links, the prefix path is included in both and may be changed during reconnect.  See
-        * cifs_tree_connect().
-        */
-       ref_path = dfs_cache_canonical_path(full_path, cifs_sb->local_nls, cifs_remap(cifs_sb));
-       kfree(full_path);
-       full_path = NULL;
+       uuid_gen(&mnt_ctx.mount_id);
+       rc = connect_dfs_root(&mnt_ctx, &tl);
+       dfs_cache_free_tgts(&tl);
 
-       if (IS_ERR(ref_path)) {
-               rc = PTR_ERR(ref_path);
-               ref_path = NULL;
+       if (rc)
                goto error;
-       }
-       cifs_sb->origin_fullpath = ref_path;
 
-       ref_path = kstrdup(cifs_sb->origin_fullpath, GFP_KERNEL);
-       if (!ref_path) {
-               rc = -ENOMEM;
+       rc = is_path_remote(&mnt_ctx);
+       if (rc == -EREMOTE)
+               rc = follow_dfs_link(&mnt_ctx);
+       if (rc)
                goto error;
-       }
-       spin_lock(&cifs_tcp_ses_lock);
-       tcon->dfs_path = ref_path;
-       ref_path = NULL;
-       spin_unlock(&cifs_tcp_ses_lock);
 
+       setup_server_referral_paths(&mnt_ctx);
        /*
-        * After reconnecting to a different server, unique ids won't
-        * match anymore, so we disable serverino. This prevents
-        * dentry revalidation to think the dentry are stale (ESTALE).
+        * After reconnecting to a different server, unique ids won't match anymore, so we disable
+        * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
         */
        cifs_autodisable_serverino(cifs_sb);
        /*
-        * Force the use of prefix path to support failover on DFS paths that
-        * resolve to targets that have different prefix paths.
+        * Force the use of prefix path to support failover on DFS paths that resolve to targets
+        * that have different prefix paths.
         */
        cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
        kfree(cifs_sb->prepath);
        cifs_sb->prepath = ctx->prepath;
        ctx->prepath = NULL;
-       uuid_copy(&cifs_sb->dfs_mount_id, &mount_id);
+       uuid_copy(&cifs_sb->dfs_mount_id, &mnt_ctx.mount_id);
 
 out:
-       free_xid(xid);
-       cifs_try_adding_channels(cifs_sb, ses);
-       return mount_setup_tlink(cifs_sb, ses, tcon);
+       free_xid(mnt_ctx.xid);
+       cifs_try_adding_channels(cifs_sb, mnt_ctx.ses);
+       return mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon);
 
 error:
-       kfree(ref_path);
-       kfree(full_path);
-       kfree(cifs_sb->origin_fullpath);
-       dfs_cache_put_refsrv_sessions(&mount_id);
-       mount_put_conns(cifs_sb, xid, server, ses, tcon);
+       dfs_cache_put_refsrv_sessions(&mnt_ctx.mount_id);
+       kfree(mnt_ctx.origin_fullpath);
+       kfree(mnt_ctx.leaf_fullpath);
+       mount_put_conns(&mnt_ctx);
        return rc;
 }
 #else
 int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb3_fs_context *ctx)
 {
        int rc = 0;
-       unsigned int xid;
-       struct cifs_ses *ses;
-       struct cifs_tcon *tcon;
-       struct TCP_Server_Info *server;
+       struct mount_ctx mnt_ctx = { .cifs_sb = cifs_sb, .fs_ctx = ctx, };
 
-       rc = mount_get_conns(ctx, cifs_sb, &xid, &server, &ses, &tcon);
+       rc = mount_get_conns(&mnt_ctx);
        if (rc)
                goto error;
 
-       if (tcon) {
-               rc = is_path_remote(cifs_sb, ctx, xid, server, tcon);
+       if (mnt_ctx.tcon) {
+               rc = is_path_remote(&mnt_ctx);
                if (rc == -EREMOTE)
                        rc = -EOPNOTSUPP;
                if (rc)
                        goto error;
        }
 
-       free_xid(xid);
-
-       return mount_setup_tlink(cifs_sb, ses, tcon);
+       free_xid(mnt_ctx.xid);
+       return mount_setup_tlink(cifs_sb, mnt_ctx.ses, mnt_ctx.tcon);
 
 error:
-       mount_put_conns(cifs_sb, xid, server, ses, tcon);
+       mount_put_conns(&mnt_ctx);
        return rc;
 }
 #endif
@@ -3818,7 +3785,6 @@ cifs_umount(struct cifs_sb_info *cifs_sb)
        kfree(cifs_sb->prepath);
 #ifdef CONFIG_CIFS_DFS_UPCALL
        dfs_cache_put_refsrv_sessions(&cifs_sb->dfs_mount_id);
-       kfree(cifs_sb->origin_fullpath);
 #endif
        call_rcu(&cifs_sb->rcu, delayed_free);
 }
@@ -4145,104 +4111,246 @@ cifs_prune_tlinks(struct work_struct *work)
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
-int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
+static void mark_tcon_tcp_ses_for_reconnect(struct cifs_tcon *tcon)
+{
+       int i;
+
+       for (i = 0; i < tcon->ses->chan_count; i++) {
+               spin_lock(&GlobalMid_Lock);
+               if (tcon->ses->chans[i].server->tcpStatus != CifsExiting)
+                       tcon->ses->chans[i].server->tcpStatus = CifsNeedReconnect;
+               spin_unlock(&GlobalMid_Lock);
+       }
+}
+
+/* Update dfs referral path of superblock */
+static int update_server_fullpath(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb,
+                                 const char *target)
+{
+       int rc = 0;
+       size_t len = strlen(target);
+       char *refpath, *npath;
+
+       if (unlikely(len < 2 || *target != '\\'))
+               return -EINVAL;
+
+       if (target[1] == '\\') {
+               len += 1;
+               refpath = kmalloc(len, GFP_KERNEL);
+               if (!refpath)
+                       return -ENOMEM;
+
+               scnprintf(refpath, len, "%s", target);
+       } else {
+               len += sizeof("\\");
+               refpath = kmalloc(len, GFP_KERNEL);
+               if (!refpath)
+                       return -ENOMEM;
+
+               scnprintf(refpath, len, "\\%s", target);
+       }
+
+       npath = dfs_cache_canonical_path(refpath, cifs_sb->local_nls, cifs_remap(cifs_sb));
+       kfree(refpath);
+
+       if (IS_ERR(npath)) {
+               rc = PTR_ERR(npath);
+       } else {
+               mutex_lock(&server->refpath_lock);
+               kfree(server->leaf_fullpath);
+               server->leaf_fullpath = npath;
+               mutex_unlock(&server->refpath_lock);
+               server->current_fullpath = server->leaf_fullpath;
+       }
+       return rc;
+}
+
+static int target_share_matches_server(struct TCP_Server_Info *server, const char *tcp_host,
+                                      size_t tcp_host_len, char *share, bool *target_match)
+{
+       int rc = 0;
+       const char *dfs_host;
+       size_t dfs_host_len;
+
+       *target_match = true;
+       extract_unc_hostname(share, &dfs_host, &dfs_host_len);
+
+       /* Check if hostnames or addresses match */
+       if (dfs_host_len != tcp_host_len || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
+               cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len,
+                        dfs_host, (int)tcp_host_len, tcp_host);
+               rc = match_target_ip(server, dfs_host, dfs_host_len, target_match);
+               if (rc)
+                       cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
+       }
+       return rc;
+}
+
+static int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
+                                    struct cifs_sb_info *cifs_sb, char *tree, bool islink,
+                                    struct dfs_cache_tgt_list *tl)
 {
        int rc;
        struct TCP_Server_Info *server = tcon->ses->server;
        const struct smb_version_operations *ops = server->ops;
-       struct dfs_cache_tgt_list tl;
-       struct dfs_cache_tgt_iterator *it = NULL;
-       char *tree;
+       struct cifs_tcon *ipc = tcon->ses->tcon_ipc;
+       char *share = NULL, *prefix = NULL;
        const char *tcp_host;
        size_t tcp_host_len;
-       const char *dfs_host;
-       size_t dfs_host_len;
-       char *share = NULL, *prefix = NULL;
-       struct dfs_info3_param ref = {0};
-       bool isroot;
+       struct dfs_cache_tgt_iterator *tit;
+       bool target_match;
 
-       tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
-       if (!tree)
-               return -ENOMEM;
+       extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
 
-       /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
-       if (!tcon->dfs_path || dfs_cache_noreq_find(tcon->dfs_path + 1, &ref, &tl)) {
-               if (tcon->ipc) {
-                       scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
-                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
-               } else {
-                       rc = ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc);
-               }
+       tit = dfs_cache_get_tgt_iterator(tl);
+       if (!tit) {
+               rc = -ENOENT;
                goto out;
        }
 
-       isroot = ref.server_type == DFS_TYPE_ROOT;
-       free_dfs_info_param(&ref);
-
-       extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
-
-       for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) {
-               bool target_match;
+       /* Try to tree connect to all dfs targets */
+       for (; tit; tit = dfs_cache_get_next_tgt(tl, tit)) {
+               const char *target = dfs_cache_get_tgt_name(tit);
+               struct dfs_cache_tgt_list ntl = DFS_CACHE_TGT_LIST_INIT(ntl);
 
                kfree(share);
                kfree(prefix);
-               share = NULL;
-               prefix = NULL;
+               share = prefix = NULL;
 
-               rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix);
+               /* Check if share matches with tcp ses */
+               rc = dfs_cache_get_tgt_share(server->current_fullpath + 1, tit, &share, &prefix);
                if (rc) {
-                       cifs_dbg(VFS, "%s: failed to parse target share %d\n",
-                                __func__, rc);
-                       continue;
+                       cifs_dbg(VFS, "%s: failed to parse target share: %d\n", __func__, rc);
+                       break;
                }
 
-               extract_unc_hostname(share, &dfs_host, &dfs_host_len);
-
-               if (dfs_host_len != tcp_host_len
-                   || strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
-                       cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len,
-                                dfs_host, (int)tcp_host_len, tcp_host);
+               rc = target_share_matches_server(server, tcp_host, tcp_host_len, share,
+                                                &target_match);
+               if (rc)
+                       break;
+               if (!target_match) {
+                       rc = -EHOSTUNREACH;
+                       continue;
+               }
 
-                       rc = match_target_ip(server, dfs_host, dfs_host_len, &target_match);
-                       if (rc) {
-                               cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
+               if (ipc->need_reconnect) {
+                       scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
+                       rc = ops->tree_connect(xid, ipc->ses, tree, ipc, cifs_sb->local_nls);
+                       if (rc)
                                break;
-                       }
-
-                       if (!target_match) {
-                               cifs_dbg(FYI, "%s: skipping target\n", __func__);
-                               continue;
-                       }
                }
 
-               if (tcon->ipc) {
-                       scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", share);
-                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
+               scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
+               if (!islink) {
+                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
+                       break;
+               }
+               /*
+                * If no dfs referrals were returned from link target, then just do a TREE_CONNECT
+                * to it.  Otherwise, cache the dfs referral and then mark current tcp ses for
+                * reconnect so either the demultiplex thread or the echo worker will reconnect to
+                * newly resolved target.
+                */
+               if (dfs_cache_find(xid, tcon->ses, cifs_sb->local_nls, cifs_remap(cifs_sb), target,
+                                  NULL, &ntl)) {
+                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, cifs_sb->local_nls);
+                       if (rc)
+                               continue;
+                       rc = dfs_cache_noreq_update_tgthint(server->current_fullpath + 1, tit);
+                       if (!rc)
+                               rc = cifs_update_super_prepath(cifs_sb, prefix);
                } else {
-                       scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
-                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
-                       /* Only handle prefix paths of DFS link targets */
-                       if (!rc && !isroot) {
-                               rc = update_super_prepath(tcon, prefix);
-                               break;
-                       }
+                       /* Target is another dfs share */
+                       rc = update_server_fullpath(server, cifs_sb, target);
+                       dfs_cache_free_tgts(tl);
+
+                       if (!rc) {
+                               rc = -EREMOTE;
+                               list_replace_init(&ntl.tl_list, &tl->tl_list);
+                       } else
+                               dfs_cache_free_tgts(&ntl);
                }
-               if (rc == -EREMOTE)
-                       break;
+               break;
        }
 
+out:
        kfree(share);
        kfree(prefix);
 
-       if (!rc) {
-               if (it)
-                       rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it);
-               else
-                       rc = -ENOENT;
+       return rc;
+}
+
+static int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
+                                  struct cifs_sb_info *cifs_sb, char *tree, bool islink,
+                                  struct dfs_cache_tgt_list *tl)
+{
+       int rc;
+       int num_links = 0;
+       struct TCP_Server_Info *server = tcon->ses->server;
+
+       do {
+               rc = __tree_connect_dfs_target(xid, tcon, cifs_sb, tree, islink, tl);
+               if (!rc || rc != -EREMOTE)
+                       break;
+       } while (rc = -ELOOP, ++num_links < MAX_NESTED_LINKS);
+       /*
+        * If we couldn't tree connect to any targets from last referral path, then retry from
+        * original referral path.
+        */
+       if (rc && server->current_fullpath != server->origin_fullpath) {
+               server->current_fullpath = server->origin_fullpath;
+               mark_tcon_tcp_ses_for_reconnect(tcon);
        }
-       dfs_cache_free_tgts(&tl);
+
+       dfs_cache_free_tgts(tl);
+       return rc;
+}
+
+int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
+{
+       int rc;
+       struct TCP_Server_Info *server = tcon->ses->server;
+       const struct smb_version_operations *ops = server->ops;
+       struct super_block *sb = NULL;
+       struct cifs_sb_info *cifs_sb;
+       struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
+       char *tree;
+       struct dfs_info3_param ref = {0};
+
+       tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
+       if (!tree)
+               return -ENOMEM;
+
+       if (tcon->ipc) {
+               scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
+               rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
+               goto out;
+       }
+
+       sb = cifs_get_tcp_super(server);
+       if (IS_ERR(sb)) {
+               rc = PTR_ERR(sb);
+               cifs_dbg(VFS, "%s: could not find superblock: %d\n", __func__, rc);
+               goto out;
+       }
+
+       cifs_sb = CIFS_SB(sb);
+
+       /* If it is not dfs or there was no cached dfs referral, then reconnect to same share */
+       if (!server->current_fullpath ||
+           dfs_cache_noreq_find(server->current_fullpath + 1, &ref, &tl)) {
+               rc = ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, cifs_sb->local_nls);
+               goto out;
+       }
+
+       rc = tree_connect_dfs_target(xid, tcon, cifs_sb, tree, ref.server_type == DFS_TYPE_LINK,
+                                    &tl);
+       free_dfs_info_param(&ref);
+
 out:
        kfree(tree);
+       cifs_put_tcp_super(sb);
+
        return rc;
 }
 #else
index 2837455..5c1259d 100644 (file)
@@ -283,7 +283,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
                        seq_printf(m,
                                   "cache entry: path=%s,type=%s,ttl=%d,etime=%ld,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n",
                                   ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link",
-                                  ce->ttl, ce->etime.tv_nsec, ce->ref_flags, ce->hdr_flags,
+                                  ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags,
                                   IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
                                   ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
 
@@ -1364,9 +1364,9 @@ static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cach
 }
 
 /* Refresh dfs referral of tcon and mark it for reconnect if needed */
-static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
+static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
+                         bool force_refresh)
 {
-       const char *path = tcon->dfs_path + 1;
        struct cifs_ses *ses;
        struct cache_entry *ce;
        struct dfs_info3_param *refs = NULL;
@@ -1422,6 +1422,20 @@ out:
        return rc;
 }
 
+static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
+{
+       struct TCP_Server_Info *server = tcon->ses->server;
+
+       mutex_lock(&server->refpath_lock);
+       if (strcasecmp(server->leaf_fullpath, server->origin_fullpath))
+               __refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, force_refresh);
+       mutex_unlock(&server->refpath_lock);
+
+       __refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh);
+
+       return 0;
+}
+
 /**
  * dfs_cache_remount_fs - remount a DFS share
  *
@@ -1435,6 +1449,7 @@ out:
 int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
 {
        struct cifs_tcon *tcon;
+       struct TCP_Server_Info *server;
        struct mount_group *mg;
        struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
        int rc;
@@ -1443,13 +1458,15 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
                return -EINVAL;
 
        tcon = cifs_sb_master_tcon(cifs_sb);
-       if (!tcon->dfs_path) {
-               cifs_dbg(FYI, "%s: not a dfs tcon\n", __func__);
+       server = tcon->ses->server;
+
+       if (!server->origin_fullpath) {
+               cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
                return 0;
        }
 
        if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
-               cifs_dbg(FYI, "%s: tcon has no dfs mount group id\n", __func__);
+               cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
                return -EINVAL;
        }
 
@@ -1457,7 +1474,7 @@ int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
        mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
        if (IS_ERR(mg)) {
                mutex_unlock(&mount_group_list_lock);
-               cifs_dbg(FYI, "%s: tcon has ipc session to refresh referral\n", __func__);
+               cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
                return PTR_ERR(mg);
        }
        kref_get(&mg->refcount);
@@ -1498,9 +1515,12 @@ static void refresh_mounts(struct cifs_ses **sessions)
 
        spin_lock(&cifs_tcp_ses_lock);
        list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+               if (!server->is_dfs_conn)
+                       continue;
+
                list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
                        list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
-                               if (tcon->dfs_path) {
+                               if (!tcon->ipc && !tcon->need_reconnect) {
                                        tcon->tc_count++;
                                        list_add_tail(&tcon->ulist, &tcons);
                                }
@@ -1510,8 +1530,16 @@ static void refresh_mounts(struct cifs_ses **sessions)
        spin_unlock(&cifs_tcp_ses_lock);
 
        list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
+               struct TCP_Server_Info *server = tcon->ses->server;
+
                list_del_init(&tcon->ulist);
-               refresh_tcon(sessions, tcon, false);
+
+               mutex_lock(&server->refpath_lock);
+               if (strcasecmp(server->leaf_fullpath, server->origin_fullpath))
+                       __refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
+               mutex_unlock(&server->refpath_lock);
+
+               __refresh_tcon(server->origin_fullpath + 1, sessions, tcon, false);
                cifs_put_tcon(tcon);
        }
 }
index 1b855fc..9fee3af 100644 (file)
@@ -2692,12 +2692,23 @@ int cifs_strict_fsync(struct file *file, loff_t start, loff_t end,
        tcon = tlink_tcon(smbfile->tlink);
        if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC)) {
                server = tcon->ses->server;
-               if (server->ops->flush)
-                       rc = server->ops->flush(xid, tcon, &smbfile->fid);
-               else
+               if (server->ops->flush == NULL) {
                        rc = -ENOSYS;
+                       goto strict_fsync_exit;
+               }
+
+               if ((OPEN_FMODE(smbfile->f_flags) & FMODE_WRITE) == 0) {
+                       smbfile = find_writable_file(CIFS_I(inode), FIND_WR_ANY);
+                       if (smbfile) {
+                               rc = server->ops->flush(xid, tcon, &smbfile->fid);
+                               cifsFileInfo_put(smbfile);
+                       } else
+                               cifs_dbg(FYI, "ignore fsync for file not open for write\n");
+               } else
+                       rc = server->ops->flush(xid, tcon, &smbfile->fid);
        }
 
+strict_fsync_exit:
        free_xid(xid);
        return rc;
 }
@@ -2709,6 +2720,7 @@ int cifs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
        struct cifs_tcon *tcon;
        struct TCP_Server_Info *server;
        struct cifsFileInfo *smbfile = file->private_data;
+       struct inode *inode = file_inode(file);
        struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file);
 
        rc = file_write_and_wait_range(file, start, end);
@@ -2725,12 +2737,23 @@ int cifs_fsync(struct file *file, loff_t start, loff_t end, int datasync)
        tcon = tlink_tcon(smbfile->tlink);
        if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC)) {
                server = tcon->ses->server;
-               if (server->ops->flush)
-                       rc = server->ops->flush(xid, tcon, &smbfile->fid);
-               else
+               if (server->ops->flush == NULL) {
                        rc = -ENOSYS;
+                       goto fsync_exit;
+               }
+
+               if ((OPEN_FMODE(smbfile->f_flags) & FMODE_WRITE) == 0) {
+                       smbfile = find_writable_file(CIFS_I(inode), FIND_WR_ANY);
+                       if (smbfile) {
+                               rc = server->ops->flush(xid, tcon, &smbfile->fid);
+                               cifsFileInfo_put(smbfile);
+                       } else
+                               cifs_dbg(FYI, "ignore fsync for file not open for write\n");
+               } else
+                       rc = server->ops->flush(xid, tcon, &smbfile->fid);
        }
 
+fsync_exit:
        free_xid(xid);
        return rc;
 }
index 38d96a4..6a179ae 100644 (file)
@@ -308,7 +308,9 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
        new_ctx->nodename = NULL;
        new_ctx->username = NULL;
        new_ctx->password = NULL;
+       new_ctx->server_hostname = NULL;
        new_ctx->domainname = NULL;
+       new_ctx->workstation_name = NULL;
        new_ctx->UNC = NULL;
        new_ctx->source = NULL;
        new_ctx->iocharset = NULL;
@@ -323,6 +325,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
        DUP_CTX_STR(UNC);
        DUP_CTX_STR(source);
        DUP_CTX_STR(domainname);
+       DUP_CTX_STR(workstation_name);
        DUP_CTX_STR(nodename);
        DUP_CTX_STR(iocharset);
 
@@ -459,6 +462,7 @@ smb3_parse_devname(const char *devname, struct smb3_fs_context *ctx)
                return -EINVAL;
 
        /* record the server hostname */
+       kfree(ctx->server_hostname);
        ctx->server_hostname = kstrndup(devname + 2, pos - devname - 2, GFP_KERNEL);
        if (!ctx->server_hostname)
                return -ENOMEM;
@@ -720,6 +724,11 @@ static int smb3_verify_reconfigure_ctx(struct fs_context *fc,
                cifs_errorf(fc, "can not change domainname during remount\n");
                return -EINVAL;
        }
+       if (new_ctx->workstation_name &&
+           (!old_ctx->workstation_name || strcmp(new_ctx->workstation_name, old_ctx->workstation_name))) {
+               cifs_errorf(fc, "can not change workstation_name during remount\n");
+               return -EINVAL;
+       }
        if (new_ctx->nodename &&
            (!old_ctx->nodename || strcmp(new_ctx->nodename, old_ctx->nodename))) {
                cifs_errorf(fc, "can not change nodename during remount\n");
@@ -753,7 +762,8 @@ static int smb3_reconfigure(struct fs_context *fc)
                return rc;
 
        /*
-        * We can not change UNC/username/password/domainname/nodename/iocharset
+        * We can not change UNC/username/password/domainname/
+        * workstation_name/nodename/iocharset
         * during reconnect so ignore what we have in the new context and
         * just use what we already have in cifs_sb->ctx.
         */
@@ -762,6 +772,7 @@ static int smb3_reconfigure(struct fs_context *fc)
        STEAL_STRING(cifs_sb, ctx, username);
        STEAL_STRING(cifs_sb, ctx, password);
        STEAL_STRING(cifs_sb, ctx, domainname);
+       STEAL_STRING(cifs_sb, ctx, workstation_name);
        STEAL_STRING(cifs_sb, ctx, nodename);
        STEAL_STRING(cifs_sb, ctx, iocharset);
 
@@ -1414,13 +1425,22 @@ static int smb3_fs_context_parse_param(struct fs_context *fc,
 
 int smb3_init_fs_context(struct fs_context *fc)
 {
+       int rc;
        struct smb3_fs_context *ctx;
        char *nodename = utsname()->nodename;
        int i;
 
        ctx = kzalloc(sizeof(struct smb3_fs_context), GFP_KERNEL);
-       if (unlikely(!ctx))
-               return -ENOMEM;
+       if (unlikely(!ctx)) {
+               rc = -ENOMEM;
+               goto err_exit;
+       }
+
+       ctx->workstation_name = kstrdup(nodename, GFP_KERNEL);
+       if (unlikely(!ctx->workstation_name)) {
+               rc = -ENOMEM;
+               goto err_exit;
+       }
 
        /*
         * does not have to be perfect mapping since field is
@@ -1493,6 +1513,14 @@ int smb3_init_fs_context(struct fs_context *fc)
        fc->fs_private = ctx;
        fc->ops = &smb3_fs_context_ops;
        return 0;
+
+err_exit:
+       if (ctx) {
+               kfree(ctx->workstation_name);
+               kfree(ctx);
+       }
+
+       return rc;
 }
 
 void
@@ -1518,6 +1546,8 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
        ctx->source = NULL;
        kfree(ctx->domainname);
        ctx->domainname = NULL;
+       kfree(ctx->workstation_name);
+       ctx->workstation_name = NULL;
        kfree(ctx->nodename);
        ctx->nodename = NULL;
        kfree(ctx->iocharset);
index b2d22cf..e54090d 100644 (file)
@@ -170,6 +170,7 @@ struct smb3_fs_context {
        char *server_hostname;
        char *UNC;
        char *nodename;
+       char *workstation_name;
        char *iocharset;  /* local code page for mapping to and from Unicode */
        char source_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* clnt nb name */
        char target_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* srvr nb name */
index 8eedd20..7e409a3 100644 (file)
@@ -87,6 +87,14 @@ void cifs_fscache_get_super_cookie(struct cifs_tcon *tcon)
        char *sharename;
        struct cifs_fscache_super_auxdata auxdata;
 
+       /*
+        * Check if cookie was already initialized so don't reinitialize it.
+        * In the future, as we integrate with newer fscache features,
+        * we may want to instead add a check if cookie has changed
+        */
+       if (tcon->fscache == NULL)
+               return;
+
        sharename = extract_sharename(tcon->treeName);
        if (IS_ERR(sharename)) {
                cifs_dbg(FYI, "%s: couldn't extract sharename\n", __func__);
index ba2c3e8..5148d48 100644 (file)
@@ -75,6 +75,7 @@ sesInfoAlloc(void)
                INIT_LIST_HEAD(&ret_buf->tcon_list);
                mutex_init(&ret_buf->session_mutex);
                spin_lock_init(&ret_buf->iface_lock);
+               spin_lock_init(&ret_buf->chan_lock);
        }
        return ret_buf;
 }
@@ -94,6 +95,7 @@ sesInfoFree(struct cifs_ses *buf_to_free)
        kfree_sensitive(buf_to_free->password);
        kfree(buf_to_free->user_name);
        kfree(buf_to_free->domainName);
+       kfree(buf_to_free->workstation_name);
        kfree_sensitive(buf_to_free->auth_key.response);
        kfree(buf_to_free->iface_list);
        kfree_sensitive(buf_to_free);
@@ -138,9 +140,6 @@ tconInfoFree(struct cifs_tcon *buf_to_free)
        kfree(buf_to_free->nativeFileSystem);
        kfree_sensitive(buf_to_free->password);
        kfree(buf_to_free->crfid.fid);
-#ifdef CONFIG_CIFS_DFS_UPCALL
-       kfree(buf_to_free->dfs_path);
-#endif
        kfree(buf_to_free);
 }
 
@@ -1287,69 +1286,20 @@ out:
        return rc;
 }
 
-static void tcon_super_cb(struct super_block *sb, void *arg)
+int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix)
 {
-       struct super_cb_data *sd = arg;
-       struct cifs_tcon *tcon = sd->data;
-       struct cifs_sb_info *cifs_sb;
-
-       if (sd->sb)
-               return;
-
-       cifs_sb = CIFS_SB(sb);
-       if (tcon->dfs_path && cifs_sb->origin_fullpath &&
-           !strcasecmp(tcon->dfs_path, cifs_sb->origin_fullpath))
-               sd->sb = sb;
-}
-
-static inline struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon)
-{
-       return __cifs_get_super(tcon_super_cb, tcon);
-}
-
-static inline void cifs_put_tcon_super(struct super_block *sb)
-{
-       __cifs_put_super(sb);
-}
-#else
-static inline struct super_block *cifs_get_tcon_super(struct cifs_tcon *tcon)
-{
-       return ERR_PTR(-EOPNOTSUPP);
-}
-
-static inline void cifs_put_tcon_super(struct super_block *sb)
-{
-}
-#endif
-
-int update_super_prepath(struct cifs_tcon *tcon, char *prefix)
-{
-       struct super_block *sb;
-       struct cifs_sb_info *cifs_sb;
-       int rc = 0;
-
-       sb = cifs_get_tcon_super(tcon);
-       if (IS_ERR(sb))
-               return PTR_ERR(sb);
-
-       cifs_sb = CIFS_SB(sb);
-
        kfree(cifs_sb->prepath);
 
        if (prefix && *prefix) {
                cifs_sb->prepath = kstrdup(prefix, GFP_ATOMIC);
-               if (!cifs_sb->prepath) {
-                       rc = -ENOMEM;
-                       goto out;
-               }
+               if (!cifs_sb->prepath)
+                       return -ENOMEM;
 
                convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb));
        } else
                cifs_sb->prepath = NULL;
 
        cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
-
-out:
-       cifs_put_tcon_super(sb);
-       return rc;
+       return 0;
 }
+#endif
index 25a2b8e..fe707f4 100644 (file)
@@ -119,7 +119,9 @@ typedef struct _AUTHENTICATE_MESSAGE {
  */
 
 int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len, struct cifs_ses *ses);
-void build_ntlmssp_negotiate_blob(unsigned char *pbuffer, struct cifs_ses *ses);
+int build_ntlmssp_negotiate_blob(unsigned char **pbuffer, u16 *buflen,
+                                struct cifs_ses *ses,
+                                const struct nls_table *nls_cp);
 int build_ntlmssp_auth_blob(unsigned char **pbuffer, u16 *buflen,
                        struct cifs_ses *ses,
                        const struct nls_table *nls_cp);
index 23e02db..2c10b18 100644 (file)
@@ -54,41 +54,53 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
 {
        int i;
 
+       spin_lock(&ses->chan_lock);
        for (i = 0; i < ses->chan_count; i++) {
-               if (is_server_using_iface(ses->chans[i].server, iface))
+               if (is_server_using_iface(ses->chans[i].server, iface)) {
+                       spin_unlock(&ses->chan_lock);
                        return true;
+               }
        }
+       spin_unlock(&ses->chan_lock);
        return false;
 }
 
 /* returns number of channels added */
 int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
 {
-       int old_chan_count = ses->chan_count;
-       int left = ses->chan_max - ses->chan_count;
+       int old_chan_count, new_chan_count;
+       int left;
        int i = 0;
        int rc = 0;
        int tries = 0;
        struct cifs_server_iface *ifaces = NULL;
        size_t iface_count;
 
+       if (ses->server->dialect < SMB30_PROT_ID) {
+               cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
+               return 0;
+       }
+
+       spin_lock(&ses->chan_lock);
+
+       new_chan_count = old_chan_count = ses->chan_count;
+       left = ses->chan_max - ses->chan_count;
+
        if (left <= 0) {
                cifs_dbg(FYI,
                         "ses already at max_channels (%zu), nothing to open\n",
                         ses->chan_max);
-               return 0;
-       }
-
-       if (ses->server->dialect < SMB30_PROT_ID) {
-               cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
+               spin_unlock(&ses->chan_lock);
                return 0;
        }
 
        if (!(ses->server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
                cifs_dbg(VFS, "server %s does not support multichannel\n", ses->server->hostname);
                ses->chan_max = 1;
+               spin_unlock(&ses->chan_lock);
                return 0;
        }
+       spin_unlock(&ses->chan_lock);
 
        /*
         * Make a copy of the iface list at the time and use that
@@ -142,10 +154,11 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
                cifs_dbg(FYI, "successfully opened new channel on iface#%d\n",
                         i);
                left--;
+               new_chan_count++;
        }
 
        kfree(ifaces);
-       return ses->chan_count - old_chan_count;
+       return new_chan_count - old_chan_count;
 }
 
 /*
@@ -157,10 +170,14 @@ cifs_ses_find_chan(struct cifs_ses *ses, struct TCP_Server_Info *server)
 {
        int i;
 
+       spin_lock(&ses->chan_lock);
        for (i = 0; i < ses->chan_count; i++) {
-               if (ses->chans[i].server == server)
+               if (ses->chans[i].server == server) {
+                       spin_unlock(&ses->chan_lock);
                        return &ses->chans[i];
+               }
        }
+       spin_unlock(&ses->chan_lock);
        return NULL;
 }
 
@@ -168,6 +185,7 @@ static int
 cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
                     struct cifs_server_iface *iface)
 {
+       struct TCP_Server_Info *chan_server;
        struct cifs_chan *chan;
        struct smb3_fs_context ctx = {NULL};
        static const char unc_fmt[] = "\\%s\\foo";
@@ -240,18 +258,19 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
               SMB2_CLIENT_GUID_SIZE);
        ctx.use_client_guid = true;
 
-       mutex_lock(&ses->session_mutex);
+       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->server = cifs_get_tcp_session(&ctx);
+       chan->server = chan_server;
        if (IS_ERR(chan->server)) {
                rc = PTR_ERR(chan->server);
                chan->server = NULL;
+               spin_unlock(&ses->chan_lock);
                goto out;
        }
-       spin_lock(&cifs_tcp_ses_lock);
-       chan->server->is_channel = true;
-       spin_unlock(&cifs_tcp_ses_lock);
+       spin_unlock(&ses->chan_lock);
 
        /*
         * We need to allocate the server crypto now as we will need
@@ -283,8 +302,11 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
         * ses to the new server.
         */
 
+       spin_lock(&ses->chan_lock);
        ses->chan_count++;
        atomic_set(&ses->chan_seq, 0);
+       spin_unlock(&ses->chan_lock);
+
 out:
        ses->binding = false;
        ses->binding_chan = NULL;
@@ -599,18 +621,85 @@ int decode_ntlmssp_challenge(char *bcc_ptr, int blob_len,
        return 0;
 }
 
+static int size_of_ntlmssp_blob(struct cifs_ses *ses, int base_size)
+{
+       int sz = base_size + ses->auth_key.len
+               - CIFS_SESS_KEY_SIZE + CIFS_CPHTXT_SIZE + 2;
+
+       if (ses->domainName)
+               sz += sizeof(__le16) * strnlen(ses->domainName, CIFS_MAX_DOMAINNAME_LEN);
+       else
+               sz += sizeof(__le16);
+
+       if (ses->user_name)
+               sz += sizeof(__le16) * strnlen(ses->user_name, CIFS_MAX_USERNAME_LEN);
+       else
+               sz += sizeof(__le16);
+
+       sz += sizeof(__le16) * strnlen(ses->workstation_name, CIFS_MAX_WORKSTATION_LEN);
+
+       return sz;
+}
+
+static inline void cifs_security_buffer_from_str(SECURITY_BUFFER *pbuf,
+                                                char *str_value,
+                                                int str_length,
+                                                unsigned char *pstart,
+                                                unsigned char **pcur,
+                                                const struct nls_table *nls_cp)
+{
+       unsigned char *tmp = pstart;
+       int len;
+
+       if (!pbuf)
+               return;
+
+       if (!pcur)
+               pcur = &tmp;
+
+       if (!str_value) {
+               pbuf->BufferOffset = cpu_to_le32(*pcur - pstart);
+               pbuf->Length = 0;
+               pbuf->MaximumLength = 0;
+               *pcur += sizeof(__le16);
+       } else {
+               len = cifs_strtoUTF16((__le16 *)*pcur,
+                                     str_value,
+                                     str_length,
+                                     nls_cp);
+               len *= sizeof(__le16);
+               pbuf->BufferOffset = cpu_to_le32(*pcur - pstart);
+               pbuf->Length = cpu_to_le16(len);
+               pbuf->MaximumLength = cpu_to_le16(len);
+               *pcur += len;
+       }
+}
+
 /* BB Move to ntlmssp.c eventually */
 
-/* We do not malloc the blob, it is passed in pbuffer, because
-   it is fixed size, and small, making this approach cleaner */
-void build_ntlmssp_negotiate_blob(unsigned char *pbuffer,
-                                        struct cifs_ses *ses)
+int build_ntlmssp_negotiate_blob(unsigned char **pbuffer,
+                                u16 *buflen,
+                                struct cifs_ses *ses,
+                                const struct nls_table *nls_cp)
 {
+       int rc = 0;
        struct TCP_Server_Info *server = cifs_ses_server(ses);
-       NEGOTIATE_MESSAGE *sec_blob = (NEGOTIATE_MESSAGE *)pbuffer;
+       NEGOTIATE_MESSAGE *sec_blob;
        __u32 flags;
+       unsigned char *tmp;
+       int len;
+
+       len = size_of_ntlmssp_blob(ses, sizeof(NEGOTIATE_MESSAGE));
+       *pbuffer = kmalloc(len, GFP_KERNEL);
+       if (!*pbuffer) {
+               rc = -ENOMEM;
+               cifs_dbg(VFS, "Error %d during NTLMSSP allocation\n", rc);
+               *buflen = 0;
+               goto setup_ntlm_neg_ret;
+       }
+       sec_blob = (NEGOTIATE_MESSAGE *)*pbuffer;
 
-       memset(pbuffer, 0, sizeof(NEGOTIATE_MESSAGE));
+       memset(*pbuffer, 0, sizeof(NEGOTIATE_MESSAGE));
        memcpy(sec_blob->Signature, NTLMSSP_SIGNATURE, 8);
        sec_blob->MessageType = NtLmNegotiate;
 
@@ -624,34 +713,25 @@ void build_ntlmssp_negotiate_blob(unsigned char *pbuffer,
        if (!server->session_estab || ses->ntlmssp->sesskey_per_smbsess)
                flags |= NTLMSSP_NEGOTIATE_KEY_XCH;
 
+       tmp = *pbuffer + sizeof(NEGOTIATE_MESSAGE);
        sec_blob->NegotiateFlags = cpu_to_le32(flags);
 
-       sec_blob->WorkstationName.BufferOffset = 0;
-       sec_blob->WorkstationName.Length = 0;
-       sec_blob->WorkstationName.MaximumLength = 0;
+       /* these fields should be null in negotiate phase MS-NLMP 3.1.5.1.1 */
+       cifs_security_buffer_from_str(&sec_blob->DomainName,
+                                     NULL,
+                                     CIFS_MAX_DOMAINNAME_LEN,
+                                     *pbuffer, &tmp,
+                                     nls_cp);
 
-       /* Domain name is sent on the Challenge not Negotiate NTLMSSP request */
-       sec_blob->DomainName.BufferOffset = 0;
-       sec_blob->DomainName.Length = 0;
-       sec_blob->DomainName.MaximumLength = 0;
-}
-
-static int size_of_ntlmssp_blob(struct cifs_ses *ses)
-{
-       int sz = sizeof(AUTHENTICATE_MESSAGE) + ses->auth_key.len
-               - CIFS_SESS_KEY_SIZE + CIFS_CPHTXT_SIZE + 2;
-
-       if (ses->domainName)
-               sz += 2 * strnlen(ses->domainName, CIFS_MAX_DOMAINNAME_LEN);
-       else
-               sz += 2;
-
-       if (ses->user_name)
-               sz += 2 * strnlen(ses->user_name, CIFS_MAX_USERNAME_LEN);
-       else
-               sz += 2;
+       cifs_security_buffer_from_str(&sec_blob->WorkstationName,
+                                     NULL,
+                                     CIFS_MAX_WORKSTATION_LEN,
+                                     *pbuffer, &tmp,
+                                     nls_cp);
 
-       return sz;
+       *buflen = tmp - *pbuffer;
+setup_ntlm_neg_ret:
+       return rc;
 }
 
 int build_ntlmssp_auth_blob(unsigned char **pbuffer,
@@ -663,6 +743,7 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer,
        AUTHENTICATE_MESSAGE *sec_blob;
        __u32 flags;
        unsigned char *tmp;
+       int len;
 
        rc = setup_ntlmv2_rsp(ses, nls_cp);
        if (rc) {
@@ -670,7 +751,9 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer,
                *buflen = 0;
                goto setup_ntlmv2_ret;
        }
-       *pbuffer = kmalloc(size_of_ntlmssp_blob(ses), GFP_KERNEL);
+
+       len = size_of_ntlmssp_blob(ses, sizeof(AUTHENTICATE_MESSAGE));
+       *pbuffer = kmalloc(len, GFP_KERNEL);
        if (!*pbuffer) {
                rc = -ENOMEM;
                cifs_dbg(VFS, "Error %d during NTLMSSP allocation\n", rc);
@@ -686,7 +769,7 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer,
                NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_TARGET_INFO |
                NTLMSSP_NEGOTIATE_128 | NTLMSSP_NEGOTIATE_UNICODE |
                NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_EXTENDED_SEC |
-               NTLMSSP_NEGOTIATE_SEAL;
+               NTLMSSP_NEGOTIATE_SEAL | NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED;
        if (ses->server->sign)
                flags |= NTLMSSP_NEGOTIATE_SIGN;
        if (!ses->server->session_estab || ses->ntlmssp->sesskey_per_smbsess)
@@ -719,42 +802,23 @@ int build_ntlmssp_auth_blob(unsigned char **pbuffer,
                sec_blob->NtChallengeResponse.MaximumLength = 0;
        }
 
-       if (ses->domainName == NULL) {
-               sec_blob->DomainName.BufferOffset = cpu_to_le32(tmp - *pbuffer);
-               sec_blob->DomainName.Length = 0;
-               sec_blob->DomainName.MaximumLength = 0;
-               tmp += 2;
-       } else {
-               int len;
-               len = cifs_strtoUTF16((__le16 *)tmp, ses->domainName,
-                                     CIFS_MAX_DOMAINNAME_LEN, nls_cp);
-               len *= 2; /* unicode is 2 bytes each */
-               sec_blob->DomainName.BufferOffset = cpu_to_le32(tmp - *pbuffer);
-               sec_blob->DomainName.Length = cpu_to_le16(len);
-               sec_blob->DomainName.MaximumLength = cpu_to_le16(len);
-               tmp += len;
-       }
+       cifs_security_buffer_from_str(&sec_blob->DomainName,
+                                     ses->domainName,
+                                     CIFS_MAX_DOMAINNAME_LEN,
+                                     *pbuffer, &tmp,
+                                     nls_cp);
 
-       if (ses->user_name == NULL) {
-               sec_blob->UserName.BufferOffset = cpu_to_le32(tmp - *pbuffer);
-               sec_blob->UserName.Length = 0;
-               sec_blob->UserName.MaximumLength = 0;
-               tmp += 2;
-       } else {
-               int len;
-               len = cifs_strtoUTF16((__le16 *)tmp, ses->user_name,
-                                     CIFS_MAX_USERNAME_LEN, nls_cp);
-               len *= 2; /* unicode is 2 bytes each */
-               sec_blob->UserName.BufferOffset = cpu_to_le32(tmp - *pbuffer);
-               sec_blob->UserName.Length = cpu_to_le16(len);
-               sec_blob->UserName.MaximumLength = cpu_to_le16(len);
-               tmp += len;
-       }
+       cifs_security_buffer_from_str(&sec_blob->UserName,
+                                     ses->user_name,
+                                     CIFS_MAX_USERNAME_LEN,
+                                     *pbuffer, &tmp,
+                                     nls_cp);
 
-       sec_blob->WorkstationName.BufferOffset = cpu_to_le32(tmp - *pbuffer);
-       sec_blob->WorkstationName.Length = 0;
-       sec_blob->WorkstationName.MaximumLength = 0;
-       tmp += 2;
+       cifs_security_buffer_from_str(&sec_blob->WorkstationName,
+                                     ses->workstation_name,
+                                     CIFS_MAX_WORKSTATION_LEN,
+                                     *pbuffer, &tmp,
+                                     nls_cp);
 
        if (((ses->ntlmssp->server_flags & NTLMSSP_NEGOTIATE_KEY_XCH) ||
                (ses->ntlmssp->server_flags & NTLMSSP_NEGOTIATE_EXTENDED_SEC))
@@ -1230,6 +1294,7 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
        struct cifs_ses *ses = sess_data->ses;
        __u16 bytes_remaining;
        char *bcc_ptr;
+       unsigned char *ntlmsspblob = NULL;
        u16 blob_len;
 
        cifs_dbg(FYI, "rawntlmssp session setup negotiate phase\n");
@@ -1253,10 +1318,15 @@ sess_auth_rawntlmssp_negotiate(struct sess_data *sess_data)
        pSMB = (SESSION_SETUP_ANDX *)sess_data->iov[0].iov_base;
 
        /* Build security blob before we assemble the request */
-       build_ntlmssp_negotiate_blob(pSMB->req.SecurityBlob, ses);
-       sess_data->iov[1].iov_len = sizeof(NEGOTIATE_MESSAGE);
-       sess_data->iov[1].iov_base = pSMB->req.SecurityBlob;
-       pSMB->req.SecurityBlobLength = cpu_to_le16(sizeof(NEGOTIATE_MESSAGE));
+       rc = build_ntlmssp_negotiate_blob(&ntlmsspblob,
+                                    &blob_len, ses,
+                                    sess_data->nls_cp);
+       if (rc)
+               goto out;
+
+       sess_data->iov[1].iov_len = blob_len;
+       sess_data->iov[1].iov_base = ntlmsspblob;
+       pSMB->req.SecurityBlobLength = cpu_to_le16(blob_len);
 
        rc = _sess_auth_rawntlmssp_assemble_req(sess_data);
        if (rc)
index 8297703..fe5bfa2 100644 (file)
@@ -46,6 +46,10 @@ struct cop_vars {
        struct smb2_file_link_info link_info;
 };
 
+/*
+ * note: If cfile is passed, the reference to it is dropped here.
+ * So make sure that you do not reuse cfile after return from this func.
+ */
 static int
 smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
                 struct cifs_sb_info *cifs_sb, const char *full_path,
@@ -536,10 +540,11 @@ smb2_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
                create_options |= OPEN_REPARSE_POINT;
 
                /* Failed on a symbolic link - query a reparse point info */
+               cifs_get_readable_path(tcon, full_path, &cfile);
                rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
                                      FILE_READ_ATTRIBUTES, FILE_OPEN,
                                      create_options, ACL_NO_MODE,
-                                     smb2_data, SMB2_OP_QUERY_INFO, NULL);
+                                     smb2_data, SMB2_OP_QUERY_INFO, cfile);
        }
        if (rc)
                goto out;
@@ -587,10 +592,11 @@ smb311_posix_query_path_info(const unsigned int xid, struct cifs_tcon *tcon,
                create_options |= OPEN_REPARSE_POINT;
 
                /* Failed on a symbolic link - query a reparse point info */
+               cifs_get_readable_path(tcon, full_path, &cfile);
                rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
                                      FILE_READ_ATTRIBUTES, FILE_OPEN,
                                      create_options, ACL_NO_MODE,
-                                     smb2_data, SMB2_OP_POSIX_QUERY_INFO, NULL);
+                                     smb2_data, SMB2_OP_POSIX_QUERY_INFO, cfile);
        }
        if (rc)
                goto out;
@@ -707,10 +713,12 @@ smb2_set_path_size(const unsigned int xid, struct cifs_tcon *tcon,
                   struct cifs_sb_info *cifs_sb, bool set_alloc)
 {
        __le64 eof = cpu_to_le64(size);
+       struct cifsFileInfo *cfile;
 
+       cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
        return smb2_compound_op(xid, tcon, cifs_sb, full_path,
                                FILE_WRITE_DATA, FILE_OPEN, 0, ACL_NO_MODE,
-                               &eof, SMB2_OP_SET_EOF, NULL);
+                               &eof, SMB2_OP_SET_EOF, cfile);
 }
 
 int
@@ -719,6 +727,8 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
 {
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
        struct tcon_link *tlink;
+       struct cifs_tcon *tcon;
+       struct cifsFileInfo *cfile;
        int rc;
 
        if ((buf->CreationTime == 0) && (buf->LastAccessTime == 0) &&
@@ -729,10 +739,12 @@ smb2_set_file_info(struct inode *inode, const char *full_path,
        tlink = cifs_sb_tlink(cifs_sb);
        if (IS_ERR(tlink))
                return PTR_ERR(tlink);
+       tcon = tlink_tcon(tlink);
 
-       rc = smb2_compound_op(xid, tlink_tcon(tlink), cifs_sb, full_path,
+       cifs_get_writable_path(tcon, full_path, FIND_WR_ANY, &cfile);
+       rc = smb2_compound_op(xid, tcon, cifs_sb, full_path,
                              FILE_WRITE_ATTRIBUTES, FILE_OPEN,
-                             0, ACL_NO_MODE, buf, SMB2_OP_SET_INFO, NULL);
+                             0, ACL_NO_MODE, buf, SMB2_OP_SET_INFO, cfile);
        cifs_put_tlink(tlink);
        return rc;
 }
index 7acf71d..c5b1dea 100644 (file)
@@ -2844,6 +2844,7 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
        struct fsctl_get_dfs_referral_req *dfs_req = NULL;
        struct get_dfs_referral_rsp *dfs_rsp = NULL;
        u32 dfs_req_size = 0, dfs_rsp_size = 0;
+       int retry_count = 0;
 
        cifs_dbg(FYI, "%s: path: %s\n", __func__, search_name);
 
@@ -2895,11 +2896,14 @@ smb2_get_dfs_refer(const unsigned int xid, struct cifs_ses *ses,
                                true /* is_fsctl */,
                                (char *)dfs_req, dfs_req_size, CIFSMaxBufSize,
                                (char **)&dfs_rsp, &dfs_rsp_size);
-       } while (rc == -EAGAIN);
+               if (!is_retryable_error(rc))
+                       break;
+               usleep_range(512, 2048);
+       } while (++retry_count < 5);
 
        if (rc) {
-               if ((rc != -ENOENT) && (rc != -EOPNOTSUPP))
-                       cifs_tcon_dbg(VFS, "ioctl error in %s rc=%d\n", __func__, rc);
+               if (!is_retryable_error(rc) && rc != -ENOENT && rc != -EOPNOTSUPP)
+                       cifs_tcon_dbg(VFS, "%s: ioctl error: rc=%d\n", __func__, rc);
                goto out;
        }
 
index d2ecb2e..2f5f2c4 100644 (file)
@@ -155,7 +155,11 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        if (tcon == NULL)
                return 0;
 
-       if (smb2_command == SMB2_TREE_CONNECT)
+       /*
+        * Need to also skip SMB2_IOCTL because it is used for checking nested dfs links in
+        * cifs_tree_connect().
+        */
+       if (smb2_command == SMB2_TREE_CONNECT || smb2_command == SMB2_IOCTL)
                return 0;
 
        if (tcon->tidStatus == CifsExiting) {
@@ -253,7 +257,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
        /*
         * If we are reconnecting an extra channel, bind
         */
-       if (server->is_channel) {
+       if (CIFS_SERVER_IS_CHAN(server)) {
                ses->binding = true;
                ses->binding_chan = cifs_ses_find_chan(ses, server);
        }
@@ -1456,7 +1460,7 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
        int rc;
        struct cifs_ses *ses = sess_data->ses;
        struct smb2_sess_setup_rsp *rsp = NULL;
-       char *ntlmssp_blob = NULL;
+       unsigned char *ntlmssp_blob = NULL;
        bool use_spnego = false; /* else use raw ntlmssp */
        u16 blob_length = 0;
 
@@ -1475,22 +1479,17 @@ SMB2_sess_auth_rawntlmssp_negotiate(struct SMB2_sess_data *sess_data)
        if (rc)
                goto out_err;
 
-       ntlmssp_blob = kmalloc(sizeof(struct _NEGOTIATE_MESSAGE),
-                              GFP_KERNEL);
-       if (ntlmssp_blob == NULL) {
-               rc = -ENOMEM;
-               goto out;
-       }
+       rc = build_ntlmssp_negotiate_blob(&ntlmssp_blob,
+                                         &blob_length, ses,
+                                         sess_data->nls_cp);
+       if (rc)
+               goto out_err;
 
-       build_ntlmssp_negotiate_blob(ntlmssp_blob, ses);
        if (use_spnego) {
                /* BB eventually need to add this */
                cifs_dbg(VFS, "spnego not supported for SMB2 yet\n");
                rc = -EOPNOTSUPP;
                goto out;
-       } else {
-               blob_length = sizeof(struct _NEGOTIATE_MESSAGE);
-               /* with raw NTLMSSP we don't encapsulate in SPNEGO */
        }
        sess_data->iov[1].iov_base = ntlmssp_blob;
        sess_data->iov[1].iov_len = blob_length;
@@ -1841,7 +1840,7 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
        cifs_small_buf_release(req);
        rsp = (struct smb2_tree_connect_rsp *)rsp_iov.iov_base;
        trace_smb3_tcon(xid, tcon->tid, ses->Suid, tree, rc);
-       if (rc != 0) {
+       if ((rc != 0) || (rsp == NULL)) {
                cifs_stats_fail_inc(tcon, SMB2_TREE_CONNECT_HE);
                tcon->need_reconnect = true;
                goto tcon_error_exit;
@@ -2669,7 +2668,18 @@ int smb311_posix_mkdir(const unsigned int xid, struct inode *inode,
                goto err_free_rsp_buf;
        }
 
+       /*
+        * Although unlikely to be possible for rsp to be null and rc not set,
+        * adding check below is slightly safer long term (and quiets Coverity
+        * warning)
+        */
        rsp = (struct smb2_create_rsp *)rsp_iov.iov_base;
+       if (rsp == NULL) {
+               rc = -EIO;
+               kfree(pc_buf);
+               goto err_free_req;
+       }
+
        trace_smb3_posix_mkdir_done(xid, le64_to_cpu(rsp->PersistentFileId),
                                    tcon->tid,
                                    ses->Suid, CREATE_NOT_FILE,
@@ -2942,7 +2952,9 @@ SMB2_open(const unsigned int xid, struct cifs_open_parms *oparms, __le16 *path,
                        tcon->need_reconnect = true;
                }
                goto creat_exit;
-       } else
+       } else if (rsp == NULL) /* unlikely to happen, but safer to check */
+               goto creat_exit;
+       else
                trace_smb3_open_done(xid, le64_to_cpu(rsp->PersistentFileId),
                                     tcon->tid,
                                     ses->Suid, oparms->create_options,
@@ -3163,6 +3175,16 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
        if ((plen == NULL) || (out_data == NULL))
                goto ioctl_exit;
 
+       /*
+        * Although unlikely to be possible for rsp to be null and rc not set,
+        * adding check below is slightly safer long term (and quiets Coverity
+        * warning)
+        */
+       if (rsp == NULL) {
+               rc = -EIO;
+               goto ioctl_exit;
+       }
+
        *plen = le32_to_cpu(rsp->OutputCount);
 
        /* We check for obvious errors in the output buffer length and offset */
index b737932..61ea3d3 100644 (file)
@@ -1044,14 +1044,17 @@ 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);
        }
 }