Merge tag 'selinux-pr-20210629' of git://git.kernel.org/pub/scm/linux/kernel/git...
[sfrench/cifs-2.6.git] / fs / cifs / dfs_cache.c
index b1fa30fefe1f6c263b14e6ce3d59048290c092cf..7c1769714609bdf1cde634404377f5314ce3489d 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/proc_fs.h>
 #include <linux/nls.h>
 #include <linux/workqueue.h>
+#include <linux/uuid.h>
 #include "cifsglob.h"
 #include "smb2pdu.h"
 #include "smb2proto.h"
 #include "cifs_debug.h"
 #include "cifs_unicode.h"
 #include "smb2glob.h"
-#include "fs_context.h"
 
 #include "dfs_cache.h"
 
 #define CACHE_HTABLE_SIZE 32
 #define CACHE_MAX_ENTRIES 64
+#define CACHE_MIN_TTL 120 /* 2 minutes */
 
-#define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \
-                                   DFSREF_STORAGE_SERVER))
+#define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
 
 struct cache_dfs_tgt {
        char *name;
@@ -48,14 +48,15 @@ struct cache_entry {
        struct cache_dfs_tgt *tgthint;
 };
 
-struct vol_info {
-       char *fullpath;
-       spinlock_t ctx_lock;
-       struct smb3_fs_context ctx;
-       char *mntdata;
+/* List of referral server sessions per dfs mount */
+struct mount_group {
        struct list_head list;
-       struct list_head rlist;
-       struct kref refcnt;
+       uuid_t id;
+       struct cifs_ses *sessions[CACHE_MAX_ENTRIES];
+       int num_sessions;
+       spinlock_t lock;
+       struct list_head refresh_list;
+       struct kref refcount;
 };
 
 static struct kmem_cache *cache_slab __read_mostly;
@@ -64,7 +65,7 @@ static struct workqueue_struct *dfscache_wq __read_mostly;
 static int cache_ttl;
 static DEFINE_SPINLOCK(cache_ttl_lock);
 
-static struct nls_table *cache_nlsc;
+static struct nls_table *cache_cp;
 
 /*
  * Number of entries in the cache
@@ -74,34 +75,145 @@ static atomic_t cache_count;
 static struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
 static DECLARE_RWSEM(htable_rw_lock);
 
-static LIST_HEAD(vol_list);
-static DEFINE_SPINLOCK(vol_list_lock);
+static LIST_HEAD(mount_group_list);
+static DEFINE_MUTEX(mount_group_list_lock);
 
 static void refresh_cache_worker(struct work_struct *work);
 
 static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
 
-static int get_normalized_path(const char *path, const char **npath)
+static void get_ipc_unc(const char *ref_path, char *ipc, size_t ipclen)
 {
-       if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/'))
-               return -EINVAL;
+       const char *host;
+       size_t len;
 
-       if (*path == '\\') {
-               *npath = path;
-       } else {
-               char *s = kstrdup(path, GFP_KERNEL);
-               if (!s)
-                       return -ENOMEM;
-               convert_delimiter(s, '\\');
-               *npath = s;
+       extract_unc_hostname(ref_path, &host, &len);
+       scnprintf(ipc, ipclen, "\\\\%.*s\\IPC$", (int)len, host);
+}
+
+static struct cifs_ses *find_ipc_from_server_path(struct cifs_ses **ses, const char *path)
+{
+       char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
+
+       get_ipc_unc(path, unc, sizeof(unc));
+       for (; *ses; ses++) {
+               if (!strcasecmp(unc, (*ses)->tcon_ipc->treeName))
+                       return *ses;
        }
-       return 0;
+       return ERR_PTR(-ENOENT);
+}
+
+static void __mount_group_release(struct mount_group *mg)
+{
+       int i;
+
+       for (i = 0; i < mg->num_sessions; i++)
+               cifs_put_smb_ses(mg->sessions[i]);
+       kfree(mg);
+}
+
+static void mount_group_release(struct kref *kref)
+{
+       struct mount_group *mg = container_of(kref, struct mount_group, refcount);
+
+       mutex_lock(&mount_group_list_lock);
+       list_del(&mg->list);
+       mutex_unlock(&mount_group_list_lock);
+       __mount_group_release(mg);
+}
+
+static struct mount_group *find_mount_group_locked(const uuid_t *id)
+{
+       struct mount_group *mg;
+
+       list_for_each_entry(mg, &mount_group_list, list) {
+               if (uuid_equal(&mg->id, id))
+                       return mg;
+       }
+       return ERR_PTR(-ENOENT);
+}
+
+static struct mount_group *__get_mount_group_locked(const uuid_t *id)
+{
+       struct mount_group *mg;
+
+       mg = find_mount_group_locked(id);
+       if (!IS_ERR(mg))
+               return mg;
+
+       mg = kmalloc(sizeof(*mg), GFP_KERNEL);
+       if (!mg)
+               return ERR_PTR(-ENOMEM);
+       kref_init(&mg->refcount);
+       uuid_copy(&mg->id, id);
+       mg->num_sessions = 0;
+       spin_lock_init(&mg->lock);
+       list_add(&mg->list, &mount_group_list);
+       return mg;
+}
+
+static struct mount_group *get_mount_group(const uuid_t *id)
+{
+       struct mount_group *mg;
+
+       mutex_lock(&mount_group_list_lock);
+       mg = __get_mount_group_locked(id);
+       if (!IS_ERR(mg))
+               kref_get(&mg->refcount);
+       mutex_unlock(&mount_group_list_lock);
+
+       return mg;
 }
 
-static inline void free_normalized_path(const char *path, const char *npath)
+static void free_mount_group_list(void)
 {
-       if (path != npath)
-               kfree(npath);
+       struct mount_group *mg, *tmp_mg;
+
+       list_for_each_entry_safe(mg, tmp_mg, &mount_group_list, list) {
+               list_del_init(&mg->list);
+               __mount_group_release(mg);
+       }
+}
+
+/**
+ * dfs_cache_canonical_path - get a canonical DFS path
+ *
+ * @path: DFS path
+ * @cp: codepage
+ * @remap: mapping type
+ *
+ * Return canonical path if success, otherwise error.
+ */
+char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap)
+{
+       char *tmp;
+       int plen = 0;
+       char *npath;
+
+       if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/'))
+               return ERR_PTR(-EINVAL);
+
+       if (unlikely(strcmp(cp->charset, cache_cp->charset))) {
+               tmp = (char *)cifs_strndup_to_utf16(path, strlen(path), &plen, cp, remap);
+               if (!tmp) {
+                       cifs_dbg(VFS, "%s: failed to convert path to utf16\n", __func__);
+                       return ERR_PTR(-EINVAL);
+               }
+
+               npath = cifs_strndup_from_utf16(tmp, plen, true, cache_cp);
+               kfree(tmp);
+
+               if (!npath) {
+                       cifs_dbg(VFS, "%s: failed to convert path from utf16\n", __func__);
+                       return ERR_PTR(-EINVAL);
+               }
+       } else {
+               npath = kstrdup(path, GFP_KERNEL);
+               if (!npath)
+                       return ERR_PTR(-ENOMEM);
+       }
+       convert_delimiter(npath, '\\');
+       return npath;
 }
 
 static inline bool cache_entry_expired(const struct cache_entry *ce)
@@ -171,7 +283,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
                                   "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,
-                                  IS_INTERLINK_SET(ce->hdr_flags) ? "yes" : "no",
+                                  IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
                                   ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
 
                        list_for_each_entry(t, &ce->tlist, list) {
@@ -240,7 +352,7 @@ static inline void dump_ce(const struct cache_entry *ce)
                 ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl,
                 ce->etime.tv_nsec,
                 ce->hdr_flags, ce->ref_flags,
-                IS_INTERLINK_SET(ce->hdr_flags) ? "yes" : "no",
+                IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
                 ce->path_consumed,
                 cache_entry_expired(ce) ? "yes" : "no");
        dump_tgts(ce);
@@ -284,8 +396,7 @@ int dfs_cache_init(void)
        int rc;
        int i;
 
-       dfscache_wq = alloc_workqueue("cifs-dfscache",
-                                     WQ_FREEZABLE | WQ_MEM_RECLAIM, 1);
+       dfscache_wq = alloc_workqueue("cifs-dfscache", WQ_FREEZABLE | WQ_UNBOUND, 1);
        if (!dfscache_wq)
                return -ENOMEM;
 
@@ -301,7 +412,9 @@ int dfs_cache_init(void)
                INIT_HLIST_HEAD(&cache_htable[i]);
 
        atomic_set(&cache_count, 0);
-       cache_nlsc = load_nls_default();
+       cache_cp = load_nls("utf8");
+       if (!cache_cp)
+               cache_cp = load_nls_default();
 
        cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__);
        return 0;
@@ -311,23 +424,24 @@ out_destroy_wq:
        return rc;
 }
 
-static inline unsigned int cache_entry_hash(const void *data, int size)
+static int cache_entry_hash(const void *data, int size, unsigned int *hash)
 {
-       unsigned int h;
-
-       h = jhash(data, size, 0);
-       return h & (CACHE_HTABLE_SIZE - 1);
-}
-
-/* Check whether second path component of @path is SYSVOL or NETLOGON */
-static inline bool is_sysvol_or_netlogon(const char *path)
-{
-       const char *s;
-       char sep = path[0];
-
-       s = strchr(path + 1, sep) + 1;
-       return !strncasecmp(s, "sysvol", strlen("sysvol")) ||
-               !strncasecmp(s, "netlogon", strlen("netlogon"));
+       int i, clen;
+       const unsigned char *s = data;
+       wchar_t c;
+       unsigned int h = 0;
+
+       for (i = 0; i < size; i += clen) {
+               clen = cache_cp->char2uni(&s[i], size - i, &c);
+               if (unlikely(clen < 0)) {
+                       cifs_dbg(VFS, "%s: can't convert char\n", __func__);
+                       return clen;
+               }
+               c = cifs_toupper(c);
+               h = jhash(&c, sizeof(c), h);
+       }
+       *hash = h % CACHE_HTABLE_SIZE;
+       return 0;
 }
 
 /* Return target hint of a DFS cache entry */
@@ -378,7 +492,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
 {
        int i;
 
-       ce->ttl = refs[0].ttl;
+       ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
        ce->etime = get_expire_time(ce->ttl);
        ce->srvtype = refs[0].server_type;
        ce->hdr_flags = refs[0].flags;
@@ -409,9 +523,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
 }
 
 /* Allocate a new cache entry */
-static struct cache_entry *alloc_cache_entry(const char *path,
-                                            const struct dfs_info3_param *refs,
-                                            int numrefs)
+static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
 {
        struct cache_entry *ce;
        int rc;
@@ -420,11 +532,9 @@ static struct cache_entry *alloc_cache_entry(const char *path,
        if (!ce)
                return ERR_PTR(-ENOMEM);
 
-       ce->path = kstrdup(path, GFP_KERNEL);
-       if (!ce->path) {
-               kmem_cache_free(cache_slab, ce);
-               return ERR_PTR(-ENOMEM);
-       }
+       ce->path = refs[0].path_name;
+       refs[0].path_name = NULL;
+
        INIT_HLIST_NODE(&ce->hlist);
        INIT_LIST_HEAD(&ce->tlist);
 
@@ -437,13 +547,14 @@ static struct cache_entry *alloc_cache_entry(const char *path,
        return ce;
 }
 
-/* Must be called with htable_rw_lock held */
-static void remove_oldest_entry(void)
+static void remove_oldest_entry_locked(void)
 {
        int i;
        struct cache_entry *ce;
        struct cache_entry *to_del = NULL;
 
+       WARN_ON(!rwsem_is_locked(&htable_rw_lock));
+
        for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
                struct hlist_head *l = &cache_htable[i];
 
@@ -467,12 +578,24 @@ static void remove_oldest_entry(void)
 }
 
 /* Add a new DFS cache entry */
-static int add_cache_entry(const char *path, unsigned int hash,
-                          struct dfs_info3_param *refs, int numrefs)
+static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
 {
+       int rc;
        struct cache_entry *ce;
+       unsigned int hash;
+
+       WARN_ON(!rwsem_is_locked(&htable_rw_lock));
+
+       if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) {
+               cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES);
+               remove_oldest_entry_locked();
+       }
 
-       ce = alloc_cache_entry(path, refs, numrefs);
+       rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
+       if (rc)
+               return rc;
+
+       ce = alloc_cache_entry(refs, numrefs);
        if (IS_ERR(ce))
                return PTR_ERR(ce);
 
@@ -486,65 +609,77 @@ static int add_cache_entry(const char *path, unsigned int hash,
        }
        spin_unlock(&cache_ttl_lock);
 
-       down_write(&htable_rw_lock);
        hlist_add_head(&ce->hlist, &cache_htable[hash]);
        dump_ce(ce);
-       up_write(&htable_rw_lock);
+
+       atomic_inc(&cache_count);
 
        return 0;
 }
 
-static struct cache_entry *__lookup_cache_entry(const char *path)
+/* Check if two DFS paths are equal.  @s1 and @s2 are expected to be in @cache_cp's charset */
+static bool dfs_path_equal(const char *s1, int len1, const char *s2, int len2)
 {
-       struct cache_entry *ce;
-       unsigned int h;
-       bool found = false;
+       int i, l1, l2;
+       wchar_t c1, c2;
 
-       h = cache_entry_hash(path, strlen(path));
+       if (len1 != len2)
+               return false;
 
-       hlist_for_each_entry(ce, &cache_htable[h], hlist) {
-               if (!strcasecmp(path, ce->path)) {
-                       found = true;
-                       dump_ce(ce);
-                       break;
+       for (i = 0; i < len1; i += l1) {
+               l1 = cache_cp->char2uni(&s1[i], len1 - i, &c1);
+               l2 = cache_cp->char2uni(&s2[i], len2 - i, &c2);
+               if (unlikely(l1 < 0 && l2 < 0)) {
+                       if (s1[i] != s2[i])
+                               return false;
+                       l1 = 1;
+                       continue;
                }
+               if (l1 != l2)
+                       return false;
+               if (cifs_toupper(c1) != cifs_toupper(c2))
+                       return false;
        }
+       return true;
+}
 
-       if (!found)
-               ce = ERR_PTR(-ENOENT);
-       return ce;
+static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len)
+{
+       struct cache_entry *ce;
+
+       hlist_for_each_entry(ce, &cache_htable[hash], hlist) {
+               if (dfs_path_equal(ce->path, strlen(ce->path), path, len)) {
+                       dump_ce(ce);
+                       return ce;
+               }
+       }
+       return ERR_PTR(-EEXIST);
 }
 
 /*
- * Find a DFS cache entry in hash table and optionally check prefix path against
- * @path.
- * Use whole path components in the match.
- * Must be called with htable_rw_lock held.
+ * Find a DFS cache entry in hash table and optionally check prefix path against normalized @path.
+ *
+ * Use whole path components in the match.  Must be called with htable_rw_lock held.
  *
- * Return ERR_PTR(-ENOENT) if the entry is not found.
+ * Return ERR_PTR(-EEXIST) if the entry is not found.
  */
-static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *hash)
+static struct cache_entry *lookup_cache_entry(const char *path)
 {
-       struct cache_entry *ce = ERR_PTR(-ENOENT);
-       unsigned int h;
+       struct cache_entry *ce;
        int cnt = 0;
-       char *npath;
-       char *s, *e;
-       char sep;
-
-       npath = kstrdup(path, GFP_KERNEL);
-       if (!npath)
-               return ERR_PTR(-ENOMEM);
+       const char *s = path, *e;
+       char sep = *s;
+       unsigned int hash;
+       int rc;
 
-       s = npath;
-       sep = *npath;
        while ((s = strchr(s, sep)) && ++cnt < 3)
                s++;
 
        if (cnt < 3) {
-               h = cache_entry_hash(path, strlen(path));
-               ce = __lookup_cache_entry(path);
-               goto out;
+               rc = cache_entry_hash(path, strlen(path), &hash);
+               if (rc)
+                       return ERR_PTR(rc);
+               return __lookup_cache_entry(path, hash, strlen(path));
        }
        /*
         * Handle paths that have more than two path components and are a complete prefix of the DFS
@@ -552,64 +687,29 @@ static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *ha
         *
         * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
         */
-       h = cache_entry_hash(npath, strlen(npath));
-       e = npath + strlen(npath) - 1;
+       e = path + strlen(path) - 1;
        while (e > s) {
-               char tmp;
+               int len;
 
                /* skip separators */
                while (e > s && *e == sep)
                        e--;
                if (e == s)
-                       goto out;
-
-               tmp = *(e+1);
-               *(e+1) = 0;
-
-               ce = __lookup_cache_entry(npath);
-               if (!IS_ERR(ce)) {
-                       h = cache_entry_hash(npath, strlen(npath));
                        break;
-               }
 
-               *(e+1) = tmp;
+               len = e + 1 - path;
+               rc = cache_entry_hash(path, len, &hash);
+               if (rc)
+                       return ERR_PTR(rc);
+               ce = __lookup_cache_entry(path, hash, len);
+               if (!IS_ERR(ce))
+                       return ce;
+
                /* backward until separator */
                while (e > s && *e != sep)
                        e--;
        }
-out:
-       if (hash)
-               *hash = h;
-       kfree(npath);
-       return ce;
-}
-
-static void __vol_release(struct vol_info *vi)
-{
-       kfree(vi->fullpath);
-       kfree(vi->mntdata);
-       smb3_cleanup_fs_context_contents(&vi->ctx);
-       kfree(vi);
-}
-
-static void vol_release(struct kref *kref)
-{
-       struct vol_info *vi = container_of(kref, struct vol_info, refcnt);
-
-       spin_lock(&vol_list_lock);
-       list_del(&vi->list);
-       spin_unlock(&vol_list_lock);
-       __vol_release(vi);
-}
-
-static inline void free_vol_list(void)
-{
-       struct vol_info *vi, *nvi;
-
-       list_for_each_entry_safe(vi, nvi, &vol_list, list) {
-               list_del_init(&vi->list);
-               __vol_release(vi);
-       }
+       return ERR_PTR(-EEXIST);
 }
 
 /**
@@ -618,8 +718,8 @@ static inline void free_vol_list(void)
 void dfs_cache_destroy(void)
 {
        cancel_delayed_work_sync(&refresh_task);
-       unload_nls(cache_nlsc);
-       free_vol_list();
+       unload_nls(cache_cp);
+       free_mount_group_list();
        flush_cache_ents();
        kmem_cache_destroy(cache_slab);
        destroy_workqueue(dfscache_wq);
@@ -627,18 +727,14 @@ void dfs_cache_destroy(void)
        cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__);
 }
 
-/* Must be called with htable_rw_lock held */
-static int __update_cache_entry(const char *path,
-                               const struct dfs_info3_param *refs,
-                               int numrefs)
+/* Update a cache entry with the new referral in @refs */
+static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_info3_param *refs,
+                                    int numrefs)
 {
        int rc;
-       struct cache_entry *ce;
        char *s, *th = NULL;
 
-       ce = lookup_cache_entry(path, NULL);
-       if (IS_ERR(ce))
-               return PTR_ERR(ce);
+       WARN_ON(!rwsem_is_locked(&htable_rw_lock));
 
        if (ce->tgthint) {
                s = ce->tgthint->name;
@@ -657,37 +753,30 @@ static int __update_cache_entry(const char *path,
        return rc;
 }
 
-static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
-                           const struct nls_table *nls_codepage, int remap,
-                           const char *path,  struct dfs_info3_param **refs,
-                           int *numrefs)
+static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const char *path,
+                           struct dfs_info3_param **refs, int *numrefs)
 {
-       cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
+       int rc;
+       int i;
 
-       if (!ses || !ses->server || !ses->server->ops->get_dfs_refer)
-               return -EOPNOTSUPP;
-       if (unlikely(!nls_codepage))
-               return -EINVAL;
+       cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
 
        *refs = NULL;
        *numrefs = 0;
 
-       return ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs,
-                                              nls_codepage, remap);
-}
-
-/* Update an expired cache entry by getting a new DFS referral from server */
-static int update_cache_entry(const char *path,
-                             const struct dfs_info3_param *refs,
-                             int numrefs)
-{
-
-       int rc;
+       if (!ses || !ses->server || !ses->server->ops->get_dfs_refer)
+               return -EOPNOTSUPP;
+       if (unlikely(!cache_cp))
+               return -EINVAL;
 
-       down_write(&htable_rw_lock);
-       rc = __update_cache_entry(path, refs, numrefs);
-       up_write(&htable_rw_lock);
+       rc =  ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
+                                             NO_MAP_UNI_RSVD);
+       if (!rc) {
+               struct dfs_info3_param *ref = *refs;
 
+               for (i = 0; i < *numrefs; i++)
+                       convert_delimiter(ref[i].path_name, '\\');
+       }
        return rc;
 }
 
@@ -697,15 +786,12 @@ static int update_cache_entry(const char *path,
  * If the entry wasn't found, it will create a new one. Or if it was found but
  * expired, then it will update the entry accordingly.
  *
- * For interlinks, __cifs_dfs_mount() and expand_dfs_referral() are supposed to
+ * For interlinks, cifs_mount() and expand_dfs_referral() are supposed to
  * handle them properly.
  */
-static int __dfs_cache_find(const unsigned int xid, struct cifs_ses *ses,
-                           const struct nls_table *nls_codepage, int remap,
-                           const char *path, bool noreq)
+static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
 {
        int rc;
-       unsigned int hash;
        struct cache_entry *ce;
        struct dfs_info3_param *refs = NULL;
        int numrefs = 0;
@@ -713,62 +799,38 @@ static int __dfs_cache_find(const unsigned int xid, struct cifs_ses *ses,
 
        cifs_dbg(FYI, "%s: search path: %s\n", __func__, path);
 
-       down_read(&htable_rw_lock);
-
-       ce = lookup_cache_entry(path, &hash);
-
-       /*
-        * If @noreq is set, no requests will be sent to the server. Just return
-        * the cache entry.
-        */
-       if (noreq) {
-               up_read(&htable_rw_lock);
-               return PTR_ERR_OR_ZERO(ce);
-       }
+       down_write(&htable_rw_lock);
 
+       ce = lookup_cache_entry(path);
        if (!IS_ERR(ce)) {
                if (!cache_entry_expired(ce)) {
                        dump_ce(ce);
-                       up_read(&htable_rw_lock);
+                       up_write(&htable_rw_lock);
                        return 0;
                }
        } else {
                newent = true;
        }
 
-       up_read(&htable_rw_lock);
-
        /*
-        * No entry was found.
-        *
-        * Request a new DFS referral in order to create a new cache entry, or
-        * updating an existing one.
+        * Either the entry was not found, or it is expired.
+        * Request a new DFS referral in order to create or update a cache entry.
         */
-       rc = get_dfs_referral(xid, ses, nls_codepage, remap, path,
-                             &refs, &numrefs);
+       rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
        if (rc)
-               return rc;
+               goto out_unlock;
 
        dump_refs(refs, numrefs);
 
        if (!newent) {
-               rc = update_cache_entry(path, refs, numrefs);
-               goto out_free_refs;
-       }
-
-       if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) {
-               cifs_dbg(FYI, "%s: reached max cache size (%d)\n",
-                        __func__, CACHE_MAX_ENTRIES);
-               down_write(&htable_rw_lock);
-               remove_oldest_entry();
-               up_write(&htable_rw_lock);
+               rc = update_cache_entry_locked(ce, refs, numrefs);
+               goto out_unlock;
        }
 
-       rc = add_cache_entry(path, hash, refs, numrefs);
-       if (!rc)
-               atomic_inc(&cache_count);
+       rc = add_cache_entry_locked(refs, numrefs);
 
-out_free_refs:
+out_unlock:
+       up_write(&htable_rw_lock);
        free_dfs_info_array(refs, numrefs);
        return rc;
 }
@@ -868,7 +930,7 @@ err_free_it:
  * needs to be issued:
  * @xid: syscall xid
  * @ses: smb session to issue the request on
- * @nls_codepage: charset conversion
+ * @cp: codepage
  * @remap: path character remapping type
  * @path: path to lookup in DFS referral cache.
  *
@@ -877,26 +939,25 @@ err_free_it:
  *
  * Return zero if the target was found, otherwise non-zero.
  */
-int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses,
-                  const struct nls_table *nls_codepage, int remap,
-                  const char *path, struct dfs_info3_param *ref,
+int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *cp,
+                  int remap, const char *path, struct dfs_info3_param *ref,
                   struct dfs_cache_tgt_list *tgt_list)
 {
        int rc;
        const char *npath;
        struct cache_entry *ce;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               return rc;
+       npath = dfs_cache_canonical_path(path, cp, remap);
+       if (IS_ERR(npath))
+               return PTR_ERR(npath);
 
-       rc = __dfs_cache_find(xid, ses, nls_codepage, remap, npath, false);
+       rc = cache_refresh_path(xid, ses, npath);
        if (rc)
                goto out_free_path;
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(npath);
        if (IS_ERR(ce)) {
                up_read(&htable_rw_lock);
                rc = PTR_ERR(ce);
@@ -913,7 +974,7 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses,
        up_read(&htable_rw_lock);
 
 out_free_path:
-       free_normalized_path(path, npath);
+       kfree(npath);
        return rc;
 }
 
@@ -925,7 +986,7 @@ out_free_path:
  * expired, nor create a new cache entry if @path hasn't been found. It heavily
  * relies on an existing cache entry.
  *
- * @path: path to lookup in the DFS referral cache.
+ * @path: canonical DFS path to lookup in the DFS referral cache.
  * @ref: when non-NULL, store single DFS referral result in it.
  * @tgt_list: when non-NULL, store complete DFS target list in it.
  *
@@ -937,18 +998,13 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
                         struct dfs_cache_tgt_list *tgt_list)
 {
        int rc;
-       const char *npath;
        struct cache_entry *ce;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               return rc;
-
-       cifs_dbg(FYI, "%s: path: %s\n", __func__, npath);
+       cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -963,8 +1019,6 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
 
 out_unlock:
        up_read(&htable_rw_lock);
-       free_normalized_path(path, npath);
-
        return rc;
 }
 
@@ -979,16 +1033,15 @@ out_unlock:
  *
  * @xid: syscall id
  * @ses: smb session
- * @nls_codepage: charset conversion
+ * @cp: codepage
  * @remap: type of character remapping for paths
- * @path: path to lookup in DFS referral cache.
+ * @path: path to lookup in DFS referral cache
  * @it: DFS target iterator
  *
  * Return zero if the target hint was updated successfully, otherwise non-zero.
  */
 int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
-                            const struct nls_table *nls_codepage, int remap,
-                            const char *path,
+                            const struct nls_table *cp, int remap, const char *path,
                             const struct dfs_cache_tgt_iterator *it)
 {
        int rc;
@@ -996,19 +1049,19 @@ int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
        struct cache_entry *ce;
        struct cache_dfs_tgt *t;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               return rc;
+       npath = dfs_cache_canonical_path(path, cp, remap);
+       if (IS_ERR(npath))
+               return PTR_ERR(npath);
 
        cifs_dbg(FYI, "%s: update target hint - path: %s\n", __func__, npath);
 
-       rc = __dfs_cache_find(xid, ses, nls_codepage, remap, npath, false);
+       rc = cache_refresh_path(xid, ses, npath);
        if (rc)
                goto out_free_path;
 
        down_write(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(npath);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1031,8 +1084,7 @@ int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
 out_unlock:
        up_write(&htable_rw_lock);
 out_free_path:
-       free_normalized_path(path, npath);
-
+       kfree(npath);
        return rc;
 }
 
@@ -1044,32 +1096,26 @@ out_free_path:
  * expired, nor create a new cache entry if @path hasn't been found. It heavily
  * relies on an existing cache entry.
  *
- * @path: path to lookup in DFS referral cache.
+ * @path: canonical DFS path to lookup in DFS referral cache.
  * @it: target iterator which contains the target hint to update the cache
  * entry with.
  *
  * Return zero if the target hint was updated successfully, otherwise non-zero.
  */
-int dfs_cache_noreq_update_tgthint(const char *path,
-                                  const struct dfs_cache_tgt_iterator *it)
+int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
 {
        int rc;
-       const char *npath;
        struct cache_entry *ce;
        struct cache_dfs_tgt *t;
 
        if (!it)
                return -EINVAL;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               return rc;
-
-       cifs_dbg(FYI, "%s: path: %s\n", __func__, npath);
+       cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
 
        down_write(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1092,8 +1138,6 @@ int dfs_cache_noreq_update_tgthint(const char *path,
 
 out_unlock:
        up_write(&htable_rw_lock);
-       free_normalized_path(path, npath);
-
        return rc;
 }
 
@@ -1101,32 +1145,26 @@ out_unlock:
  * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given
  * target iterator (@it).
  *
- * @path: path to lookup in DFS referral cache.
+ * @path: canonical DFS path to lookup in DFS referral cache.
  * @it: DFS target iterator.
  * @ref: DFS referral pointer to set up the gathered information.
  *
  * Return zero if the DFS referral was set up correctly, otherwise non-zero.
  */
-int dfs_cache_get_tgt_referral(const char *path,
-                              const struct dfs_cache_tgt_iterator *it,
+int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
                               struct dfs_info3_param *ref)
 {
        int rc;
-       const char *npath;
        struct cache_entry *ce;
 
        if (!it || !ref)
                return -EINVAL;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               return rc;
-
-       cifs_dbg(FYI, "%s: path: %s\n", __func__, npath);
+       cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
 
        down_read(&htable_rw_lock);
 
-       ce = lookup_cache_entry(npath, NULL);
+       ce = lookup_cache_entry(path);
        if (IS_ERR(ce)) {
                rc = PTR_ERR(ce);
                goto out_unlock;
@@ -1138,132 +1176,55 @@ int dfs_cache_get_tgt_referral(const char *path,
 
 out_unlock:
        up_read(&htable_rw_lock);
-       free_normalized_path(path, npath);
-
        return rc;
 }
 
 /**
- * dfs_cache_add_vol - add a cifs context during mount() that will be handled by
- * DFS cache refresh worker.
- *
- * @mntdata: mount data.
- * @ctx: cifs context.
- * @fullpath: origin full path.
+ * dfs_cache_add_refsrv_session - add SMB session of referral server
  *
- * Return zero if context was set up correctly, otherwise non-zero.
+ * @mount_id: mount group uuid to lookup.
+ * @ses: reference counted SMB session of referral server.
  */
-int dfs_cache_add_vol(char *mntdata, struct smb3_fs_context *ctx, const char *fullpath)
+void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses)
 {
-       int rc;
-       struct vol_info *vi;
-
-       if (!ctx || !fullpath || !mntdata)
-               return -EINVAL;
-
-       cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath);
-
-       vi = kzalloc(sizeof(*vi), GFP_KERNEL);
-       if (!vi)
-               return -ENOMEM;
+       struct mount_group *mg;
 
-       vi->fullpath = kstrdup(fullpath, GFP_KERNEL);
-       if (!vi->fullpath) {
-               rc = -ENOMEM;
-               goto err_free_vi;
-       }
-
-       rc = smb3_fs_context_dup(&vi->ctx, ctx);
-       if (rc)
-               goto err_free_fullpath;
-
-       vi->mntdata = mntdata;
-       spin_lock_init(&vi->ctx_lock);
-       kref_init(&vi->refcnt);
-
-       spin_lock(&vol_list_lock);
-       list_add_tail(&vi->list, &vol_list);
-       spin_unlock(&vol_list_lock);
-
-       return 0;
-
-err_free_fullpath:
-       kfree(vi->fullpath);
-err_free_vi:
-       kfree(vi);
-       return rc;
-}
+       if (WARN_ON_ONCE(!mount_id || uuid_is_null(mount_id) || !ses))
+               return;
 
-/* Must be called with vol_list_lock held */
-static struct vol_info *find_vol(const char *fullpath)
-{
-       struct vol_info *vi;
+       mg = get_mount_group(mount_id);
+       if (WARN_ON_ONCE(IS_ERR(mg)))
+               return;
 
-       list_for_each_entry(vi, &vol_list, list) {
-               cifs_dbg(FYI, "%s: vi->fullpath: %s\n", __func__, vi->fullpath);
-               if (!strcasecmp(vi->fullpath, fullpath))
-                       return vi;
-       }
-       return ERR_PTR(-ENOENT);
+       spin_lock(&mg->lock);
+       if (mg->num_sessions < ARRAY_SIZE(mg->sessions))
+               mg->sessions[mg->num_sessions++] = ses;
+       spin_unlock(&mg->lock);
+       kref_put(&mg->refcount, mount_group_release);
 }
 
 /**
- * dfs_cache_update_vol - update vol info in DFS cache after failover
+ * dfs_cache_put_refsrv_sessions - put all referral server sessions
  *
- * @fullpath: fullpath to look up in volume list.
- * @server: TCP ses pointer.
+ * Put all SMB sessions from the given mount group id.
  *
- * Return zero if volume was updated, otherwise non-zero.
+ * @mount_id: mount group uuid to lookup.
  */
-int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server)
+void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id)
 {
-       struct vol_info *vi;
-
-       if (!fullpath || !server)
-               return -EINVAL;
-
-       cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath);
-
-       spin_lock(&vol_list_lock);
-       vi = find_vol(fullpath);
-       if (IS_ERR(vi)) {
-               spin_unlock(&vol_list_lock);
-               return PTR_ERR(vi);
-       }
-       kref_get(&vi->refcnt);
-       spin_unlock(&vol_list_lock);
-
-       cifs_dbg(FYI, "%s: updating volume info\n", __func__);
-       spin_lock(&vi->ctx_lock);
-       memcpy(&vi->ctx.dstaddr, &server->dstaddr,
-              sizeof(vi->ctx.dstaddr));
-       spin_unlock(&vi->ctx_lock);
+       struct mount_group *mg;
 
-       kref_put(&vi->refcnt, vol_release);
-
-       return 0;
-}
-
-/**
- * dfs_cache_del_vol - remove volume info in DFS cache during umount()
- *
- * @fullpath: fullpath to look up in volume list.
- */
-void dfs_cache_del_vol(const char *fullpath)
-{
-       struct vol_info *vi;
-
-       if (!fullpath || !*fullpath)
+       if (!mount_id || uuid_is_null(mount_id))
                return;
 
-       cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath);
-
-       spin_lock(&vol_list_lock);
-       vi = find_vol(fullpath);
-       spin_unlock(&vol_list_lock);
-
-       if (!IS_ERR(vi))
-               kref_put(&vi->refcnt, vol_release);
+       mutex_lock(&mount_group_list_lock);
+       mg = find_mount_group_locked(mount_id);
+       if (IS_ERR(mg)) {
+               mutex_unlock(&mount_group_list_lock);
+               return;
+       }
+       mutex_unlock(&mount_group_list_lock);
+       kref_put(&mg->refcount, mount_group_release);
 }
 
 /**
@@ -1276,8 +1237,8 @@ void dfs_cache_del_vol(const char *fullpath)
  *
  * Return zero if target was parsed correctly, otherwise non-zero.
  */
-int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
-                           char **share, char **prefix)
+int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,
+                           char **prefix)
 {
        char *s, sep, *p;
        size_t len;
@@ -1332,278 +1293,190 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
        return 0;
 }
 
-/* Get all tcons that are within a DFS namespace and can be refreshed */
-static void get_tcons(struct TCP_Server_Info *server, struct list_head *head)
+/*
+ * Refresh all active dfs mounts regardless of whether they are in cache or not.
+ * (cache can be cleared)
+ */
+static void refresh_mounts(struct cifs_ses **sessions)
 {
+       struct TCP_Server_Info *server;
        struct cifs_ses *ses;
-       struct cifs_tcon *tcon;
+       struct cifs_tcon *tcon, *ntcon;
+       struct list_head tcons;
+       unsigned int xid;
 
-       INIT_LIST_HEAD(head);
+       INIT_LIST_HEAD(&tcons);
 
        spin_lock(&cifs_tcp_ses_lock);
-       list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
-               list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
-                       if (!tcon->need_reconnect && !tcon->need_reopen_files &&
-                           tcon->dfs_path) {
-                               tcon->tc_count++;
-                               list_add_tail(&tcon->ulist, head);
+       list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
+               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) {
+                                       tcon->tc_count++;
+                                       list_add_tail(&tcon->ulist, &tcons);
+                               }
                        }
                }
-               if (ses->tcon_ipc && !ses->tcon_ipc->need_reconnect &&
-                   ses->tcon_ipc->dfs_path) {
-                       list_add_tail(&ses->tcon_ipc->ulist, head);
-               }
        }
        spin_unlock(&cifs_tcp_ses_lock);
-}
 
-static bool is_dfs_link(const char *path)
-{
-       char *s;
-
-       s = strchr(path + 1, '\\');
-       if (!s)
-               return false;
-       return !!strchr(s + 1, '\\');
-}
-
-static char *get_dfs_root(const char *path)
-{
-       char *s, *npath;
-
-       s = strchr(path + 1, '\\');
-       if (!s)
-               return ERR_PTR(-EINVAL);
-
-       s = strchr(s + 1, '\\');
-       if (!s)
-               return ERR_PTR(-EINVAL);
-
-       npath = kstrndup(path, s - path, GFP_KERNEL);
-       if (!npath)
-               return ERR_PTR(-ENOMEM);
+       list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
+               const char *path = tcon->dfs_path + 1;
+               struct cache_entry *ce;
+               struct dfs_info3_param *refs = NULL;
+               int numrefs = 0;
+               bool needs_refresh = false;
+               int rc = 0;
 
-       return npath;
-}
+               list_del_init(&tcon->ulist);
 
-static inline void put_tcp_server(struct TCP_Server_Info *server)
-{
-       cifs_put_tcp_session(server, 0);
-}
+               ses = find_ipc_from_server_path(sessions, path);
+               if (IS_ERR(ses))
+                       goto next_tcon;
 
-static struct TCP_Server_Info *get_tcp_server(struct smb3_fs_context *ctx)
-{
-       struct TCP_Server_Info *server;
+               down_read(&htable_rw_lock);
+               ce = lookup_cache_entry(path);
+               needs_refresh = IS_ERR(ce) || cache_entry_expired(ce);
+               up_read(&htable_rw_lock);
 
-       server = cifs_find_tcp_session(ctx);
-       if (IS_ERR_OR_NULL(server))
-               return NULL;
+               if (!needs_refresh)
+                       goto next_tcon;
+
+               xid = get_xid();
+               rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
+               free_xid(xid);
+
+               /* Create or update a cache entry with the new referral */
+               if (!rc) {
+                       down_write(&htable_rw_lock);
+                       ce = lookup_cache_entry(path);
+                       if (IS_ERR(ce))
+                               add_cache_entry_locked(refs, numrefs);
+                       else if (cache_entry_expired(ce))
+                               update_cache_entry_locked(ce, refs, numrefs);
+                       up_write(&htable_rw_lock);
+               }
 
-       spin_lock(&GlobalMid_Lock);
-       if (server->tcpStatus != CifsGood) {
-               spin_unlock(&GlobalMid_Lock);
-               put_tcp_server(server);
-               return NULL;
+next_tcon:
+               free_dfs_info_array(refs, numrefs);
+               cifs_put_tcon(tcon);
        }
-       spin_unlock(&GlobalMid_Lock);
-
-       return server;
 }
 
-/* Find root SMB session out of a DFS link path */
-static struct cifs_ses *find_root_ses(struct vol_info *vi,
-                                     struct cifs_tcon *tcon,
-                                     const char *path)
+static void refresh_cache(struct cifs_ses **sessions)
 {
-       char *rpath;
-       int rc;
-       struct cache_entry *ce;
-       struct dfs_info3_param ref = {0};
-       char *mdata = NULL, *devname = NULL;
-       struct TCP_Server_Info *server;
+       int i;
        struct cifs_ses *ses;
-       struct smb3_fs_context ctx = {NULL};
+       unsigned int xid;
+       char *ref_paths[CACHE_MAX_ENTRIES];
+       int count = 0;
+       struct cache_entry *ce;
 
-       rpath = get_dfs_root(path);
-       if (IS_ERR(rpath))
-               return ERR_CAST(rpath);
+       /*
+        * Refresh all cached entries.  Get all new referrals outside critical section to avoid
+        * starvation while performing SMB2 IOCTL on broken or slow connections.
 
+        * The cache entries may cover more paths than the active mounts
+        * (e.g. domain-based DFS referrals or multi tier DFS setups).
+        */
        down_read(&htable_rw_lock);
+       for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
+               struct hlist_head *l = &cache_htable[i];
 
-       ce = lookup_cache_entry(rpath, NULL);
-       if (IS_ERR(ce)) {
-               up_read(&htable_rw_lock);
-               ses = ERR_CAST(ce);
-               goto out;
-       }
-
-       rc = setup_referral(path, ce, &ref, get_tgt_name(ce));
-       if (rc) {
-               up_read(&htable_rw_lock);
-               ses = ERR_PTR(rc);
-               goto out;
+               hlist_for_each_entry(ce, l, hlist) {
+                       if (count == ARRAY_SIZE(ref_paths))
+                               goto out_unlock;
+                       if (hlist_unhashed(&ce->hlist) || !cache_entry_expired(ce) ||
+                           IS_ERR(find_ipc_from_server_path(sessions, ce->path)))
+                               continue;
+                       ref_paths[count++] = kstrdup(ce->path, GFP_ATOMIC);
+               }
        }
 
+out_unlock:
        up_read(&htable_rw_lock);
 
-       mdata = cifs_compose_mount_options(vi->mntdata, rpath, &ref,
-                                          &devname);
-       free_dfs_info_param(&ref);
-
-       if (IS_ERR(mdata)) {
-               ses = ERR_CAST(mdata);
-               mdata = NULL;
-               goto out;
-       }
-
-       rc = cifs_setup_volume_info(&ctx, NULL, devname);
-
-       if (rc) {
-               ses = ERR_PTR(rc);
-               goto out;
-       }
-
-       server = get_tcp_server(&ctx);
-       if (!server) {
-               ses = ERR_PTR(-EHOSTDOWN);
-               goto out;
-       }
-
-       ses = cifs_get_smb_ses(server, &ctx);
-
-out:
-       smb3_cleanup_fs_context_contents(&ctx);
-       kfree(mdata);
-       kfree(rpath);
-       kfree(devname);
-
-       return ses;
-}
-
-/* Refresh DFS cache entry from a given tcon */
-static int refresh_tcon(struct vol_info *vi, struct cifs_tcon *tcon)
-{
-       int rc = 0;
-       unsigned int xid;
-       const char *path, *npath;
-       struct cache_entry *ce;
-       struct cifs_ses *root_ses = NULL, *ses;
-       struct dfs_info3_param *refs = NULL;
-       int numrefs = 0;
-
-       xid = get_xid();
-
-       path = tcon->dfs_path + 1;
+       for (i = 0; i < count; i++) {
+               char *path = ref_paths[i];
+               struct dfs_info3_param *refs = NULL;
+               int numrefs = 0;
+               int rc = 0;
 
-       rc = get_normalized_path(path, &npath);
-       if (rc)
-               goto out_free_xid;
-
-       down_read(&htable_rw_lock);
-
-       ce = lookup_cache_entry(npath, NULL);
-       if (IS_ERR(ce)) {
-               rc = PTR_ERR(ce);
-               up_read(&htable_rw_lock);
-               goto out_free_path;
-       }
+               if (!path)
+                       continue;
 
-       if (!cache_entry_expired(ce)) {
-               up_read(&htable_rw_lock);
-               goto out_free_path;
-       }
+               ses = find_ipc_from_server_path(sessions, path);
+               if (IS_ERR(ses))
+                       goto next_referral;
 
-       up_read(&htable_rw_lock);
+               xid = get_xid();
+               rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
+               free_xid(xid);
 
-       /* If it's a DFS Link, then use root SMB session for refreshing it */
-       if (is_dfs_link(npath)) {
-               ses = root_ses = find_root_ses(vi, tcon, npath);
-               if (IS_ERR(ses)) {
-                       rc = PTR_ERR(ses);
-                       root_ses = NULL;
-                       goto out_free_path;
+               if (!rc) {
+                       down_write(&htable_rw_lock);
+                       ce = lookup_cache_entry(path);
+                       /*
+                        * We need to re-check it because other tasks might have it deleted or
+                        * updated.
+                        */
+                       if (!IS_ERR(ce) && cache_entry_expired(ce))
+                               update_cache_entry_locked(ce, refs, numrefs);
+                       up_write(&htable_rw_lock);
                }
-       } else {
-               ses = tcon->ses;
-       }
 
-       rc = get_dfs_referral(xid, ses, cache_nlsc, tcon->remap, npath, &refs,
-                             &numrefs);
-       if (!rc) {
-               dump_refs(refs, numrefs);
-               rc = update_cache_entry(npath, refs, numrefs);
+next_referral:
+               kfree(path);
                free_dfs_info_array(refs, numrefs);
        }
-
-       if (root_ses)
-               cifs_put_smb_ses(root_ses);
-
-out_free_path:
-       free_normalized_path(path, npath);
-
-out_free_xid:
-       free_xid(xid);
-       return rc;
 }
 
 /*
- * Worker that will refresh DFS cache based on lowest TTL value from a DFS
+ * Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS
  * referral.
  */
 static void refresh_cache_worker(struct work_struct *work)
 {
-       struct vol_info *vi, *nvi;
-       struct TCP_Server_Info *server;
-       LIST_HEAD(vols);
-       LIST_HEAD(tcons);
-       struct cifs_tcon *tcon, *ntcon;
-       int rc;
-
-       /*
-        * Find SMB volumes that are eligible (server->tcpStatus == CifsGood)
-        * for refreshing.
-        */
-       spin_lock(&vol_list_lock);
-       list_for_each_entry(vi, &vol_list, list) {
-               server = get_tcp_server(&vi->ctx);
-               if (!server)
-                       continue;
-
-               kref_get(&vi->refcnt);
-               list_add_tail(&vi->rlist, &vols);
-               put_tcp_server(server);
+       struct list_head mglist;
+       struct mount_group *mg, *tmp_mg;
+       struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
+       int max_sessions = ARRAY_SIZE(sessions) - 1;
+       int i = 0, count;
+
+       INIT_LIST_HEAD(&mglist);
+
+       /* Get refereces of mount groups */
+       mutex_lock(&mount_group_list_lock);
+       list_for_each_entry(mg, &mount_group_list, list) {
+               kref_get(&mg->refcount);
+               list_add(&mg->refresh_list, &mglist);
        }
-       spin_unlock(&vol_list_lock);
-
-       /* Walk through all TCONs and refresh any expired cache entry */
-       list_for_each_entry_safe(vi, nvi, &vols, rlist) {
-               spin_lock(&vi->ctx_lock);
-               server = get_tcp_server(&vi->ctx);
-               spin_unlock(&vi->ctx_lock);
+       mutex_unlock(&mount_group_list_lock);
 
-               if (!server)
-                       goto next_vol;
-
-               get_tcons(server, &tcons);
-               rc = 0;
-
-               list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
-                       /*
-                        * Skip tcp server if any of its tcons failed to refresh
-                        * (possibily due to reconnects).
-                        */
-                       if (!rc)
-                               rc = refresh_tcon(vi, tcon);
+       /* Fill in local array with an NULL-terminated list of all referral server sessions */
+       list_for_each_entry(mg, &mglist, refresh_list) {
+               if (i >= max_sessions)
+                       break;
 
-                       list_del_init(&tcon->ulist);
-                       cifs_put_tcon(tcon);
-               }
+               spin_lock(&mg->lock);
+               if (i + mg->num_sessions > max_sessions)
+                       count = max_sessions - i;
+               else
+                       count = mg->num_sessions;
+               memcpy(&sessions[i], mg->sessions, count * sizeof(mg->sessions[0]));
+               spin_unlock(&mg->lock);
+               i += count;
+       }
 
-               put_tcp_server(server);
+       if (sessions[0]) {
+               /* Refresh all active mounts and cached entries */
+               refresh_mounts(sessions);
+               refresh_cache(sessions);
+       }
 
-next_vol:
-               list_del_init(&vi->rlist);
-               kref_put(&vi->refcnt, vol_release);
+       list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) {
+               list_del_init(&mg->refresh_list);
+               kref_put(&mg->refcount, mount_group_release);
        }
 
        spin_lock(&cache_ttl_lock);