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