162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * DFS referral cache routines
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/jhash.h>
962306a36Sopenharmony_ci#include <linux/ktime.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/proc_fs.h>
1262306a36Sopenharmony_ci#include <linux/nls.h>
1362306a36Sopenharmony_ci#include <linux/workqueue.h>
1462306a36Sopenharmony_ci#include <linux/uuid.h>
1562306a36Sopenharmony_ci#include "cifsglob.h"
1662306a36Sopenharmony_ci#include "smb2pdu.h"
1762306a36Sopenharmony_ci#include "smb2proto.h"
1862306a36Sopenharmony_ci#include "cifsproto.h"
1962306a36Sopenharmony_ci#include "cifs_debug.h"
2062306a36Sopenharmony_ci#include "cifs_unicode.h"
2162306a36Sopenharmony_ci#include "smb2glob.h"
2262306a36Sopenharmony_ci#include "dns_resolve.h"
2362306a36Sopenharmony_ci#include "dfs.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include "dfs_cache.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define CACHE_HTABLE_SIZE	32
2862306a36Sopenharmony_ci#define CACHE_MAX_ENTRIES	64
2962306a36Sopenharmony_ci#define CACHE_MIN_TTL		120 /* 2 minutes */
3062306a36Sopenharmony_ci#define CACHE_DEFAULT_TTL	300 /* 5 minutes */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistruct cache_dfs_tgt {
3362306a36Sopenharmony_ci	char *name;
3462306a36Sopenharmony_ci	int path_consumed;
3562306a36Sopenharmony_ci	struct list_head list;
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct cache_entry {
3962306a36Sopenharmony_ci	struct hlist_node hlist;
4062306a36Sopenharmony_ci	const char *path;
4162306a36Sopenharmony_ci	int hdr_flags; /* RESP_GET_DFS_REFERRAL.ReferralHeaderFlags */
4262306a36Sopenharmony_ci	int ttl; /* DFS_REREFERRAL_V3.TimeToLive */
4362306a36Sopenharmony_ci	int srvtype; /* DFS_REREFERRAL_V3.ServerType */
4462306a36Sopenharmony_ci	int ref_flags; /* DFS_REREFERRAL_V3.ReferralEntryFlags */
4562306a36Sopenharmony_ci	struct timespec64 etime;
4662306a36Sopenharmony_ci	int path_consumed; /* RESP_GET_DFS_REFERRAL.PathConsumed */
4762306a36Sopenharmony_ci	int numtgts;
4862306a36Sopenharmony_ci	struct list_head tlist;
4962306a36Sopenharmony_ci	struct cache_dfs_tgt *tgthint;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic struct kmem_cache *cache_slab __read_mostly;
5362306a36Sopenharmony_cistruct workqueue_struct *dfscache_wq;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ciatomic_t dfs_cache_ttl;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct nls_table *cache_cp;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/*
6062306a36Sopenharmony_ci * Number of entries in the cache
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_cistatic atomic_t cache_count;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
6562306a36Sopenharmony_cistatic DECLARE_RWSEM(htable_rw_lock);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci/**
6862306a36Sopenharmony_ci * dfs_cache_canonical_path - get a canonical DFS path
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * @path: DFS path
7162306a36Sopenharmony_ci * @cp: codepage
7262306a36Sopenharmony_ci * @remap: mapping type
7362306a36Sopenharmony_ci *
7462306a36Sopenharmony_ci * Return canonical path if success, otherwise error.
7562306a36Sopenharmony_ci */
7662306a36Sopenharmony_cichar *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	char *tmp;
7962306a36Sopenharmony_ci	int plen = 0;
8062306a36Sopenharmony_ci	char *npath;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/'))
8362306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	if (unlikely(strcmp(cp->charset, cache_cp->charset))) {
8662306a36Sopenharmony_ci		tmp = (char *)cifs_strndup_to_utf16(path, strlen(path), &plen, cp, remap);
8762306a36Sopenharmony_ci		if (!tmp) {
8862306a36Sopenharmony_ci			cifs_dbg(VFS, "%s: failed to convert path to utf16\n", __func__);
8962306a36Sopenharmony_ci			return ERR_PTR(-EINVAL);
9062306a36Sopenharmony_ci		}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		npath = cifs_strndup_from_utf16(tmp, plen, true, cache_cp);
9362306a36Sopenharmony_ci		kfree(tmp);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		if (!npath) {
9662306a36Sopenharmony_ci			cifs_dbg(VFS, "%s: failed to convert path from utf16\n", __func__);
9762306a36Sopenharmony_ci			return ERR_PTR(-EINVAL);
9862306a36Sopenharmony_ci		}
9962306a36Sopenharmony_ci	} else {
10062306a36Sopenharmony_ci		npath = kstrdup(path, GFP_KERNEL);
10162306a36Sopenharmony_ci		if (!npath)
10262306a36Sopenharmony_ci			return ERR_PTR(-ENOMEM);
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci	convert_delimiter(npath, '\\');
10562306a36Sopenharmony_ci	return npath;
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic inline bool cache_entry_expired(const struct cache_entry *ce)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct timespec64 ts;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	ktime_get_coarse_real_ts64(&ts);
11362306a36Sopenharmony_ci	return timespec64_compare(&ts, &ce->etime) >= 0;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic inline void free_tgts(struct cache_entry *ce)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct cache_dfs_tgt *t, *n;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	list_for_each_entry_safe(t, n, &ce->tlist, list) {
12162306a36Sopenharmony_ci		list_del(&t->list);
12262306a36Sopenharmony_ci		kfree(t->name);
12362306a36Sopenharmony_ci		kfree(t);
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic inline void flush_cache_ent(struct cache_entry *ce)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	hlist_del_init(&ce->hlist);
13062306a36Sopenharmony_ci	kfree(ce->path);
13162306a36Sopenharmony_ci	free_tgts(ce);
13262306a36Sopenharmony_ci	atomic_dec(&cache_count);
13362306a36Sopenharmony_ci	kmem_cache_free(cache_slab, ce);
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic void flush_cache_ents(void)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	int i;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
14162306a36Sopenharmony_ci		struct hlist_head *l = &cache_htable[i];
14262306a36Sopenharmony_ci		struct hlist_node *n;
14362306a36Sopenharmony_ci		struct cache_entry *ce;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		hlist_for_each_entry_safe(ce, n, l, hlist) {
14662306a36Sopenharmony_ci			if (!hlist_unhashed(&ce->hlist))
14762306a36Sopenharmony_ci				flush_cache_ent(ce);
14862306a36Sopenharmony_ci		}
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci/*
15362306a36Sopenharmony_ci * dfs cache /proc file
15462306a36Sopenharmony_ci */
15562306a36Sopenharmony_cistatic int dfscache_proc_show(struct seq_file *m, void *v)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	int i;
15862306a36Sopenharmony_ci	struct cache_entry *ce;
15962306a36Sopenharmony_ci	struct cache_dfs_tgt *t;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	seq_puts(m, "DFS cache\n---------\n");
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	down_read(&htable_rw_lock);
16462306a36Sopenharmony_ci	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
16562306a36Sopenharmony_ci		struct hlist_head *l = &cache_htable[i];
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		hlist_for_each_entry(ce, l, hlist) {
16862306a36Sopenharmony_ci			if (hlist_unhashed(&ce->hlist))
16962306a36Sopenharmony_ci				continue;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci			seq_printf(m,
17262306a36Sopenharmony_ci				   "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",
17362306a36Sopenharmony_ci				   ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link",
17462306a36Sopenharmony_ci				   ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags,
17562306a36Sopenharmony_ci				   DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
17662306a36Sopenharmony_ci				   ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci			list_for_each_entry(t, &ce->tlist, list) {
17962306a36Sopenharmony_ci				seq_printf(m, "  %s%s\n",
18062306a36Sopenharmony_ci					   t->name,
18162306a36Sopenharmony_ci					   READ_ONCE(ce->tgthint) == t ? " (target hint)" : "");
18262306a36Sopenharmony_ci			}
18362306a36Sopenharmony_ci		}
18462306a36Sopenharmony_ci	}
18562306a36Sopenharmony_ci	up_read(&htable_rw_lock);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic ssize_t dfscache_proc_write(struct file *file, const char __user *buffer,
19162306a36Sopenharmony_ci				   size_t count, loff_t *ppos)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	char c;
19462306a36Sopenharmony_ci	int rc;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	rc = get_user(c, buffer);
19762306a36Sopenharmony_ci	if (rc)
19862306a36Sopenharmony_ci		return rc;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (c != '0')
20162306a36Sopenharmony_ci		return -EINVAL;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	cifs_dbg(FYI, "clearing dfs cache\n");
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	down_write(&htable_rw_lock);
20662306a36Sopenharmony_ci	flush_cache_ents();
20762306a36Sopenharmony_ci	up_write(&htable_rw_lock);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return count;
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int dfscache_proc_open(struct inode *inode, struct file *file)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	return single_open(file, dfscache_proc_show, NULL);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ciconst struct proc_ops dfscache_proc_ops = {
21862306a36Sopenharmony_ci	.proc_open	= dfscache_proc_open,
21962306a36Sopenharmony_ci	.proc_read	= seq_read,
22062306a36Sopenharmony_ci	.proc_lseek	= seq_lseek,
22162306a36Sopenharmony_ci	.proc_release	= single_release,
22262306a36Sopenharmony_ci	.proc_write	= dfscache_proc_write,
22362306a36Sopenharmony_ci};
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci#ifdef CONFIG_CIFS_DEBUG2
22662306a36Sopenharmony_cistatic inline void dump_tgts(const struct cache_entry *ce)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	struct cache_dfs_tgt *t;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	cifs_dbg(FYI, "target list:\n");
23162306a36Sopenharmony_ci	list_for_each_entry(t, &ce->tlist, list) {
23262306a36Sopenharmony_ci		cifs_dbg(FYI, "  %s%s\n", t->name,
23362306a36Sopenharmony_ci			 READ_ONCE(ce->tgthint) == t ? " (target hint)" : "");
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic inline void dump_ce(const struct cache_entry *ce)
23862306a36Sopenharmony_ci{
23962306a36Sopenharmony_ci	cifs_dbg(FYI, "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",
24062306a36Sopenharmony_ci		 ce->path,
24162306a36Sopenharmony_ci		 ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl,
24262306a36Sopenharmony_ci		 ce->etime.tv_nsec,
24362306a36Sopenharmony_ci		 ce->hdr_flags, ce->ref_flags,
24462306a36Sopenharmony_ci		 DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
24562306a36Sopenharmony_ci		 ce->path_consumed,
24662306a36Sopenharmony_ci		 cache_entry_expired(ce) ? "yes" : "no");
24762306a36Sopenharmony_ci	dump_tgts(ce);
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic inline void dump_refs(const struct dfs_info3_param *refs, int numrefs)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	int i;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	cifs_dbg(FYI, "DFS referrals returned by the server:\n");
25562306a36Sopenharmony_ci	for (i = 0; i < numrefs; i++) {
25662306a36Sopenharmony_ci		const struct dfs_info3_param *ref = &refs[i];
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci		cifs_dbg(FYI,
25962306a36Sopenharmony_ci			 "\n"
26062306a36Sopenharmony_ci			 "flags:         0x%x\n"
26162306a36Sopenharmony_ci			 "path_consumed: %d\n"
26262306a36Sopenharmony_ci			 "server_type:   0x%x\n"
26362306a36Sopenharmony_ci			 "ref_flag:      0x%x\n"
26462306a36Sopenharmony_ci			 "path_name:     %s\n"
26562306a36Sopenharmony_ci			 "node_name:     %s\n"
26662306a36Sopenharmony_ci			 "ttl:           %d (%dm)\n",
26762306a36Sopenharmony_ci			 ref->flags, ref->path_consumed, ref->server_type,
26862306a36Sopenharmony_ci			 ref->ref_flag, ref->path_name, ref->node_name,
26962306a36Sopenharmony_ci			 ref->ttl, ref->ttl / 60);
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci#else
27362306a36Sopenharmony_ci#define dump_tgts(e)
27462306a36Sopenharmony_ci#define dump_ce(e)
27562306a36Sopenharmony_ci#define dump_refs(r, n)
27662306a36Sopenharmony_ci#endif
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci/**
27962306a36Sopenharmony_ci * dfs_cache_init - Initialize DFS referral cache.
28062306a36Sopenharmony_ci *
28162306a36Sopenharmony_ci * Return zero if initialized successfully, otherwise non-zero.
28262306a36Sopenharmony_ci */
28362306a36Sopenharmony_ciint dfs_cache_init(void)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	int rc;
28662306a36Sopenharmony_ci	int i;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	dfscache_wq = alloc_workqueue("cifs-dfscache",
28962306a36Sopenharmony_ci				      WQ_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM,
29062306a36Sopenharmony_ci				      0);
29162306a36Sopenharmony_ci	if (!dfscache_wq)
29262306a36Sopenharmony_ci		return -ENOMEM;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	cache_slab = kmem_cache_create("cifs_dfs_cache",
29562306a36Sopenharmony_ci				       sizeof(struct cache_entry), 0,
29662306a36Sopenharmony_ci				       SLAB_HWCACHE_ALIGN, NULL);
29762306a36Sopenharmony_ci	if (!cache_slab) {
29862306a36Sopenharmony_ci		rc = -ENOMEM;
29962306a36Sopenharmony_ci		goto out_destroy_wq;
30062306a36Sopenharmony_ci	}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	for (i = 0; i < CACHE_HTABLE_SIZE; i++)
30362306a36Sopenharmony_ci		INIT_HLIST_HEAD(&cache_htable[i]);
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	atomic_set(&cache_count, 0);
30662306a36Sopenharmony_ci	atomic_set(&dfs_cache_ttl, CACHE_DEFAULT_TTL);
30762306a36Sopenharmony_ci	cache_cp = load_nls("utf8");
30862306a36Sopenharmony_ci	if (!cache_cp)
30962306a36Sopenharmony_ci		cache_cp = load_nls_default();
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__);
31262306a36Sopenharmony_ci	return 0;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ciout_destroy_wq:
31562306a36Sopenharmony_ci	destroy_workqueue(dfscache_wq);
31662306a36Sopenharmony_ci	return rc;
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_cistatic int cache_entry_hash(const void *data, int size, unsigned int *hash)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	int i, clen;
32262306a36Sopenharmony_ci	const unsigned char *s = data;
32362306a36Sopenharmony_ci	wchar_t c;
32462306a36Sopenharmony_ci	unsigned int h = 0;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	for (i = 0; i < size; i += clen) {
32762306a36Sopenharmony_ci		clen = cache_cp->char2uni(&s[i], size - i, &c);
32862306a36Sopenharmony_ci		if (unlikely(clen < 0)) {
32962306a36Sopenharmony_ci			cifs_dbg(VFS, "%s: can't convert char\n", __func__);
33062306a36Sopenharmony_ci			return clen;
33162306a36Sopenharmony_ci		}
33262306a36Sopenharmony_ci		c = cifs_toupper(c);
33362306a36Sopenharmony_ci		h = jhash(&c, sizeof(c), h);
33462306a36Sopenharmony_ci	}
33562306a36Sopenharmony_ci	*hash = h % CACHE_HTABLE_SIZE;
33662306a36Sopenharmony_ci	return 0;
33762306a36Sopenharmony_ci}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci/* Return target hint of a DFS cache entry */
34062306a36Sopenharmony_cistatic inline char *get_tgt_name(const struct cache_entry *ce)
34162306a36Sopenharmony_ci{
34262306a36Sopenharmony_ci	struct cache_dfs_tgt *t = READ_ONCE(ce->tgthint);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	return t ? t->name : ERR_PTR(-ENOENT);
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci/* Return expire time out of a new entry's TTL */
34862306a36Sopenharmony_cistatic inline struct timespec64 get_expire_time(int ttl)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	struct timespec64 ts = {
35162306a36Sopenharmony_ci		.tv_sec = ttl,
35262306a36Sopenharmony_ci		.tv_nsec = 0,
35362306a36Sopenharmony_ci	};
35462306a36Sopenharmony_ci	struct timespec64 now;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	ktime_get_coarse_real_ts64(&now);
35762306a36Sopenharmony_ci	return timespec64_add(now, ts);
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci/* Allocate a new DFS target */
36162306a36Sopenharmony_cistatic struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed)
36262306a36Sopenharmony_ci{
36362306a36Sopenharmony_ci	struct cache_dfs_tgt *t;
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci	t = kmalloc(sizeof(*t), GFP_ATOMIC);
36662306a36Sopenharmony_ci	if (!t)
36762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
36862306a36Sopenharmony_ci	t->name = kstrdup(name, GFP_ATOMIC);
36962306a36Sopenharmony_ci	if (!t->name) {
37062306a36Sopenharmony_ci		kfree(t);
37162306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
37262306a36Sopenharmony_ci	}
37362306a36Sopenharmony_ci	t->path_consumed = path_consumed;
37462306a36Sopenharmony_ci	INIT_LIST_HEAD(&t->list);
37562306a36Sopenharmony_ci	return t;
37662306a36Sopenharmony_ci}
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci/*
37962306a36Sopenharmony_ci * Copy DFS referral information to a cache entry and conditionally update
38062306a36Sopenharmony_ci * target hint.
38162306a36Sopenharmony_ci */
38262306a36Sopenharmony_cistatic int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
38362306a36Sopenharmony_ci			 struct cache_entry *ce, const char *tgthint)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	struct cache_dfs_tgt *target;
38662306a36Sopenharmony_ci	int i;
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci	ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
38962306a36Sopenharmony_ci	ce->etime = get_expire_time(ce->ttl);
39062306a36Sopenharmony_ci	ce->srvtype = refs[0].server_type;
39162306a36Sopenharmony_ci	ce->hdr_flags = refs[0].flags;
39262306a36Sopenharmony_ci	ce->ref_flags = refs[0].ref_flag;
39362306a36Sopenharmony_ci	ce->path_consumed = refs[0].path_consumed;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	for (i = 0; i < numrefs; i++) {
39662306a36Sopenharmony_ci		struct cache_dfs_tgt *t;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci		t = alloc_target(refs[i].node_name, refs[i].path_consumed);
39962306a36Sopenharmony_ci		if (IS_ERR(t)) {
40062306a36Sopenharmony_ci			free_tgts(ce);
40162306a36Sopenharmony_ci			return PTR_ERR(t);
40262306a36Sopenharmony_ci		}
40362306a36Sopenharmony_ci		if (tgthint && !strcasecmp(t->name, tgthint)) {
40462306a36Sopenharmony_ci			list_add(&t->list, &ce->tlist);
40562306a36Sopenharmony_ci			tgthint = NULL;
40662306a36Sopenharmony_ci		} else {
40762306a36Sopenharmony_ci			list_add_tail(&t->list, &ce->tlist);
40862306a36Sopenharmony_ci		}
40962306a36Sopenharmony_ci		ce->numtgts++;
41062306a36Sopenharmony_ci	}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt,
41362306a36Sopenharmony_ci					  list);
41462306a36Sopenharmony_ci	WRITE_ONCE(ce->tgthint, target);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	return 0;
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci/* Allocate a new cache entry */
42062306a36Sopenharmony_cistatic struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	struct cache_entry *ce;
42362306a36Sopenharmony_ci	int rc;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	ce = kmem_cache_zalloc(cache_slab, GFP_KERNEL);
42662306a36Sopenharmony_ci	if (!ce)
42762306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	ce->path = refs[0].path_name;
43062306a36Sopenharmony_ci	refs[0].path_name = NULL;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	INIT_HLIST_NODE(&ce->hlist);
43362306a36Sopenharmony_ci	INIT_LIST_HEAD(&ce->tlist);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	rc = copy_ref_data(refs, numrefs, ce, NULL);
43662306a36Sopenharmony_ci	if (rc) {
43762306a36Sopenharmony_ci		kfree(ce->path);
43862306a36Sopenharmony_ci		kmem_cache_free(cache_slab, ce);
43962306a36Sopenharmony_ci		ce = ERR_PTR(rc);
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci	return ce;
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_cistatic void remove_oldest_entry_locked(void)
44562306a36Sopenharmony_ci{
44662306a36Sopenharmony_ci	int i;
44762306a36Sopenharmony_ci	struct cache_entry *ce;
44862306a36Sopenharmony_ci	struct cache_entry *to_del = NULL;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	WARN_ON(!rwsem_is_locked(&htable_rw_lock));
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
45362306a36Sopenharmony_ci		struct hlist_head *l = &cache_htable[i];
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci		hlist_for_each_entry(ce, l, hlist) {
45662306a36Sopenharmony_ci			if (hlist_unhashed(&ce->hlist))
45762306a36Sopenharmony_ci				continue;
45862306a36Sopenharmony_ci			if (!to_del || timespec64_compare(&ce->etime,
45962306a36Sopenharmony_ci							  &to_del->etime) < 0)
46062306a36Sopenharmony_ci				to_del = ce;
46162306a36Sopenharmony_ci		}
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (!to_del) {
46562306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: no entry to remove\n", __func__);
46662306a36Sopenharmony_ci		return;
46762306a36Sopenharmony_ci	}
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: removing entry\n", __func__);
47062306a36Sopenharmony_ci	dump_ce(to_del);
47162306a36Sopenharmony_ci	flush_cache_ent(to_del);
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci/* Add a new DFS cache entry */
47562306a36Sopenharmony_cistatic struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs,
47662306a36Sopenharmony_ci						  int numrefs)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	int rc;
47962306a36Sopenharmony_ci	struct cache_entry *ce;
48062306a36Sopenharmony_ci	unsigned int hash;
48162306a36Sopenharmony_ci	int ttl;
48262306a36Sopenharmony_ci
48362306a36Sopenharmony_ci	WARN_ON(!rwsem_is_locked(&htable_rw_lock));
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) {
48662306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES);
48762306a36Sopenharmony_ci		remove_oldest_entry_locked();
48862306a36Sopenharmony_ci	}
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
49162306a36Sopenharmony_ci	if (rc)
49262306a36Sopenharmony_ci		return ERR_PTR(rc);
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	ce = alloc_cache_entry(refs, numrefs);
49562306a36Sopenharmony_ci	if (IS_ERR(ce))
49662306a36Sopenharmony_ci		return ce;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	ttl = min_t(int, atomic_read(&dfs_cache_ttl), ce->ttl);
49962306a36Sopenharmony_ci	atomic_set(&dfs_cache_ttl, ttl);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	hlist_add_head(&ce->hlist, &cache_htable[hash]);
50262306a36Sopenharmony_ci	dump_ce(ce);
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	atomic_inc(&cache_count);
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	return ce;
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci/* Check if two DFS paths are equal.  @s1 and @s2 are expected to be in @cache_cp's charset */
51062306a36Sopenharmony_cistatic bool dfs_path_equal(const char *s1, int len1, const char *s2, int len2)
51162306a36Sopenharmony_ci{
51262306a36Sopenharmony_ci	int i, l1, l2;
51362306a36Sopenharmony_ci	wchar_t c1, c2;
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	if (len1 != len2)
51662306a36Sopenharmony_ci		return false;
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	for (i = 0; i < len1; i += l1) {
51962306a36Sopenharmony_ci		l1 = cache_cp->char2uni(&s1[i], len1 - i, &c1);
52062306a36Sopenharmony_ci		l2 = cache_cp->char2uni(&s2[i], len2 - i, &c2);
52162306a36Sopenharmony_ci		if (unlikely(l1 < 0 && l2 < 0)) {
52262306a36Sopenharmony_ci			if (s1[i] != s2[i])
52362306a36Sopenharmony_ci				return false;
52462306a36Sopenharmony_ci			l1 = 1;
52562306a36Sopenharmony_ci			continue;
52662306a36Sopenharmony_ci		}
52762306a36Sopenharmony_ci		if (l1 != l2)
52862306a36Sopenharmony_ci			return false;
52962306a36Sopenharmony_ci		if (cifs_toupper(c1) != cifs_toupper(c2))
53062306a36Sopenharmony_ci			return false;
53162306a36Sopenharmony_ci	}
53262306a36Sopenharmony_ci	return true;
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_cistatic struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len)
53662306a36Sopenharmony_ci{
53762306a36Sopenharmony_ci	struct cache_entry *ce;
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_ci	hlist_for_each_entry(ce, &cache_htable[hash], hlist) {
54062306a36Sopenharmony_ci		if (dfs_path_equal(ce->path, strlen(ce->path), path, len)) {
54162306a36Sopenharmony_ci			dump_ce(ce);
54262306a36Sopenharmony_ci			return ce;
54362306a36Sopenharmony_ci		}
54462306a36Sopenharmony_ci	}
54562306a36Sopenharmony_ci	return ERR_PTR(-ENOENT);
54662306a36Sopenharmony_ci}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci/*
54962306a36Sopenharmony_ci * Find a DFS cache entry in hash table and optionally check prefix path against normalized @path.
55062306a36Sopenharmony_ci *
55162306a36Sopenharmony_ci * Use whole path components in the match.  Must be called with htable_rw_lock held.
55262306a36Sopenharmony_ci *
55362306a36Sopenharmony_ci * Return cached entry if successful.
55462306a36Sopenharmony_ci * Return ERR_PTR(-ENOENT) if the entry is not found.
55562306a36Sopenharmony_ci * Return error ptr otherwise.
55662306a36Sopenharmony_ci */
55762306a36Sopenharmony_cistatic struct cache_entry *lookup_cache_entry(const char *path)
55862306a36Sopenharmony_ci{
55962306a36Sopenharmony_ci	struct cache_entry *ce;
56062306a36Sopenharmony_ci	int cnt = 0;
56162306a36Sopenharmony_ci	const char *s = path, *e;
56262306a36Sopenharmony_ci	char sep = *s;
56362306a36Sopenharmony_ci	unsigned int hash;
56462306a36Sopenharmony_ci	int rc;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	while ((s = strchr(s, sep)) && ++cnt < 3)
56762306a36Sopenharmony_ci		s++;
56862306a36Sopenharmony_ci
56962306a36Sopenharmony_ci	if (cnt < 3) {
57062306a36Sopenharmony_ci		rc = cache_entry_hash(path, strlen(path), &hash);
57162306a36Sopenharmony_ci		if (rc)
57262306a36Sopenharmony_ci			return ERR_PTR(rc);
57362306a36Sopenharmony_ci		return __lookup_cache_entry(path, hash, strlen(path));
57462306a36Sopenharmony_ci	}
57562306a36Sopenharmony_ci	/*
57662306a36Sopenharmony_ci	 * Handle paths that have more than two path components and are a complete prefix of the DFS
57762306a36Sopenharmony_ci	 * referral request path (@path).
57862306a36Sopenharmony_ci	 *
57962306a36Sopenharmony_ci	 * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
58062306a36Sopenharmony_ci	 */
58162306a36Sopenharmony_ci	e = path + strlen(path) - 1;
58262306a36Sopenharmony_ci	while (e > s) {
58362306a36Sopenharmony_ci		int len;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci		/* skip separators */
58662306a36Sopenharmony_ci		while (e > s && *e == sep)
58762306a36Sopenharmony_ci			e--;
58862306a36Sopenharmony_ci		if (e == s)
58962306a36Sopenharmony_ci			break;
59062306a36Sopenharmony_ci
59162306a36Sopenharmony_ci		len = e + 1 - path;
59262306a36Sopenharmony_ci		rc = cache_entry_hash(path, len, &hash);
59362306a36Sopenharmony_ci		if (rc)
59462306a36Sopenharmony_ci			return ERR_PTR(rc);
59562306a36Sopenharmony_ci		ce = __lookup_cache_entry(path, hash, len);
59662306a36Sopenharmony_ci		if (!IS_ERR(ce))
59762306a36Sopenharmony_ci			return ce;
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci		/* backward until separator */
60062306a36Sopenharmony_ci		while (e > s && *e != sep)
60162306a36Sopenharmony_ci			e--;
60262306a36Sopenharmony_ci	}
60362306a36Sopenharmony_ci	return ERR_PTR(-ENOENT);
60462306a36Sopenharmony_ci}
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci/**
60762306a36Sopenharmony_ci * dfs_cache_destroy - destroy DFS referral cache
60862306a36Sopenharmony_ci */
60962306a36Sopenharmony_civoid dfs_cache_destroy(void)
61062306a36Sopenharmony_ci{
61162306a36Sopenharmony_ci	unload_nls(cache_cp);
61262306a36Sopenharmony_ci	flush_cache_ents();
61362306a36Sopenharmony_ci	kmem_cache_destroy(cache_slab);
61462306a36Sopenharmony_ci	destroy_workqueue(dfscache_wq);
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__);
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci/* Update a cache entry with the new referral in @refs */
62062306a36Sopenharmony_cistatic int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_info3_param *refs,
62162306a36Sopenharmony_ci				     int numrefs)
62262306a36Sopenharmony_ci{
62362306a36Sopenharmony_ci	struct cache_dfs_tgt *target;
62462306a36Sopenharmony_ci	char *th = NULL;
62562306a36Sopenharmony_ci	int rc;
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	WARN_ON(!rwsem_is_locked(&htable_rw_lock));
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci	target = READ_ONCE(ce->tgthint);
63062306a36Sopenharmony_ci	if (target) {
63162306a36Sopenharmony_ci		th = kstrdup(target->name, GFP_ATOMIC);
63262306a36Sopenharmony_ci		if (!th)
63362306a36Sopenharmony_ci			return -ENOMEM;
63462306a36Sopenharmony_ci	}
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	free_tgts(ce);
63762306a36Sopenharmony_ci	ce->numtgts = 0;
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	rc = copy_ref_data(refs, numrefs, ce, th);
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	kfree(th);
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	return rc;
64462306a36Sopenharmony_ci}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_cistatic int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const char *path,
64762306a36Sopenharmony_ci			    struct dfs_info3_param **refs, int *numrefs)
64862306a36Sopenharmony_ci{
64962306a36Sopenharmony_ci	int rc;
65062306a36Sopenharmony_ci	int i;
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	*refs = NULL;
65362306a36Sopenharmony_ci	*numrefs = 0;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	if (!ses || !ses->server || !ses->server->ops->get_dfs_refer)
65662306a36Sopenharmony_ci		return -EOPNOTSUPP;
65762306a36Sopenharmony_ci	if (unlikely(!cache_cp))
65862306a36Sopenharmony_ci		return -EINVAL;
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: ipc=%s referral=%s\n", __func__, ses->tcon_ipc->tree_name, path);
66162306a36Sopenharmony_ci	rc =  ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
66262306a36Sopenharmony_ci					      NO_MAP_UNI_RSVD);
66362306a36Sopenharmony_ci	if (!rc) {
66462306a36Sopenharmony_ci		struct dfs_info3_param *ref = *refs;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci		for (i = 0; i < *numrefs; i++)
66762306a36Sopenharmony_ci			convert_delimiter(ref[i].path_name, '\\');
66862306a36Sopenharmony_ci	}
66962306a36Sopenharmony_ci	return rc;
67062306a36Sopenharmony_ci}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci/*
67362306a36Sopenharmony_ci * Find, create or update a DFS cache entry.
67462306a36Sopenharmony_ci *
67562306a36Sopenharmony_ci * If the entry wasn't found, it will create a new one. Or if it was found but
67662306a36Sopenharmony_ci * expired, then it will update the entry accordingly.
67762306a36Sopenharmony_ci *
67862306a36Sopenharmony_ci * For interlinks, cifs_mount() and expand_dfs_referral() are supposed to
67962306a36Sopenharmony_ci * handle them properly.
68062306a36Sopenharmony_ci *
68162306a36Sopenharmony_ci * On success, return entry with acquired lock for reading, otherwise error ptr.
68262306a36Sopenharmony_ci */
68362306a36Sopenharmony_cistatic struct cache_entry *cache_refresh_path(const unsigned int xid,
68462306a36Sopenharmony_ci					      struct cifs_ses *ses,
68562306a36Sopenharmony_ci					      const char *path,
68662306a36Sopenharmony_ci					      bool force_refresh)
68762306a36Sopenharmony_ci{
68862306a36Sopenharmony_ci	struct dfs_info3_param *refs = NULL;
68962306a36Sopenharmony_ci	struct cache_entry *ce;
69062306a36Sopenharmony_ci	int numrefs = 0;
69162306a36Sopenharmony_ci	int rc;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: search path: %s\n", __func__, path);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	down_read(&htable_rw_lock);
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
69862306a36Sopenharmony_ci	if (!IS_ERR(ce)) {
69962306a36Sopenharmony_ci		if (!force_refresh && !cache_entry_expired(ce))
70062306a36Sopenharmony_ci			return ce;
70162306a36Sopenharmony_ci	} else if (PTR_ERR(ce) != -ENOENT) {
70262306a36Sopenharmony_ci		up_read(&htable_rw_lock);
70362306a36Sopenharmony_ci		return ce;
70462306a36Sopenharmony_ci	}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	/*
70762306a36Sopenharmony_ci	 * Unlock shared access as we don't want to hold any locks while getting
70862306a36Sopenharmony_ci	 * a new referral.  The @ses used for performing the I/O could be
70962306a36Sopenharmony_ci	 * reconnecting and it acquires @htable_rw_lock to look up the dfs cache
71062306a36Sopenharmony_ci	 * in order to failover -- if necessary.
71162306a36Sopenharmony_ci	 */
71262306a36Sopenharmony_ci	up_read(&htable_rw_lock);
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	/*
71562306a36Sopenharmony_ci	 * Either the entry was not found, or it is expired, or it is a forced
71662306a36Sopenharmony_ci	 * refresh.
71762306a36Sopenharmony_ci	 * Request a new DFS referral in order to create or update a cache entry.
71862306a36Sopenharmony_ci	 */
71962306a36Sopenharmony_ci	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
72062306a36Sopenharmony_ci	if (rc) {
72162306a36Sopenharmony_ci		ce = ERR_PTR(rc);
72262306a36Sopenharmony_ci		goto out;
72362306a36Sopenharmony_ci	}
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	dump_refs(refs, numrefs);
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	down_write(&htable_rw_lock);
72862306a36Sopenharmony_ci	/* Re-check as another task might have it added or refreshed already */
72962306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
73062306a36Sopenharmony_ci	if (!IS_ERR(ce)) {
73162306a36Sopenharmony_ci		if (force_refresh || cache_entry_expired(ce)) {
73262306a36Sopenharmony_ci			rc = update_cache_entry_locked(ce, refs, numrefs);
73362306a36Sopenharmony_ci			if (rc)
73462306a36Sopenharmony_ci				ce = ERR_PTR(rc);
73562306a36Sopenharmony_ci		}
73662306a36Sopenharmony_ci	} else if (PTR_ERR(ce) == -ENOENT) {
73762306a36Sopenharmony_ci		ce = add_cache_entry_locked(refs, numrefs);
73862306a36Sopenharmony_ci	}
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	if (IS_ERR(ce)) {
74162306a36Sopenharmony_ci		up_write(&htable_rw_lock);
74262306a36Sopenharmony_ci		goto out;
74362306a36Sopenharmony_ci	}
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci	downgrade_write(&htable_rw_lock);
74662306a36Sopenharmony_ciout:
74762306a36Sopenharmony_ci	free_dfs_info_array(refs, numrefs);
74862306a36Sopenharmony_ci	return ce;
74962306a36Sopenharmony_ci}
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci/*
75262306a36Sopenharmony_ci * Set up a DFS referral from a given cache entry.
75362306a36Sopenharmony_ci *
75462306a36Sopenharmony_ci * Must be called with htable_rw_lock held.
75562306a36Sopenharmony_ci */
75662306a36Sopenharmony_cistatic int setup_referral(const char *path, struct cache_entry *ce,
75762306a36Sopenharmony_ci			  struct dfs_info3_param *ref, const char *target)
75862306a36Sopenharmony_ci{
75962306a36Sopenharmony_ci	int rc;
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: set up new ref\n", __func__);
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	memset(ref, 0, sizeof(*ref));
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci	ref->path_name = kstrdup(path, GFP_ATOMIC);
76662306a36Sopenharmony_ci	if (!ref->path_name)
76762306a36Sopenharmony_ci		return -ENOMEM;
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	ref->node_name = kstrdup(target, GFP_ATOMIC);
77062306a36Sopenharmony_ci	if (!ref->node_name) {
77162306a36Sopenharmony_ci		rc = -ENOMEM;
77262306a36Sopenharmony_ci		goto err_free_path;
77362306a36Sopenharmony_ci	}
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	ref->path_consumed = ce->path_consumed;
77662306a36Sopenharmony_ci	ref->ttl = ce->ttl;
77762306a36Sopenharmony_ci	ref->server_type = ce->srvtype;
77862306a36Sopenharmony_ci	ref->ref_flag = ce->ref_flags;
77962306a36Sopenharmony_ci	ref->flags = ce->hdr_flags;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	return 0;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_cierr_free_path:
78462306a36Sopenharmony_ci	kfree(ref->path_name);
78562306a36Sopenharmony_ci	ref->path_name = NULL;
78662306a36Sopenharmony_ci	return rc;
78762306a36Sopenharmony_ci}
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci/* Return target list of a DFS cache entry */
79062306a36Sopenharmony_cistatic int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)
79162306a36Sopenharmony_ci{
79262306a36Sopenharmony_ci	int rc;
79362306a36Sopenharmony_ci	struct list_head *head = &tl->tl_list;
79462306a36Sopenharmony_ci	struct cache_dfs_tgt *t;
79562306a36Sopenharmony_ci	struct dfs_cache_tgt_iterator *it, *nit;
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	memset(tl, 0, sizeof(*tl));
79862306a36Sopenharmony_ci	INIT_LIST_HEAD(head);
79962306a36Sopenharmony_ci
80062306a36Sopenharmony_ci	list_for_each_entry(t, &ce->tlist, list) {
80162306a36Sopenharmony_ci		it = kzalloc(sizeof(*it), GFP_ATOMIC);
80262306a36Sopenharmony_ci		if (!it) {
80362306a36Sopenharmony_ci			rc = -ENOMEM;
80462306a36Sopenharmony_ci			goto err_free_it;
80562306a36Sopenharmony_ci		}
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci		it->it_name = kstrdup(t->name, GFP_ATOMIC);
80862306a36Sopenharmony_ci		if (!it->it_name) {
80962306a36Sopenharmony_ci			kfree(it);
81062306a36Sopenharmony_ci			rc = -ENOMEM;
81162306a36Sopenharmony_ci			goto err_free_it;
81262306a36Sopenharmony_ci		}
81362306a36Sopenharmony_ci		it->it_path_consumed = t->path_consumed;
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci		if (READ_ONCE(ce->tgthint) == t)
81662306a36Sopenharmony_ci			list_add(&it->it_list, head);
81762306a36Sopenharmony_ci		else
81862306a36Sopenharmony_ci			list_add_tail(&it->it_list, head);
81962306a36Sopenharmony_ci	}
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_ci	tl->tl_numtgts = ce->numtgts;
82262306a36Sopenharmony_ci
82362306a36Sopenharmony_ci	return 0;
82462306a36Sopenharmony_ci
82562306a36Sopenharmony_cierr_free_it:
82662306a36Sopenharmony_ci	list_for_each_entry_safe(it, nit, head, it_list) {
82762306a36Sopenharmony_ci		list_del(&it->it_list);
82862306a36Sopenharmony_ci		kfree(it->it_name);
82962306a36Sopenharmony_ci		kfree(it);
83062306a36Sopenharmony_ci	}
83162306a36Sopenharmony_ci	return rc;
83262306a36Sopenharmony_ci}
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci/**
83562306a36Sopenharmony_ci * dfs_cache_find - find a DFS cache entry
83662306a36Sopenharmony_ci *
83762306a36Sopenharmony_ci * If it doesn't find the cache entry, then it will get a DFS referral
83862306a36Sopenharmony_ci * for @path and create a new entry.
83962306a36Sopenharmony_ci *
84062306a36Sopenharmony_ci * In case the cache entry exists but expired, it will get a DFS referral
84162306a36Sopenharmony_ci * for @path and then update the respective cache entry.
84262306a36Sopenharmony_ci *
84362306a36Sopenharmony_ci * These parameters are passed down to the get_dfs_refer() call if it
84462306a36Sopenharmony_ci * needs to be issued:
84562306a36Sopenharmony_ci * @xid: syscall xid
84662306a36Sopenharmony_ci * @ses: smb session to issue the request on
84762306a36Sopenharmony_ci * @cp: codepage
84862306a36Sopenharmony_ci * @remap: path character remapping type
84962306a36Sopenharmony_ci * @path: path to lookup in DFS referral cache.
85062306a36Sopenharmony_ci *
85162306a36Sopenharmony_ci * @ref: when non-NULL, store single DFS referral result in it.
85262306a36Sopenharmony_ci * @tgt_list: when non-NULL, store complete DFS target list in it.
85362306a36Sopenharmony_ci *
85462306a36Sopenharmony_ci * Return zero if the target was found, otherwise non-zero.
85562306a36Sopenharmony_ci */
85662306a36Sopenharmony_ciint dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *cp,
85762306a36Sopenharmony_ci		   int remap, const char *path, struct dfs_info3_param *ref,
85862306a36Sopenharmony_ci		   struct dfs_cache_tgt_list *tgt_list)
85962306a36Sopenharmony_ci{
86062306a36Sopenharmony_ci	int rc;
86162306a36Sopenharmony_ci	const char *npath;
86262306a36Sopenharmony_ci	struct cache_entry *ce;
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	npath = dfs_cache_canonical_path(path, cp, remap);
86562306a36Sopenharmony_ci	if (IS_ERR(npath))
86662306a36Sopenharmony_ci		return PTR_ERR(npath);
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	ce = cache_refresh_path(xid, ses, npath, false);
86962306a36Sopenharmony_ci	if (IS_ERR(ce)) {
87062306a36Sopenharmony_ci		rc = PTR_ERR(ce);
87162306a36Sopenharmony_ci		goto out_free_path;
87262306a36Sopenharmony_ci	}
87362306a36Sopenharmony_ci
87462306a36Sopenharmony_ci	if (ref)
87562306a36Sopenharmony_ci		rc = setup_referral(path, ce, ref, get_tgt_name(ce));
87662306a36Sopenharmony_ci	else
87762306a36Sopenharmony_ci		rc = 0;
87862306a36Sopenharmony_ci	if (!rc && tgt_list)
87962306a36Sopenharmony_ci		rc = get_targets(ce, tgt_list);
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci	up_read(&htable_rw_lock);
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ciout_free_path:
88462306a36Sopenharmony_ci	kfree(npath);
88562306a36Sopenharmony_ci	return rc;
88662306a36Sopenharmony_ci}
88762306a36Sopenharmony_ci
88862306a36Sopenharmony_ci/**
88962306a36Sopenharmony_ci * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to
89062306a36Sopenharmony_ci * the currently connected server.
89162306a36Sopenharmony_ci *
89262306a36Sopenharmony_ci * NOTE: This function will neither update a cache entry in case it was
89362306a36Sopenharmony_ci * expired, nor create a new cache entry if @path hasn't been found. It heavily
89462306a36Sopenharmony_ci * relies on an existing cache entry.
89562306a36Sopenharmony_ci *
89662306a36Sopenharmony_ci * @path: canonical DFS path to lookup in the DFS referral cache.
89762306a36Sopenharmony_ci * @ref: when non-NULL, store single DFS referral result in it.
89862306a36Sopenharmony_ci * @tgt_list: when non-NULL, store complete DFS target list in it.
89962306a36Sopenharmony_ci *
90062306a36Sopenharmony_ci * Return 0 if successful.
90162306a36Sopenharmony_ci * Return -ENOENT if the entry was not found.
90262306a36Sopenharmony_ci * Return non-zero for other errors.
90362306a36Sopenharmony_ci */
90462306a36Sopenharmony_ciint dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
90562306a36Sopenharmony_ci			 struct dfs_cache_tgt_list *tgt_list)
90662306a36Sopenharmony_ci{
90762306a36Sopenharmony_ci	int rc;
90862306a36Sopenharmony_ci	struct cache_entry *ce;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
91162306a36Sopenharmony_ci
91262306a36Sopenharmony_ci	down_read(&htable_rw_lock);
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
91562306a36Sopenharmony_ci	if (IS_ERR(ce)) {
91662306a36Sopenharmony_ci		rc = PTR_ERR(ce);
91762306a36Sopenharmony_ci		goto out_unlock;
91862306a36Sopenharmony_ci	}
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	if (ref)
92162306a36Sopenharmony_ci		rc = setup_referral(path, ce, ref, get_tgt_name(ce));
92262306a36Sopenharmony_ci	else
92362306a36Sopenharmony_ci		rc = 0;
92462306a36Sopenharmony_ci	if (!rc && tgt_list)
92562306a36Sopenharmony_ci		rc = get_targets(ce, tgt_list);
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ciout_unlock:
92862306a36Sopenharmony_ci	up_read(&htable_rw_lock);
92962306a36Sopenharmony_ci	return rc;
93062306a36Sopenharmony_ci}
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci/**
93362306a36Sopenharmony_ci * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry
93462306a36Sopenharmony_ci * without sending any requests to the currently connected server.
93562306a36Sopenharmony_ci *
93662306a36Sopenharmony_ci * NOTE: This function will neither update a cache entry in case it was
93762306a36Sopenharmony_ci * expired, nor create a new cache entry if @path hasn't been found. It heavily
93862306a36Sopenharmony_ci * relies on an existing cache entry.
93962306a36Sopenharmony_ci *
94062306a36Sopenharmony_ci * @path: canonical DFS path to lookup in DFS referral cache.
94162306a36Sopenharmony_ci * @it: target iterator which contains the target hint to update the cache
94262306a36Sopenharmony_ci * entry with.
94362306a36Sopenharmony_ci *
94462306a36Sopenharmony_ci * Return zero if the target hint was updated successfully, otherwise non-zero.
94562306a36Sopenharmony_ci */
94662306a36Sopenharmony_civoid dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
94762306a36Sopenharmony_ci{
94862306a36Sopenharmony_ci	struct cache_dfs_tgt *t;
94962306a36Sopenharmony_ci	struct cache_entry *ce;
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	if (!path || !it)
95262306a36Sopenharmony_ci		return;
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	down_read(&htable_rw_lock);
95762306a36Sopenharmony_ci
95862306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
95962306a36Sopenharmony_ci	if (IS_ERR(ce))
96062306a36Sopenharmony_ci		goto out_unlock;
96162306a36Sopenharmony_ci
96262306a36Sopenharmony_ci	t = READ_ONCE(ce->tgthint);
96362306a36Sopenharmony_ci
96462306a36Sopenharmony_ci	if (unlikely(!strcasecmp(it->it_name, t->name)))
96562306a36Sopenharmony_ci		goto out_unlock;
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	list_for_each_entry(t, &ce->tlist, list) {
96862306a36Sopenharmony_ci		if (!strcasecmp(t->name, it->it_name)) {
96962306a36Sopenharmony_ci			WRITE_ONCE(ce->tgthint, t);
97062306a36Sopenharmony_ci			cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
97162306a36Sopenharmony_ci				 it->it_name);
97262306a36Sopenharmony_ci			break;
97362306a36Sopenharmony_ci		}
97462306a36Sopenharmony_ci	}
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_ciout_unlock:
97762306a36Sopenharmony_ci	up_read(&htable_rw_lock);
97862306a36Sopenharmony_ci}
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci/**
98162306a36Sopenharmony_ci * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given
98262306a36Sopenharmony_ci * target iterator (@it).
98362306a36Sopenharmony_ci *
98462306a36Sopenharmony_ci * @path: canonical DFS path to lookup in DFS referral cache.
98562306a36Sopenharmony_ci * @it: DFS target iterator.
98662306a36Sopenharmony_ci * @ref: DFS referral pointer to set up the gathered information.
98762306a36Sopenharmony_ci *
98862306a36Sopenharmony_ci * Return zero if the DFS referral was set up correctly, otherwise non-zero.
98962306a36Sopenharmony_ci */
99062306a36Sopenharmony_ciint dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
99162306a36Sopenharmony_ci			       struct dfs_info3_param *ref)
99262306a36Sopenharmony_ci{
99362306a36Sopenharmony_ci	int rc;
99462306a36Sopenharmony_ci	struct cache_entry *ce;
99562306a36Sopenharmony_ci
99662306a36Sopenharmony_ci	if (!it || !ref)
99762306a36Sopenharmony_ci		return -EINVAL;
99862306a36Sopenharmony_ci
99962306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
100062306a36Sopenharmony_ci
100162306a36Sopenharmony_ci	down_read(&htable_rw_lock);
100262306a36Sopenharmony_ci
100362306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
100462306a36Sopenharmony_ci	if (IS_ERR(ce)) {
100562306a36Sopenharmony_ci		rc = PTR_ERR(ce);
100662306a36Sopenharmony_ci		goto out_unlock;
100762306a36Sopenharmony_ci	}
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name);
101062306a36Sopenharmony_ci
101162306a36Sopenharmony_ci	rc = setup_referral(path, ce, ref, it->it_name);
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ciout_unlock:
101462306a36Sopenharmony_ci	up_read(&htable_rw_lock);
101562306a36Sopenharmony_ci	return rc;
101662306a36Sopenharmony_ci}
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci/* Extract share from DFS target and return a pointer to prefix path or NULL */
101962306a36Sopenharmony_cistatic const char *parse_target_share(const char *target, char **share)
102062306a36Sopenharmony_ci{
102162306a36Sopenharmony_ci	const char *s, *seps = "/\\";
102262306a36Sopenharmony_ci	size_t len;
102362306a36Sopenharmony_ci
102462306a36Sopenharmony_ci	s = strpbrk(target + 1, seps);
102562306a36Sopenharmony_ci	if (!s)
102662306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ci	len = strcspn(s + 1, seps);
102962306a36Sopenharmony_ci	if (!len)
103062306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
103162306a36Sopenharmony_ci	s += len;
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci	len = s - target + 1;
103462306a36Sopenharmony_ci	*share = kstrndup(target, len, GFP_KERNEL);
103562306a36Sopenharmony_ci	if (!*share)
103662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
103762306a36Sopenharmony_ci
103862306a36Sopenharmony_ci	s = target + len;
103962306a36Sopenharmony_ci	return s + strspn(s, seps);
104062306a36Sopenharmony_ci}
104162306a36Sopenharmony_ci
104262306a36Sopenharmony_ci/**
104362306a36Sopenharmony_ci * dfs_cache_get_tgt_share - parse a DFS target
104462306a36Sopenharmony_ci *
104562306a36Sopenharmony_ci * @path: DFS full path
104662306a36Sopenharmony_ci * @it: DFS target iterator.
104762306a36Sopenharmony_ci * @share: tree name.
104862306a36Sopenharmony_ci * @prefix: prefix path.
104962306a36Sopenharmony_ci *
105062306a36Sopenharmony_ci * Return zero if target was parsed correctly, otherwise non-zero.
105162306a36Sopenharmony_ci */
105262306a36Sopenharmony_ciint dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,
105362306a36Sopenharmony_ci			    char **prefix)
105462306a36Sopenharmony_ci{
105562306a36Sopenharmony_ci	char sep;
105662306a36Sopenharmony_ci	char *target_share;
105762306a36Sopenharmony_ci	char *ppath = NULL;
105862306a36Sopenharmony_ci	const char *target_ppath, *dfsref_ppath;
105962306a36Sopenharmony_ci	size_t target_pplen, dfsref_pplen;
106062306a36Sopenharmony_ci	size_t len, c;
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	if (!it || !path || !share || !prefix || strlen(path) < it->it_path_consumed)
106362306a36Sopenharmony_ci		return -EINVAL;
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_ci	sep = it->it_name[0];
106662306a36Sopenharmony_ci	if (sep != '\\' && sep != '/')
106762306a36Sopenharmony_ci		return -EINVAL;
106862306a36Sopenharmony_ci
106962306a36Sopenharmony_ci	target_ppath = parse_target_share(it->it_name, &target_share);
107062306a36Sopenharmony_ci	if (IS_ERR(target_ppath))
107162306a36Sopenharmony_ci		return PTR_ERR(target_ppath);
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	/* point to prefix in DFS referral path */
107462306a36Sopenharmony_ci	dfsref_ppath = path + it->it_path_consumed;
107562306a36Sopenharmony_ci	dfsref_ppath += strspn(dfsref_ppath, "/\\");
107662306a36Sopenharmony_ci
107762306a36Sopenharmony_ci	target_pplen = strlen(target_ppath);
107862306a36Sopenharmony_ci	dfsref_pplen = strlen(dfsref_ppath);
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_ci	/* merge prefix paths from DFS referral path and target node */
108162306a36Sopenharmony_ci	if (target_pplen || dfsref_pplen) {
108262306a36Sopenharmony_ci		len = target_pplen + dfsref_pplen + 2;
108362306a36Sopenharmony_ci		ppath = kzalloc(len, GFP_KERNEL);
108462306a36Sopenharmony_ci		if (!ppath) {
108562306a36Sopenharmony_ci			kfree(target_share);
108662306a36Sopenharmony_ci			return -ENOMEM;
108762306a36Sopenharmony_ci		}
108862306a36Sopenharmony_ci		c = strscpy(ppath, target_ppath, len);
108962306a36Sopenharmony_ci		if (c && dfsref_pplen)
109062306a36Sopenharmony_ci			ppath[c] = sep;
109162306a36Sopenharmony_ci		strlcat(ppath, dfsref_ppath, len);
109262306a36Sopenharmony_ci	}
109362306a36Sopenharmony_ci	*share = target_share;
109462306a36Sopenharmony_ci	*prefix = ppath;
109562306a36Sopenharmony_ci	return 0;
109662306a36Sopenharmony_ci}
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_cistatic bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
109962306a36Sopenharmony_ci{
110062306a36Sopenharmony_ci	char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
110162306a36Sopenharmony_ci	const char *host;
110262306a36Sopenharmony_ci	size_t hostlen;
110362306a36Sopenharmony_ci	struct sockaddr_storage ss;
110462306a36Sopenharmony_ci	bool match;
110562306a36Sopenharmony_ci	int rc;
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci	if (strcasecmp(s1, s2))
110862306a36Sopenharmony_ci		return false;
110962306a36Sopenharmony_ci
111062306a36Sopenharmony_ci	/*
111162306a36Sopenharmony_ci	 * Resolve share's hostname and check if server address matches.  Otherwise just ignore it
111262306a36Sopenharmony_ci	 * as we could not have upcall to resolve hostname or failed to convert ip address.
111362306a36Sopenharmony_ci	 */
111462306a36Sopenharmony_ci	extract_unc_hostname(s1, &host, &hostlen);
111562306a36Sopenharmony_ci	scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);
111662306a36Sopenharmony_ci
111762306a36Sopenharmony_ci	rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
111862306a36Sopenharmony_ci	if (rc < 0) {
111962306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
112062306a36Sopenharmony_ci			 __func__, (int)hostlen, host);
112162306a36Sopenharmony_ci		return true;
112262306a36Sopenharmony_ci	}
112362306a36Sopenharmony_ci
112462306a36Sopenharmony_ci	cifs_server_lock(server);
112562306a36Sopenharmony_ci	match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
112662306a36Sopenharmony_ci	cifs_server_unlock(server);
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_ci	return match;
112962306a36Sopenharmony_ci}
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_ci/*
113262306a36Sopenharmony_ci * Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new
113362306a36Sopenharmony_ci * target shares in @refs.
113462306a36Sopenharmony_ci */
113562306a36Sopenharmony_cistatic void mark_for_reconnect_if_needed(struct TCP_Server_Info *server,
113662306a36Sopenharmony_ci					 const char *path,
113762306a36Sopenharmony_ci					 struct dfs_cache_tgt_list *old_tl,
113862306a36Sopenharmony_ci					 struct dfs_cache_tgt_list *new_tl)
113962306a36Sopenharmony_ci{
114062306a36Sopenharmony_ci	struct dfs_cache_tgt_iterator *oit, *nit;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	for (oit = dfs_cache_get_tgt_iterator(old_tl); oit;
114362306a36Sopenharmony_ci	     oit = dfs_cache_get_next_tgt(old_tl, oit)) {
114462306a36Sopenharmony_ci		for (nit = dfs_cache_get_tgt_iterator(new_tl); nit;
114562306a36Sopenharmony_ci		     nit = dfs_cache_get_next_tgt(new_tl, nit)) {
114662306a36Sopenharmony_ci			if (target_share_equal(server,
114762306a36Sopenharmony_ci					       dfs_cache_get_tgt_name(oit),
114862306a36Sopenharmony_ci					       dfs_cache_get_tgt_name(nit))) {
114962306a36Sopenharmony_ci				dfs_cache_noreq_update_tgthint(path, nit);
115062306a36Sopenharmony_ci				return;
115162306a36Sopenharmony_ci			}
115262306a36Sopenharmony_ci		}
115362306a36Sopenharmony_ci	}
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_ci	cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__);
115662306a36Sopenharmony_ci	cifs_signal_cifsd_for_reconnect(server, true);
115762306a36Sopenharmony_ci}
115862306a36Sopenharmony_ci
115962306a36Sopenharmony_cistatic bool is_ses_good(struct cifs_ses *ses)
116062306a36Sopenharmony_ci{
116162306a36Sopenharmony_ci	struct TCP_Server_Info *server = ses->server;
116262306a36Sopenharmony_ci	struct cifs_tcon *tcon = ses->tcon_ipc;
116362306a36Sopenharmony_ci	bool ret;
116462306a36Sopenharmony_ci
116562306a36Sopenharmony_ci	spin_lock(&ses->ses_lock);
116662306a36Sopenharmony_ci	spin_lock(&ses->chan_lock);
116762306a36Sopenharmony_ci	ret = !cifs_chan_needs_reconnect(ses, server) &&
116862306a36Sopenharmony_ci		ses->ses_status == SES_GOOD &&
116962306a36Sopenharmony_ci		!tcon->need_reconnect;
117062306a36Sopenharmony_ci	spin_unlock(&ses->chan_lock);
117162306a36Sopenharmony_ci	spin_unlock(&ses->ses_lock);
117262306a36Sopenharmony_ci	return ret;
117362306a36Sopenharmony_ci}
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci/* Refresh dfs referral of tcon and mark it for reconnect if needed */
117662306a36Sopenharmony_cistatic int __refresh_tcon(const char *path, struct cifs_ses *ses, bool force_refresh)
117762306a36Sopenharmony_ci{
117862306a36Sopenharmony_ci	struct TCP_Server_Info *server = ses->server;
117962306a36Sopenharmony_ci	DFS_CACHE_TGT_LIST(old_tl);
118062306a36Sopenharmony_ci	DFS_CACHE_TGT_LIST(new_tl);
118162306a36Sopenharmony_ci	bool needs_refresh = false;
118262306a36Sopenharmony_ci	struct cache_entry *ce;
118362306a36Sopenharmony_ci	unsigned int xid;
118462306a36Sopenharmony_ci	int rc = 0;
118562306a36Sopenharmony_ci
118662306a36Sopenharmony_ci	xid = get_xid();
118762306a36Sopenharmony_ci
118862306a36Sopenharmony_ci	down_read(&htable_rw_lock);
118962306a36Sopenharmony_ci	ce = lookup_cache_entry(path);
119062306a36Sopenharmony_ci	needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
119162306a36Sopenharmony_ci	if (!IS_ERR(ce)) {
119262306a36Sopenharmony_ci		rc = get_targets(ce, &old_tl);
119362306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: get_targets: %d\n", __func__, rc);
119462306a36Sopenharmony_ci	}
119562306a36Sopenharmony_ci	up_read(&htable_rw_lock);
119662306a36Sopenharmony_ci
119762306a36Sopenharmony_ci	if (!needs_refresh) {
119862306a36Sopenharmony_ci		rc = 0;
119962306a36Sopenharmony_ci		goto out;
120062306a36Sopenharmony_ci	}
120162306a36Sopenharmony_ci
120262306a36Sopenharmony_ci	ses = CIFS_DFS_ROOT_SES(ses);
120362306a36Sopenharmony_ci	if (!is_ses_good(ses)) {
120462306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n",
120562306a36Sopenharmony_ci			 __func__);
120662306a36Sopenharmony_ci		goto out;
120762306a36Sopenharmony_ci	}
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	ce = cache_refresh_path(xid, ses, path, true);
121062306a36Sopenharmony_ci	if (!IS_ERR(ce)) {
121162306a36Sopenharmony_ci		rc = get_targets(ce, &new_tl);
121262306a36Sopenharmony_ci		up_read(&htable_rw_lock);
121362306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: get_targets: %d\n", __func__, rc);
121462306a36Sopenharmony_ci		mark_for_reconnect_if_needed(server, path, &old_tl, &new_tl);
121562306a36Sopenharmony_ci	}
121662306a36Sopenharmony_ci
121762306a36Sopenharmony_ciout:
121862306a36Sopenharmony_ci	free_xid(xid);
121962306a36Sopenharmony_ci	dfs_cache_free_tgts(&old_tl);
122062306a36Sopenharmony_ci	dfs_cache_free_tgts(&new_tl);
122162306a36Sopenharmony_ci	return rc;
122262306a36Sopenharmony_ci}
122362306a36Sopenharmony_ci
122462306a36Sopenharmony_cistatic int refresh_tcon(struct cifs_tcon *tcon, bool force_refresh)
122562306a36Sopenharmony_ci{
122662306a36Sopenharmony_ci	struct TCP_Server_Info *server = tcon->ses->server;
122762306a36Sopenharmony_ci	struct cifs_ses *ses = tcon->ses;
122862306a36Sopenharmony_ci
122962306a36Sopenharmony_ci	mutex_lock(&server->refpath_lock);
123062306a36Sopenharmony_ci	if (server->leaf_fullpath)
123162306a36Sopenharmony_ci		__refresh_tcon(server->leaf_fullpath + 1, ses, force_refresh);
123262306a36Sopenharmony_ci	mutex_unlock(&server->refpath_lock);
123362306a36Sopenharmony_ci	return 0;
123462306a36Sopenharmony_ci}
123562306a36Sopenharmony_ci
123662306a36Sopenharmony_ci/**
123762306a36Sopenharmony_ci * dfs_cache_remount_fs - remount a DFS share
123862306a36Sopenharmony_ci *
123962306a36Sopenharmony_ci * Reconfigure dfs mount by forcing a new DFS referral and if the currently cached targets do not
124062306a36Sopenharmony_ci * match any of the new targets, mark it for reconnect.
124162306a36Sopenharmony_ci *
124262306a36Sopenharmony_ci * @cifs_sb: cifs superblock.
124362306a36Sopenharmony_ci *
124462306a36Sopenharmony_ci * Return zero if remounted, otherwise non-zero.
124562306a36Sopenharmony_ci */
124662306a36Sopenharmony_ciint dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
124762306a36Sopenharmony_ci{
124862306a36Sopenharmony_ci	struct cifs_tcon *tcon;
124962306a36Sopenharmony_ci
125062306a36Sopenharmony_ci	if (!cifs_sb || !cifs_sb->master_tlink)
125162306a36Sopenharmony_ci		return -EINVAL;
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	tcon = cifs_sb_master_tcon(cifs_sb);
125462306a36Sopenharmony_ci
125562306a36Sopenharmony_ci	spin_lock(&tcon->tc_lock);
125662306a36Sopenharmony_ci	if (!tcon->origin_fullpath) {
125762306a36Sopenharmony_ci		spin_unlock(&tcon->tc_lock);
125862306a36Sopenharmony_ci		cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
125962306a36Sopenharmony_ci		return 0;
126062306a36Sopenharmony_ci	}
126162306a36Sopenharmony_ci	spin_unlock(&tcon->tc_lock);
126262306a36Sopenharmony_ci
126362306a36Sopenharmony_ci	/*
126462306a36Sopenharmony_ci	 * After reconnecting to a different server, unique ids won't match anymore, so we disable
126562306a36Sopenharmony_ci	 * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
126662306a36Sopenharmony_ci	 */
126762306a36Sopenharmony_ci	cifs_autodisable_serverino(cifs_sb);
126862306a36Sopenharmony_ci	/*
126962306a36Sopenharmony_ci	 * Force the use of prefix path to support failover on DFS paths that resolve to targets
127062306a36Sopenharmony_ci	 * that have different prefix paths.
127162306a36Sopenharmony_ci	 */
127262306a36Sopenharmony_ci	cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
127362306a36Sopenharmony_ci
127462306a36Sopenharmony_ci	return refresh_tcon(tcon, true);
127562306a36Sopenharmony_ci}
127662306a36Sopenharmony_ci
127762306a36Sopenharmony_ci/* Refresh all DFS referrals related to DFS tcon */
127862306a36Sopenharmony_civoid dfs_cache_refresh(struct work_struct *work)
127962306a36Sopenharmony_ci{
128062306a36Sopenharmony_ci	struct TCP_Server_Info *server;
128162306a36Sopenharmony_ci	struct dfs_root_ses *rses;
128262306a36Sopenharmony_ci	struct cifs_tcon *tcon;
128362306a36Sopenharmony_ci	struct cifs_ses *ses;
128462306a36Sopenharmony_ci
128562306a36Sopenharmony_ci	tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work);
128662306a36Sopenharmony_ci	ses = tcon->ses;
128762306a36Sopenharmony_ci	server = ses->server;
128862306a36Sopenharmony_ci
128962306a36Sopenharmony_ci	mutex_lock(&server->refpath_lock);
129062306a36Sopenharmony_ci	if (server->leaf_fullpath)
129162306a36Sopenharmony_ci		__refresh_tcon(server->leaf_fullpath + 1, ses, false);
129262306a36Sopenharmony_ci	mutex_unlock(&server->refpath_lock);
129362306a36Sopenharmony_ci
129462306a36Sopenharmony_ci	list_for_each_entry(rses, &tcon->dfs_ses_list, list) {
129562306a36Sopenharmony_ci		ses = rses->ses;
129662306a36Sopenharmony_ci		server = ses->server;
129762306a36Sopenharmony_ci		mutex_lock(&server->refpath_lock);
129862306a36Sopenharmony_ci		if (server->leaf_fullpath)
129962306a36Sopenharmony_ci			__refresh_tcon(server->leaf_fullpath + 1, ses, false);
130062306a36Sopenharmony_ci		mutex_unlock(&server->refpath_lock);
130162306a36Sopenharmony_ci	}
130262306a36Sopenharmony_ci
130362306a36Sopenharmony_ci	queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work,
130462306a36Sopenharmony_ci			   atomic_read(&dfs_cache_ttl) * HZ);
130562306a36Sopenharmony_ci}
1306