cifs: support nested dfs links over reconnect
authorPaulo Alcantara <pc@cjr.nz>
Wed, 3 Nov 2021 16:53:29 +0000 (13:53 -0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 2 Dec 2022 16:40:59 +0000 (17:40 +0100)
[ Upstream commit c88f7dcd6d6429197fc2fd87b54a894ffcd48e8e ]

Mounting a dfs link that has nested links was already supported at
mount(2), so make it work over reconnect as well.

Make the following case work:

* mount //root/dfs/link /mnt -o ...
  - final share: /server/share

* in server settings
  - change target folder of /root/dfs/link3 to /server/share2
  - change target folder of /root/dfs/link2 to /root/dfs/link3
  - change target folder of /root/dfs/link to /root/dfs/link2

* mount -o remount,... /mnt
 - refresh all dfs referrals
 - mark current connection for failover
 - cifs_reconnect() reconnects to root server
 - tree_connect()
   * checks that /root/dfs/link2 is a link, then chase it
   * checks that root/dfs/link3 is a link, then chase it
   * finally tree connect to /server/share2

If the mounted share is no longer accessible and a reconnect had been
triggered, the client will retry it from both last referral
path (/root/dfs/link3) and original referral path (/root/dfs/link).

Any new referral paths found while chasing dfs links over reconnect,
it will be updated to TCP_Server_Info::leaf_fullpath, accordingly.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: Steve French <stfrench@microsoft.com>
Stable-dep-of: 1dcdf5f5b213 ("cifs: Fix connections leak when tlink setup failed")
Signed-off-by: Sasha Levin <sashal@kernel.org>
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/misc.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c

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 a97ed30..1ab72c3 100644 (file)
@@ -693,6 +693,19 @@ struct TCP_Server_Info {
 #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
 };
 
@@ -1097,7 +1110,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
 };
@@ -1950,4 +1962,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..b269735 100644 (file)
@@ -607,7 +607,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 +634,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 5d87d5c..902eb8a 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);
@@ -303,14 +317,68 @@ static int __cifs_reconnect(struct TCP_Server_Info *server)
 }
 
 #ifdef CONFIG_CIFS_DFS_UPCALL
-static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb)
+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);
+                       }
+               }
+               /* 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);
+
+       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 = cifs_sb->origin_fullpath + 1;
+       const char *refpath = server->current_fullpath + 1;
        struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
-       struct dfs_cache_tgt_iterator *tit = NULL;
-       int num_targets = 1;
-       char *hostname;
+       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.
@@ -320,11 +388,10 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_i
         * 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)) {
+       if (!dfs_cache_noreq_find(refpath, NULL, &tl))
                num_targets = dfs_cache_get_nr_tgts(&tl);
-               if (!num_targets)
-                       num_targets = 1;
-       }
+       if (!num_targets)
+               num_targets = 1;
 
        if (!cifs_tcp_ses_needs_reconnect(server, num_targets))
                return 0;
@@ -332,51 +399,17 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_i
        cifs_mark_tcp_ses_conns_for_reconnect(server);
 
        do {
-               /* Get next dfs target from target list (if any) */
-               if (!tit)
-                       tit = dfs_cache_get_tgt_iterator(&tl);
-               else
-                       tit = dfs_cache_get_next_tgt(&tl, tit);
-
                try_to_freeze();
                mutex_lock(&server->srv_mutex);
 
-               if (!cifs_swn_set_server_dstaddr(server)) {
-                       /*
-                        * If any dfs target was selected, then update @server with either a
-                        * hostname or an address.
-                        */
-                       if (tit) {
-                               hostname = extract_hostname(dfs_cache_get_tgt_name(tit));
-                               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);
-                               }
-                       }
-                       /* 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);
-
+               rc = reconnect_target_unlocked(server, &tl, &target_hint);
                if (rc) {
-                       /* Failed to reconnect socket.  Retry next dfs target. */
+                       /* 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
@@ -392,8 +425,8 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_i
                mutex_unlock(&server->srv_mutex);
        } while (server->tcpStatus == CifsNeedReconnect);
 
-       if (tit)
-               dfs_cache_noreq_update_tgthint(refpath, tit);
+       if (target_hint)
+               dfs_cache_noreq_update_tgthint(refpath, target_hint);
 
        dfs_cache_free_tgts(&tl);
 
@@ -407,38 +440,15 @@ static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_i
 
 int cifs_reconnect(struct TCP_Server_Info *server)
 {
-       int rc;
-       struct super_block *sb;
-       struct cifs_sb_info *cifs_sb;
-
-       /*
-        * If tcp session is not an dfs connection or it is a channel, then reconnect to last target
-        * 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->is_channel) {
+       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);
 
-       /* If no superblock, then it might be an ipc connection */
-       sb = cifs_get_tcp_super(server);
-       if (IS_ERR(sb))
-               return __cifs_reconnect(server);
-
-       /*
-        * Check for a referral path to look up in superblock.  If unset, then simply reconnect to
-        * last target server.
-        */
-       cifs_sb = CIFS_SB(sb);
-       if (!cifs_sb->origin_fullpath || !cifs_sb->origin_fullpath[0])
-               rc = __cifs_reconnect(server);
-       else
-               rc = reconnect_dfs_server(server, cifs_sb);
-
-       cifs_put_tcp_super(sb);
-       return rc;
+       return reconnect_dfs_server(server);
 }
 #else
 int cifs_reconnect(struct TCP_Server_Info *server)
@@ -829,6 +839,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);
@@ -1445,6 +1459,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,
@@ -2909,73 +2926,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);
        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;
@@ -2986,17 +2994,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))
@@ -3020,7 +3030,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,
@@ -3050,18 +3066,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;
@@ -3103,190 +3118,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
+ * expand_dfs_referral - Update cifs_sb from dfs referral path
  *
- * 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.
- *
- * 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
@@ -3393,12 +3256,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)
@@ -3436,280 +3301,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;
 
-               kfree(*dfs_path);
-               *dfs_path = npath;
-               rc = -EREMOTE;
+       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;
+
+       /* 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)
+               rc = __follow_dfs_link(mnt_ctx);
+               if (!rc || rc != -EREMOTE)
                        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)
-                       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;
+}
 
-               /* 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);
+/* 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;
+
+       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
@@ -3877,7 +3751,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);
 }
@@ -4204,104 +4077,249 @@ 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;
+}
+
+int __tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
+                             struct cifs_sb_info *cifs_sb, char *tree,
+                             struct dfs_cache_tgt_list *tl, struct dfs_info3_param *ref)
 {
        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;
+       bool islink;
+       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);
-               }
+       islink = ref->server_type == DFS_TYPE_LINK;
+       free_dfs_info_param(ref);
+
+       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;
 
-               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__);
+               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,
+                                  ref, &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);
+                       break;
                }
+               /* Target is another dfs share */
+               rc = update_server_fullpath(server, cifs_sb, target);
+               dfs_cache_free_tgts(tl);
 
-               if (tcon->ipc) {
-                       scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", share);
-                       rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
+               if (!rc) {
+                       rc = -EREMOTE;
+                       list_replace_init(&ntl.tl_list, &tl->tl_list);
                } 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;
-                       }
+                       dfs_cache_free_tgts(&ntl);
+                       free_dfs_info_param(ref);
                }
-               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;
+}
+
+int tree_connect_dfs_target(const unsigned int xid, struct cifs_tcon *tcon,
+                           struct cifs_sb_info *cifs_sb, char *tree,
+                           struct dfs_cache_tgt_list *tl, struct dfs_info3_param *ref)
+{
+       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, tl, ref);
+               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, &tl, &ref);
+
 out:
        kfree(tree);
+       cifs_put_tcp_super(sb);
+
        return rc;
 }
 #else
index 2837455..1f3efa7 100644 (file)
@@ -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 699f676..94143d7 100644 (file)
@@ -139,9 +139,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);
 }
 
@@ -1299,69 +1296,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 53e8746..5e6526c 100644 (file)
@@ -2869,6 +2869,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);
 
@@ -2920,11 +2921,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 8aa0372..c0ea281 100644 (file)
@@ -156,7 +156,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) {