Message ID | 20181115142103.24617-9-aaptel@suse.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | DFS failover | expand |
merged into cifs-2.6.git for-next with minor changes. Let me know if objections to the fix of the return code (ENOSYS is for syscalls only) $ diff ~/save-patch1/0019-cifs-Add-DFS-cache-routines.patch 0019-cifs-Add-DFS-cache-routines.patch 834c834 < + ce = ERR_PTR(-ENOSYS); --- > + ce = ERR_PTR(-EOPNOTSUPP); 1481c1481 < + rc = -ENOSYS; --- > + rc = -EOPNOTSUPP; On Thu, Nov 15, 2018 at 8:22 AM Aurelien Aptel <aaptel@suse.com> wrote: > > From: Paulo Alcantara <paulo@paulo.ac> > > * Add new dfs_cache.[ch] files > > * Add new /proc/fs/cifs/dfscache file > - dump current cache when read > - clear current cache when writing "0" to it > > * Add delayed_work to periodically refresh cache entries > > The new interface will be used for caching DFS referrals, as well as > supporting client target failover. > > The DFS cache is a hashtable that maps UNC paths to cache entries. > > A cache entry contains: > - the UNC path it is mapped on > - how much the the UNC path the entry consumes > - flags > - a Time-To-Live after which the entry expires > - a list of possible targets (linked lists of UNC paths) > - a "hint target" pointing the last known working target or the first > target if none were tried. This hint lets cifs.ko remember and try > working targets first. > > * Looking for an entry in the cache is done with dfs_cache_find() > - if no valid entries are found, a DFS query is made, stored in the > cache and returned > - the full target list can be copied and returned to avoid race > conditions and looped on with the help with the > dfs_cache_tgt_iterator > > * Updating the target hint to the next target is done with > dfs_cache_update_tgthint() > > These functions have a dfs_cache_noreq_XXX() version that doesn't > fetches referrals if no entries are found. These versions don't > require the tcp/ses/tcon/cifs_sb parameters as a result. > > Expired entries cannot be used and since they have a pretty short TTL > [1] in order for them to be useful for failover the DFS cache adds a > delayed work called periodically to keep them fresh. > > Since we might not have available connections to issue the referral > request when refreshing we need to store volume_info structs with > credentials and other needed info to be able to connect to the right > server. > > 1: Windows defaults: 5mn for domain-based referrals, 30mn for regular > links > > Signed-off-by: Paulo Alcantara <palcantara@suse.de> > Signed-off-by: Aurelien Aptel <aaptel@suse.com> > --- > fs/cifs/Makefile | 2 +- > fs/cifs/cifs_debug.c | 12 + > fs/cifs/cifsglob.h | 5 + > fs/cifs/cifsproto.h | 3 + > fs/cifs/connect.c | 2 +- > fs/cifs/dfs_cache.c | 1379 ++++++++++++++++++++++++++++++++++++++++++++++++++ > fs/cifs/dfs_cache.h | 97 ++++ > 7 files changed, 1498 insertions(+), 2 deletions(-) > create mode 100644 fs/cifs/dfs_cache.c > create mode 100644 fs/cifs/dfs_cache.h > > diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile > index 85817991ee68..51af69a1a328 100644 > --- a/fs/cifs/Makefile > +++ b/fs/cifs/Makefile > @@ -17,7 +17,7 @@ cifs-$(CONFIG_CIFS_ACL) += cifsacl.o > > cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o > > -cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o > +cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o dfs_cache.o > > cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o > > diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c > index ba178b09de0b..593fb422d0f3 100644 > --- a/fs/cifs/cifs_debug.c > +++ b/fs/cifs/cifs_debug.c > @@ -30,6 +30,9 @@ > #include "cifsproto.h" > #include "cifs_debug.h" > #include "cifsfs.h" > +#ifdef CONFIG_CIFS_DFS_UPCALL > +#include "dfs_cache.h" > +#endif > #ifdef CONFIG_CIFS_SMB_DIRECT > #include "smbdirect.h" > #endif > @@ -629,6 +632,11 @@ cifs_proc_init(void) > &cifs_security_flags_proc_fops); > proc_create("LookupCacheEnabled", 0644, proc_fs_cifs, > &cifs_lookup_cache_proc_fops); > + > +#ifdef CONFIG_CIFS_DFS_UPCALL > + proc_create("dfscache", 0644, proc_fs_cifs, &dfscache_proc_fops); > +#endif > + > #ifdef CONFIG_CIFS_SMB_DIRECT > proc_create("rdma_readwrite_threshold", 0644, proc_fs_cifs, > &cifs_rdma_readwrite_threshold_proc_fops); > @@ -663,6 +671,10 @@ cifs_proc_clean(void) > remove_proc_entry("SecurityFlags", proc_fs_cifs); > remove_proc_entry("LinuxExtensionsEnabled", proc_fs_cifs); > remove_proc_entry("LookupCacheEnabled", proc_fs_cifs); > + > +#ifdef CONFIG_CIFS_DFS_UPCALL > + remove_proc_entry("dfscache", proc_fs_cifs); > +#endif > #ifdef CONFIG_CIFS_SMB_DIRECT > remove_proc_entry("rdma_readwrite_threshold", proc_fs_cifs); > remove_proc_entry("smbd_max_frmr_depth", proc_fs_cifs); > diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h > index 9534f46e6ad2..bd183ec17066 100644 > --- a/fs/cifs/cifsglob.h > +++ b/fs/cifs/cifsglob.h > @@ -1014,6 +1014,11 @@ struct cifs_tcon { > struct list_head pending_opens; /* list of incomplete opens */ > struct cached_fid crfid; /* Cached root fid */ > /* BB add field for back pointer to sb struct(s)? */ > +#ifdef CONFIG_CIFS_DFS_UPCALL > + char *dfs_path; > + int remap:2; > + struct list_head ulist; /* cache update list */ > +#endif > }; > > /* > diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h > index f4dd2a3795dd..efa5e36b3762 100644 > --- a/fs/cifs/cifsproto.h > +++ b/fs/cifs/cifsproto.h > @@ -527,6 +527,9 @@ extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, > extern void > cifs_cleanup_volume_info_contents(struct smb_vol *volume_info); > > +extern struct TCP_Server_Info * > +cifs_find_tcp_session(struct smb_vol *vol); > + > void cifs_readdata_release(struct kref *refcount); > int cifs_async_readv(struct cifs_readdata *rdata); > int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid); > diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c > index 86649230b4f2..b4fed69add86 100644 > --- a/fs/cifs/connect.c > +++ b/fs/cifs/connect.c > @@ -2294,7 +2294,7 @@ static int match_server(struct TCP_Server_Info *server, struct smb_vol *vol) > return 1; > } > > -static struct TCP_Server_Info * > +struct TCP_Server_Info * > cifs_find_tcp_session(struct smb_vol *vol) > { > struct TCP_Server_Info *server; > diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c > new file mode 100644 > index 000000000000..18da279e9354 > --- /dev/null > +++ b/fs/cifs/dfs_cache.c > @@ -0,0 +1,1379 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * DFS referral cache routines > + * > + * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> > + */ > + > +#include <linux/rcupdate.h> > +#include <linux/rculist.h> > +#include <linux/jhash.h> > +#include <linux/ktime.h> > +#include <linux/slab.h> > +#include <linux/nls.h> > +#include <linux/workqueue.h> > +#include "cifsglob.h" > +#include "smb2pdu.h" > +#include "smb2proto.h" > +#include "cifsproto.h" > +#include "cifs_debug.h" > +#include "cifs_unicode.h" > +#include "smb2glob.h" > + > +#include "dfs_cache.h" > + > +#define DFS_CACHE_HTABLE_SIZE 32 > +#define DFS_CACHE_MAX_ENTRIES 64 > + > +#define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \ > + DFSREF_STORAGE_SERVER)) > + > +struct dfs_cache_tgt { > + char *t_name; > + struct list_head t_list; > +}; > + > +struct dfs_cache_entry { > + struct hlist_node ce_hlist; > + const char *ce_path; > + int ce_ttl; > + int ce_srvtype; > + int ce_flags; > + struct timespec64 ce_etime; > + int ce_path_consumed; > + int ce_numtgts; > + struct list_head ce_tlist; > + struct dfs_cache_tgt *ce_tgthint; > + struct rcu_head ce_rcu; > +}; > + > +static struct kmem_cache *dfs_cache_slab __read_mostly; > + > +struct dfs_cache_vol_info { > + char *vi_fullpath; > + struct smb_vol vi_vol; > + struct list_head vi_list; > +}; > + > +struct dfs_cache { > + struct mutex dc_lock; > + struct nls_table *dc_nlsc; > + struct list_head dc_vol_list; > + int dc_ttl; > + struct delayed_work dc_refresh; > +}; > + > +static struct dfs_cache dfs_cache; > + > +/* > + * Number of entries in the cache > + */ > +static size_t dfs_cache_count; > + > +static DEFINE_MUTEX(dfs_cache_list_lock); > +static struct hlist_head dfs_cache_htable[DFS_CACHE_HTABLE_SIZE]; > + > +static void refresh_cache_worker(struct work_struct *work); > + > +static inline bool is_path_valid(const char *path) > +{ > + return path && (strchr(path + 1, '\\') || strchr(path + 1, '/')); > +} > + > +static inline int get_normalized_path(const char *path, char **npath) > +{ > + if (*path == '\\') { > + *npath = (char *)path; > + } else { > + *npath = kstrndup(path, strlen(path), GFP_KERNEL); > + if (!*npath) > + return -ENOMEM; > + convert_delimiter(*npath, '\\'); > + } > + return 0; > +} > + > +static inline void free_normalized_path(const char *path, char *npath) > +{ > + if (path != npath) > + kfree(npath); > +} > + > +static inline bool cache_entry_expired(const struct dfs_cache_entry *ce) > +{ > + struct timespec64 ts; > + > + ts = current_kernel_time64(); > + return timespec64_compare(&ts, &ce->ce_etime) >= 0; > +} > + > +static inline void free_tgts(struct dfs_cache_entry *ce) > +{ > + struct dfs_cache_tgt *t, *n; > + > + list_for_each_entry_safe(t, n, &ce->ce_tlist, t_list) { > + list_del(&t->t_list); > + kfree(t->t_name); > + kfree(t); > + } > +} > + > +static void free_cache_entry(struct rcu_head *rcu) > +{ > + struct dfs_cache_entry *ce = container_of(rcu, struct dfs_cache_entry, > + ce_rcu); > + kmem_cache_free(dfs_cache_slab, ce); > +} > + > +static inline void flush_cache_ent(struct dfs_cache_entry *ce) > +{ > + if (hlist_unhashed(&ce->ce_hlist)) > + return; > + > + hlist_del_init_rcu(&ce->ce_hlist); > + kfree(ce->ce_path); > + free_tgts(ce); > + dfs_cache_count--; > + call_rcu(&ce->ce_rcu, free_cache_entry); > +} > + > +static void flush_cache_ents(void) > +{ > + int i; > + > + rcu_read_lock(); > + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) { > + struct hlist_head *l = &dfs_cache_htable[i]; > + struct dfs_cache_entry *ce; > + > + hlist_for_each_entry_rcu(ce, l, ce_hlist) > + flush_cache_ent(ce); > + } > + rcu_read_unlock(); > +} > + > +/* > + * dfs cache /proc file > + */ > +static int dfscache_proc_show(struct seq_file *m, void *v) > +{ > + int bucket; > + struct dfs_cache_entry *ce; > + struct dfs_cache_tgt *t; > + > + seq_puts(m, "DFS cache\n---------\n"); > + > + mutex_lock(&dfs_cache_list_lock); > + > + rcu_read_lock(); > + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { > + seq_printf(m, > + "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," > + "interlink=%s,path_consumed=%d,expired=%s\n", > + ce->ce_path, > + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", > + ce->ce_ttl, ce->ce_etime.tv_nsec, > + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", > + ce->ce_path_consumed, > + cache_entry_expired(ce) ? "yes" : "no"); > + > + list_for_each_entry(t, &ce->ce_tlist, t_list) { > + seq_printf(m, " %s%s\n", > + t->t_name, > + ce->ce_tgthint == t ? " (target hint)" : ""); > + } > + > + } > + rcu_read_unlock(); > + > + mutex_unlock(&dfs_cache_list_lock); > + return 0; > +} > + > +static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer, > + size_t count, loff_t *ppos) > +{ > + char c; > + int rc; > + > + rc = get_user(c, buffer); > + if (rc) > + return rc; > + > + if (c != '0') > + return -EINVAL; > + > + cifs_dbg(FYI, "clearing dfs cache"); > + mutex_lock(&dfs_cache_list_lock); > + flush_cache_ents(); > + mutex_unlock(&dfs_cache_list_lock); > + > + return count; > +} > + > +static int dfscache_proc_open(struct inode *inode, struct file *file) > +{ > + return single_open(file, dfscache_proc_show, NULL); > +} > + > +const struct file_operations dfscache_proc_fops = { > + .open = dfscache_proc_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > + .write = dfscache_proc_write, > +}; > + > +#ifdef CONFIG_CIFS_DEBUG2 > +static inline void dump_tgts(const struct dfs_cache_entry *ce) > +{ > + struct dfs_cache_tgt *t; > + > + cifs_dbg(FYI, "target list:\n"); > + list_for_each_entry(t, &ce->ce_tlist, t_list) { > + cifs_dbg(FYI, " %s%s\n", t->t_name, > + ce->ce_tgthint == t ? " (target hint)" : ""); > + } > +} > + > +static inline void dump_ce(const struct dfs_cache_entry *ce) > +{ > + cifs_dbg(FYI, "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," > + "interlink=%s,path_consumed=%d,expired=%s\n", ce->ce_path, > + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ce_ttl, > + ce->ce_etime.tv_nsec, > + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", > + ce->ce_path_consumed, > + cache_entry_expired(ce) ? "yes" : "no"); > + dump_tgts(ce); > +} > + > +static inline void dump_refs(const struct dfs_info3_param *refs, int numrefs) > +{ > + int i; > + > + cifs_dbg(FYI, "DFS referrals returned by the server:\n"); > + for (i = 0; i < numrefs; i++) { > + const struct dfs_info3_param *ref = &refs[i]; > + > + cifs_dbg(FYI, > + "\n" > + "flags: 0x%x\n" > + "path_consumed: %d\n" > + "server_type: 0x%x\n" > + "ref_flag: 0x%x\n" > + "path_name: %s\n" > + "node_name: %s\n" > + "ttl: %d (%dm)\n", > + ref->flags, ref->path_consumed, ref->server_type, > + ref->ref_flag, ref->path_name, ref->node_name, > + ref->ttl, ref->ttl / 60); > + } > +} > +#else > +#define dump_tgts(e) > +#define dump_ce(e) > +#define dump_refs(r, n) > +#endif > + > +/** > + * dfs_cache_init - Initialize DFS referral cache. > + * > + * Return zero if initialized successfully, otherwise non-zero. > + */ > +int dfs_cache_init(void) > +{ > + int i; > + > + dfs_cache_slab = kmem_cache_create("cifs_dfs_cache", > + sizeof(struct dfs_cache_entry), 0, > + SLAB_HWCACHE_ALIGN, NULL); > + if (!dfs_cache_slab) > + return -ENOMEM; > + > + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) > + INIT_HLIST_HEAD(&dfs_cache_htable[i]); > + > + INIT_LIST_HEAD(&dfs_cache.dc_vol_list); > + mutex_init(&dfs_cache.dc_lock); > + INIT_DELAYED_WORK(&dfs_cache.dc_refresh, refresh_cache_worker); > + dfs_cache.dc_ttl = -1; > + dfs_cache.dc_nlsc = load_nls_default(); > + > + cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); > + return 0; > +} > + > +static inline unsigned int cache_entry_hash(const void *data, int size) > +{ > + unsigned int h; > + > + h = jhash(data, size, 0); > + return h & (DFS_CACHE_HTABLE_SIZE - 1); > +} > + > +/* Check whether second path component of @path is SYSVOL or NETLOGON */ > +static inline bool is_sysvol_or_netlogon(const char *path) > +{ > + const char *s; > + char sep = path[0]; > + > + s = strchr(path + 1, sep) + 1; > + return !strncasecmp(s, "sysvol", strlen("sysvol")) || > + !strncasecmp(s, "netlogon", strlen("netlogon")); > +} > + > +/* Return target hint of a DFS cache entry */ > +static inline char *get_tgt_name(const struct dfs_cache_entry *ce) > +{ > + struct dfs_cache_tgt *t = ce->ce_tgthint; > + > + return t ? t->t_name : ERR_PTR(-ENOENT); > +} > + > +/* Return expire time out of a new entry's TTL */ > +static inline struct timespec64 get_expire_time(int ttl) > +{ > + struct timespec64 ts = { > + .tv_sec = ttl, > + .tv_nsec = 0, > + }; > + > + return timespec64_add(current_kernel_time64(), ts); > +} > + > +/* Allocate a new DFS target */ > +static inline struct dfs_cache_tgt *alloc_tgt(const char *name) > +{ > + struct dfs_cache_tgt *t; > + > + t = kmalloc(sizeof(*t), GFP_KERNEL); > + if (!t) > + return ERR_PTR(-ENOMEM); > + t->t_name = kstrndup(name, strlen(name), GFP_KERNEL); > + if (!t->t_name) { > + kfree(t); > + return ERR_PTR(-ENOMEM); > + } > + INIT_LIST_HEAD(&t->t_list); > + return t; > +} > + > +/* > + * Copy DFS referral information to a cache entry and conditionally update > + * target hint. > + */ > +static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, > + struct dfs_cache_entry *ce, const char *tgthint) > +{ > + int i; > + > + ce->ce_ttl = refs[0].ttl; > + ce->ce_etime = get_expire_time(ce->ce_ttl); > + ce->ce_srvtype = refs[0].server_type; > + ce->ce_flags = refs[0].ref_flag; > + ce->ce_path_consumed = refs[0].path_consumed; > + > + for (i = 0; i < numrefs; i++) { > + struct dfs_cache_tgt *t; > + > + t = alloc_tgt(refs[i].node_name); > + if (IS_ERR(t)) { > + free_tgts(ce); > + return PTR_ERR(t); > + } > + if (tgthint && !strncasecmp(t->t_name, tgthint, > + strlen(tgthint))) { > + list_add(&t->t_list, &ce->ce_tlist); > + tgthint = NULL; > + } else { > + list_add_tail(&t->t_list, &ce->ce_tlist); > + } > + ce->ce_numtgts++; > + } > + > + ce->ce_tgthint = list_first_entry_or_null(&ce->ce_tlist, > + struct dfs_cache_tgt, t_list); > + > + return 0; > +} > + > +/* Allocate a new cache entry */ > +static struct dfs_cache_entry * > +alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, > + int numrefs) > +{ > + struct dfs_cache_entry *ce; > + int rc; > + > + ce = kmem_cache_zalloc(dfs_cache_slab, GFP_KERNEL); > + if (!ce) > + return ERR_PTR(-ENOMEM); > + > + ce->ce_path = kstrdup_const(path, GFP_KERNEL); > + if (!ce->ce_path) { > + kfree(ce); > + return ERR_PTR(-ENOMEM); > + } > + INIT_HLIST_NODE(&ce->ce_hlist); > + INIT_LIST_HEAD(&ce->ce_tlist); > + > + rc = copy_ref_data(refs, numrefs, ce, NULL); > + if (rc) { > + kfree(ce->ce_path); > + kfree(ce); > + ce = ERR_PTR(rc); > + } > + return ce; > +} > + > +static void remove_oldest_entry(void) > +{ > + int bucket; > + struct dfs_cache_entry *ce; > + struct dfs_cache_entry *to_del = NULL; > + > + rcu_read_lock(); > + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { > + if (!to_del || timespec64_compare(&ce->ce_etime, > + &to_del->ce_etime) < 0) > + to_del = ce; > + } > + if (!to_del) { > + cifs_dbg(FYI, "%s: no entry to remove", __func__); > + goto out; > + } > + cifs_dbg(FYI, "%s: removing entry", __func__); > + dump_ce(to_del); > + flush_cache_ent(to_del); > +out: > + rcu_read_unlock(); > +} > + > +/* Add a new DFS cache entry */ > +static inline struct dfs_cache_entry * > +add_cache_entry(unsigned int hash, const char *path, > + const struct dfs_info3_param *refs, int numrefs) > +{ > + struct dfs_cache_entry *ce; > + > + ce = alloc_cache_entry(path, refs, numrefs); > + if (IS_ERR(ce)) > + return ce; > + > + hlist_add_head_rcu(&ce->ce_hlist, &dfs_cache_htable[hash]); > + > + mutex_lock(&dfs_cache.dc_lock); > + if (dfs_cache.dc_ttl < 0) { > + dfs_cache.dc_ttl = ce->ce_ttl; > + queue_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, > + dfs_cache.dc_ttl * HZ); > + } else { > + dfs_cache.dc_ttl = min_t(int, dfs_cache.dc_ttl, ce->ce_ttl); > + mod_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, > + dfs_cache.dc_ttl * HZ); > + } > + mutex_unlock(&dfs_cache.dc_lock); > + > + return ce; > +} > + > +static struct dfs_cache_entry *__find_cache_entry(unsigned int hash, > + const char *path, int len) > +{ > + struct dfs_cache_entry *ce; > + bool found = false; > + > + rcu_read_lock(); > + hlist_for_each_entry_rcu(ce, &dfs_cache_htable[hash], ce_hlist) { > + if (!strncasecmp(ce->ce_path, path, len)) { > +#ifdef CONFIG_CIFS_DEBUG2 > + char *name = get_tgt_name(ce); > + > + if (unlikely(IS_ERR(name))) { > + rcu_read_unlock(); > + return ERR_CAST(name); > + } > + cifs_dbg(FYI, "%s: cache hit\n", __func__); > + cifs_dbg(FYI, "%s: target hint: %s\n", __func__, name); > +#endif > + found = true; > + break; > + } > + } > + rcu_read_unlock(); > + return found ? ce : ERR_PTR(-ENOENT); > +} > + > +/* > + * Find a DFS cache entry in hash table and optionally check prefix path against > + * @path. > + * Use whole path components in the match. > + * Return ERR_PTR(-ENOENT) if the entry is not found. > + */ > +static inline struct dfs_cache_entry *find_cache_entry(const char *path, > + unsigned int *hash) > +{ > + int len = strlen(path); > + > + *hash = cache_entry_hash(path, len); > + return __find_cache_entry(*hash, path, len); > +} > + > +static inline void destroy_slab_cache(void) > +{ > + rcu_barrier(); > + kmem_cache_destroy(dfs_cache_slab); > +} > + > +static inline void free_vol(struct dfs_cache_vol_info *vi) > +{ > + list_del(&vi->vi_list); > + kfree(vi->vi_fullpath); > + cifs_cleanup_volume_info_contents(&vi->vi_vol); > + kfree(vi); > +} > + > +static inline void free_vol_list(void) > +{ > + struct dfs_cache_vol_info *vi, *nvi; > + > + list_for_each_entry_safe(vi, nvi, &dfs_cache.dc_vol_list, vi_list) > + free_vol(vi); > +} > + > +/** > + * dfs_cache_destroy - destroy DFS referral cache > + */ > +void dfs_cache_destroy(void) > +{ > + cancel_delayed_work_sync(&dfs_cache.dc_refresh); > + unload_nls(dfs_cache.dc_nlsc); > + free_vol_list(); > + mutex_destroy(&dfs_cache.dc_lock); > + > + flush_cache_ents(); > + destroy_slab_cache(); > + mutex_destroy(&dfs_cache_list_lock); > + > + cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__); > +} > + > +static inline struct dfs_cache_entry * > +__update_cache_entry(const char *path, const struct dfs_info3_param *refs, > + int numrefs) > +{ > + int rc; > + unsigned int h; > + struct dfs_cache_entry *ce; > + char *s, *th = NULL; > + > + ce = find_cache_entry(path, &h); > + if (IS_ERR(ce)) > + return ce; > + > + if (ce->ce_tgthint) { > + s = ce->ce_tgthint->t_name; > + th = kstrndup(s, strlen(s), GFP_KERNEL); > + if (!th) > + return ERR_PTR(-ENOMEM); > + } > + > + free_tgts(ce); > + ce->ce_numtgts = 0; > + > + rc = copy_ref_data(refs, numrefs, ce, th); > + kfree(th); > + > + if (rc) > + ce = ERR_PTR(rc); > + > + return ce; > +} > + > +/* Update an expired cache entry by getting a new DFS referral from server */ > +static struct dfs_cache_entry * > +update_cache_entry(const unsigned int xid, struct cifs_ses *ses, > + const struct nls_table *nls_codepage, int remap, > + const char *path, struct dfs_cache_entry *ce) > +{ > + int rc; > + struct dfs_info3_param *refs = NULL; > + int numrefs = 0; > + > + cifs_dbg(FYI, "%s: update expired cache entry\n", __func__); > + /* > + * Check if caller provided enough parameters to update an expired > + * entry. > + */ > + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) > + return ERR_PTR(-ETIME); > + if (unlikely(!nls_codepage)) > + return ERR_PTR(-ETIME); > + > + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, path); > + > + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, &numrefs, > + nls_codepage, remap); > + if (rc) > + ce = ERR_PTR(rc); > + else > + ce = __update_cache_entry(path, refs, numrefs); > + > + dump_refs(refs, numrefs); > + free_dfs_info_array(refs, numrefs); > + > + return ce; > +} > + > +/* > + * Find, create or update a DFS cache entry. > + * > + * If the entry wasn't found, it will create a new one. Or if it was found but > + * expired, then it will update the entry accordingly. > + * > + * For interlinks, __cifs_dfs_mount() and expand_dfs_referral() are supposed to > + * handle them properly. > + */ > +static struct dfs_cache_entry * > +do_dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, > + const struct nls_table *nls_codepage, int remap, > + const char *path, bool noreq) > +{ > + int rc; > + unsigned int h; > + struct dfs_cache_entry *ce; > + struct dfs_info3_param *nrefs; > + int numnrefs; > + > + cifs_dbg(FYI, "%s: search path: %s\n", __func__, path); > + > + ce = find_cache_entry(path, &h); > + if (IS_ERR(ce)) { > + cifs_dbg(FYI, "%s: cache miss\n", __func__); > + /* > + * If @noreq is set, no requests will be sent to the server for > + * either updating or getting a new DFS referral. > + */ > + if (noreq) > + return ce; > + /* > + * No cache entry was found, so check for valid parameters that > + * will be required to get a new DFS referral and then create a > + * new cache entry. > + */ > + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) { > + ce = ERR_PTR(-ENOSYS); > + return ce; > + } > + if (unlikely(!nls_codepage)) { > + ce = ERR_PTR(-EINVAL); > + return ce; > + } > + > + nrefs = NULL; > + numnrefs = 0; > + > + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, > + path); > + > + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &nrefs, > + &numnrefs, nls_codepage, > + remap); > + if (rc) { > + ce = ERR_PTR(rc); > + return ce; > + } > + > + dump_refs(nrefs, numnrefs); > + > + cifs_dbg(FYI, "%s: new cache entry\n", __func__); > + > + if (dfs_cache_count >= DFS_CACHE_MAX_ENTRIES) { > + cifs_dbg(FYI, "%s: reached max cache size (%d)", > + __func__, DFS_CACHE_MAX_ENTRIES); > + remove_oldest_entry(); > + } > + ce = add_cache_entry(h, path, nrefs, numnrefs); > + free_dfs_info_array(nrefs, numnrefs); > + > + if (IS_ERR(ce)) > + return ce; > + > + dfs_cache_count++; > + } > + > + dump_ce(ce); > + > + /* Just return the found cache entry in case @noreq is set */ > + if (noreq) > + return ce; > + > + if (cache_entry_expired(ce)) { > + cifs_dbg(FYI, "%s: expired cache entry\n", __func__); > + ce = update_cache_entry(xid, ses, nls_codepage, remap, path, > + ce); > + if (IS_ERR(ce)) { > + cifs_dbg(FYI, "%s: failed to update expired entry\n", > + __func__); > + } > + } > + return ce; > +} > + > +/* Set up a new DFS referral from a given cache entry */ > +static int setup_ref(const char *path, const struct dfs_cache_entry *ce, > + struct dfs_info3_param *ref, const char *tgt) > +{ > + int rc; > + > + cifs_dbg(FYI, "%s: set up new ref\n", __func__); > + > + memset(ref, 0, sizeof(*ref)); > + > + ref->path_name = kstrndup(path, strlen(path), GFP_KERNEL); > + if (!ref->path_name) > + return -ENOMEM; > + > + ref->path_consumed = ce->ce_path_consumed; > + > + ref->node_name = kstrndup(tgt, strlen(tgt), GFP_KERNEL); > + if (!ref->node_name) { > + rc = -ENOMEM; > + goto err_free_path; > + } > + > + ref->ttl = ce->ce_ttl; > + ref->server_type = ce->ce_srvtype; > + ref->ref_flag = ce->ce_flags; > + > + return 0; > + > +err_free_path: > + kfree(ref->path_name); > + ref->path_name = NULL; > + return rc; > +} > + > +/* Return target list of a DFS cache entry */ > +static int get_tgt_list(const struct dfs_cache_entry *ce, > + struct dfs_cache_tgt_list *tl) > +{ > + int rc; > + struct list_head *head = &tl->tl_list; > + struct dfs_cache_tgt *t; > + struct dfs_cache_tgt_iterator *it, *nit; > + > + memset(tl, 0, sizeof(*tl)); > + INIT_LIST_HEAD(head); > + > + list_for_each_entry(t, &ce->ce_tlist, t_list) { > + it = kzalloc(sizeof(*it), GFP_KERNEL); > + if (!it) { > + rc = -ENOMEM; > + goto err_free_it; > + } > + > + it->it_name = kstrndup(t->t_name, strlen(t->t_name), > + GFP_KERNEL); > + if (!it->it_name) { > + rc = -ENOMEM; > + goto err_free_it; > + } > + > + if (ce->ce_tgthint == t) > + list_add(&it->it_list, head); > + else > + list_add_tail(&it->it_list, head); > + } > + tl->tl_numtgts = ce->ce_numtgts; > + > + return 0; > + > +err_free_it: > + list_for_each_entry_safe(it, nit, head, it_list) { > + kfree(it->it_name); > + kfree(it); > + } > + return rc; > +} > + > +/** > + * dfs_cache_find - find a DFS cache entry > + * > + * If it doesn't find the cache entry, then it will get a DFS referral > + * for @path and create a new entry. > + * > + * In case the cache entry exists but expired, it will get a DFS referral > + * for @path and then update the respective cache entry. > + * > + * These parameters are passed down to the get_dfs_refer() call if it > + * needs to be issued: > + * @xid: syscall xid > + * @ses: smb session to issue the request on > + * @nls_codepage: charset conversion > + * @remap: path character remapping type > + * @path: path to lookup in DFS referral cache. > + * > + * @ref: when non-NULL, store single DFS referral result in it. > + * @tgt_list: when non-NULL, store complete DFS target list in it. > + * > + * Return zero if the target was found, otherwise non-zero. > + */ > +int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, > + const struct nls_table *nls_codepage, int remap, > + const char *path, struct dfs_info3_param *ref, > + struct dfs_cache_tgt_list *tgt_list) > +{ > + int rc; > + char *npath; > + struct dfs_cache_entry *ce; > + > + if (unlikely(!is_path_valid(path))) > + return -EINVAL; > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + return rc; > + > + mutex_lock(&dfs_cache_list_lock); > + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); > + if (!IS_ERR(ce)) { > + if (ref) > + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); > + else > + rc = 0; > + if (!rc && tgt_list) > + rc = get_tgt_list(ce, tgt_list); > + } else { > + rc = PTR_ERR(ce); > + } > + mutex_unlock(&dfs_cache_list_lock); > + free_normalized_path(path, npath); > + return rc; > +} > + > +/** > + * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to > + * the currently connected server. > + * > + * NOTE: This function will neither update a cache entry in case it was > + * expired, nor create a new cache entry if @path hasn't been found. It heavily > + * relies on an existing cache entry. > + * > + * @path: path to lookup in the DFS referral cache. > + * @ref: when non-NULL, store single DFS referral result in it. > + * @tgt_list: when non-NULL, store complete DFS target list in it. > + * > + * Return 0 if successful. > + * Return -ENOENT if the entry was not found. > + * Return non-zero for other errors. > + */ > +int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, > + struct dfs_cache_tgt_list *tgt_list) > +{ > + int rc; > + char *npath; > + struct dfs_cache_entry *ce; > + > + if (unlikely(!is_path_valid(path))) > + return -EINVAL; > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + return rc; > + > + mutex_lock(&dfs_cache_list_lock); > + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); > + if (IS_ERR(ce)) { > + rc = PTR_ERR(ce); > + goto out; > + } > + > + if (ref) > + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); > + else > + rc = 0; > + if (!rc && tgt_list) > + rc = get_tgt_list(ce, tgt_list); > +out: > + mutex_unlock(&dfs_cache_list_lock); > + free_normalized_path(path, npath); > + return rc; > +} > + > +/** > + * dfs_cache_update_tgthint - update target hint of a DFS cache entry > + * > + * If it doesn't find the cache entry, then it will get a DFS referral for @path > + * and create a new entry. > + * > + * In case the cache entry exists but expired, it will get a DFS referral > + * for @path and then update the respective cache entry. > + * > + * @xid: syscall id > + * @ses: smb session > + * @nls_codepage: charset conversion > + * @remap: type of character remapping for paths > + * @path: path to lookup in DFS referral cache. > + * @it: DFS target iterator > + * > + * Return zero if the target hint was updated successfully, otherwise non-zero. > + */ > +int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses, > + const struct nls_table *nls_codepage, int remap, > + const char *path, > + const struct dfs_cache_tgt_iterator *it) > +{ > + int rc; > + char *npath; > + struct dfs_cache_entry *ce; > + struct dfs_cache_tgt *t; > + > + if (unlikely(!is_path_valid(path))) > + return -EINVAL; > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + return rc; > + > + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); > + > + mutex_lock(&dfs_cache_list_lock); > + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); > + if (IS_ERR(ce)) { > + rc = PTR_ERR(ce); > + goto out; > + } > + > + rc = 0; > + > + t = ce->ce_tgthint; > + > + if (likely(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) > + goto out; > + > + list_for_each_entry(t, &ce->ce_tlist, t_list) { > + if (!strncasecmp(t->t_name, it->it_name, strlen(it->it_name))) { > + ce->ce_tgthint = t; > + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, > + it->it_name); > + break; > + } > + } > + > +out: > + mutex_unlock(&dfs_cache_list_lock); > + free_normalized_path(path, npath); > + return rc; > +} > + > +/** > + * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry > + * without sending any requests to the currently connected server. > + * > + * NOTE: This function will neither update a cache entry in case it was > + * expired, nor create a new cache entry if @path hasn't been found. It heavily > + * relies on an existing cache entry. > + * > + * @path: path to lookup in DFS referral cache. > + * @it: target iterator which contains the target hint to update the cache > + * entry with. > + * > + * Return zero if the target hint was updated successfully, otherwise non-zero. > + */ > +int dfs_cache_noreq_update_tgthint(const char *path, > + const struct dfs_cache_tgt_iterator *it) > +{ > + int rc; > + char *npath; > + struct dfs_cache_entry *ce; > + struct dfs_cache_tgt *t; > + > + if (unlikely(!is_path_valid(path)) || !it) > + return -EINVAL; > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + return rc; > + > + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); > + > + mutex_lock(&dfs_cache_list_lock); > + > + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); > + if (IS_ERR(ce)) { > + rc = PTR_ERR(ce); > + goto out; > + } > + > + rc = 0; > + > + t = ce->ce_tgthint; > + > + if (unlikely(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) > + goto out; > + > + list_for_each_entry(t, &ce->ce_tlist, t_list) { > + if (!strncasecmp(t->t_name, it->it_name, strlen(it->it_name))) { > + ce->ce_tgthint = t; > + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, > + it->it_name); > + break; > + } > + } > + > +out: > + mutex_unlock(&dfs_cache_list_lock); > + free_normalized_path(path, npath); > + return rc; > +} > + > +/** > + * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given > + * target iterator (@it). > + * > + * @path: path to lookup in DFS referral cache. > + * @it: DFS target iterator. > + * @ref: DFS referral pointer to set up the gathered information. > + * > + * Return zero if the DFS referral was set up correctly, otherwise non-zero. > + */ > +int dfs_cache_get_tgt_referral(const char *path, > + const struct dfs_cache_tgt_iterator *it, > + struct dfs_info3_param *ref) > +{ > + int rc; > + char *npath; > + struct dfs_cache_entry *ce; > + unsigned int h; > + > + if (!it || !ref) > + return -EINVAL; > + if (unlikely(!is_path_valid(path))) > + return -EINVAL; > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + return rc; > + > + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); > + > + mutex_lock(&dfs_cache_list_lock); > + > + ce = find_cache_entry(npath, &h); > + if (IS_ERR(ce)) { > + rc = PTR_ERR(ce); > + goto out; > + } > + > + cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name); > + > + rc = setup_ref(path, ce, ref, it->it_name); > + > +out: > + mutex_unlock(&dfs_cache_list_lock); > + free_normalized_path(path, npath); > + return rc; > +} > + > +static int dup_vol(struct smb_vol *vol, struct smb_vol *new) > +{ > + memcpy(new, vol, sizeof(*new)); > + > + if (vol->username) { > + new->username = kstrndup(vol->username, strlen(vol->username), > + GFP_KERNEL); > + if (!new->username) > + return -ENOMEM; > + } > + if (vol->password) { > + new->password = kstrndup(vol->password, strlen(vol->password), > + GFP_KERNEL); > + if (!new->password) > + goto err_free_username; > + } > + if (vol->UNC) { > + cifs_dbg(FYI, "%s: vol->UNC: %s\n", __func__, vol->UNC); > + new->UNC = kstrndup(vol->UNC, strlen(vol->UNC), GFP_KERNEL); > + if (!new->UNC) > + goto err_free_password; > + } > + if (vol->domainname) { > + new->domainname = kstrndup(vol->domainname, > + strlen(vol->domainname), GFP_KERNEL); > + if (!new->domainname) > + goto err_free_unc; > + } > + if (vol->iocharset) { > + new->iocharset = kstrndup(vol->iocharset, > + strlen(vol->iocharset), GFP_KERNEL); > + if (!new->iocharset) > + goto err_free_domainname; > + } > + if (vol->prepath) { > + cifs_dbg(FYI, "%s: vol->prepath: %s\n", __func__, vol->prepath); > + new->prepath = kstrndup(vol->prepath, strlen(vol->prepath), > + GFP_KERNEL); > + if (!new->prepath) > + goto err_free_iocharset; > + } > + > + return 0; > + > +err_free_iocharset: > + kfree(new->iocharset); > +err_free_domainname: > + kfree(new->domainname); > +err_free_unc: > + kfree(new->UNC); > +err_free_password: > + kfree(new->password); > +err_free_username: > + kfree(new->username); > + kfree(new); > + return -ENOMEM; > +} > + > +/** > + * dfs_cache_add_vol - add a cifs volume during mount() that will be handled by > + * DFS cache refresh worker. > + * > + * @vol: cifs volume. > + * @fullpath: origin full path. > + * > + * Return zero if volume was set up correctly, otherwise non-zero. > + */ > +int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath) > +{ > + int rc; > + struct dfs_cache_vol_info *vi; > + > + if (!vol || !fullpath) > + return -EINVAL; > + > + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); > + > + vi = kzalloc(sizeof(*vi), GFP_KERNEL); > + if (!vi) > + return -ENOMEM; > + > + vi->vi_fullpath = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); > + if (!vi->vi_fullpath) { > + rc = -ENOMEM; > + goto err_free_vi; > + } > + > + rc = dup_vol(vol, &vi->vi_vol); > + if (rc) > + goto err_free_fullpath; > + > + mutex_lock(&dfs_cache.dc_lock); > + list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list); > + mutex_unlock(&dfs_cache.dc_lock); > + return 0; > + > +err_free_fullpath: > + kfree(vi->vi_fullpath); > +err_free_vi: > + kfree(vi); > + return rc; > +} > + > +static inline struct dfs_cache_vol_info *find_vol(const char *fullpath) > +{ > + struct dfs_cache_vol_info *vi; > + > + list_for_each_entry(vi, &dfs_cache.dc_vol_list, vi_list) { > + cifs_dbg(FYI, "%s: vi->vi_fullpath: %s\n", __func__, > + vi->vi_fullpath); > + if (!strncasecmp(vi->vi_fullpath, fullpath, > + strlen(vi->vi_fullpath))) > + return vi; > + } > + return ERR_PTR(-ENOENT); > +} > + > +/** > + * dfs_cache_update_vol - update vol info in DFS cache after failover > + * > + * @fullpath: fullpath to look up in volume list. > + * @server: TCP ses pointer. > + * > + * Return zero if volume was updated, otherwise non-zero. > + */ > +int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server) > +{ > + int rc; > + struct dfs_cache_vol_info *vi; > + > + if (!fullpath || !server) > + return -EINVAL; > + > + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); > + > + mutex_lock(&dfs_cache.dc_lock); > + > + vi = find_vol(fullpath); > + if (IS_ERR(vi)) { > + rc = PTR_ERR(vi); > + goto out; > + } > + > + cifs_dbg(FYI, "%s: updating volume info\n", __func__); > + memcpy(&vi->vi_vol.dstaddr, &server->dstaddr, > + sizeof(vi->vi_vol.dstaddr)); > + rc = 0; > + > +out: > + mutex_unlock(&dfs_cache.dc_lock); > + return rc; > +} > + > +/** > + * dfs_cache_del_vol - remove volume info in DFS cache during umount() > + * > + * @fullpath: fullpath to look up in volume list. > + * > + * Return zero if volume was deleted, otherwise non-zero. > + */ > +void dfs_cache_del_vol(const char *fullpath) > +{ > + struct dfs_cache_vol_info *vi; > + > + if (!fullpath || !*fullpath) > + return; > + > + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); > + > + mutex_lock(&dfs_cache.dc_lock); > + vi = find_vol(fullpath); > + if (!IS_ERR(vi)) > + free_vol(vi); > + mutex_unlock(&dfs_cache.dc_lock); > +} > + > +/* Get all tcons that are within a DFS namespace and can be refreshed */ > +static void get_tcons(struct TCP_Server_Info *server, struct list_head *head) > +{ > + struct cifs_ses *ses; > + struct cifs_tcon *tcon; > + > + INIT_LIST_HEAD(head); > + > + spin_lock(&cifs_tcp_ses_lock); > + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { > + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { > + if (!tcon->need_reconnect && !tcon->need_reopen_files && > + tcon->dfs_path) { > + tcon->tc_count++; > + list_add_tail(&tcon->ulist, head); > + } > + } > + if (ses->tcon_ipc && !ses->tcon_ipc->need_reconnect && > + ses->tcon_ipc->dfs_path) { > + list_add_tail(&ses->tcon_ipc->ulist, head); > + } > + } > + spin_unlock(&cifs_tcp_ses_lock); > +} > + > +/* Refresh DFS cache entry from a given tcon */ > +static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon) > +{ > + int rc = 0; > + unsigned int xid; > + char *path, *npath; > + unsigned int h; > + struct dfs_cache_entry *ce; > + struct dfs_info3_param *refs = NULL; > + int numrefs = 0; > + > + xid = get_xid(); > + > + path = tcon->dfs_path + 1; > + > + cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path); > + > + rc = get_normalized_path(path, &npath); > + if (rc) > + goto out; > + > + mutex_lock(&dfs_cache_list_lock); > + ce = find_cache_entry(npath, &h); > + mutex_unlock(&dfs_cache_list_lock); > + > + if (IS_ERR(ce)) { > + rc = PTR_ERR(ce); > + goto out; > + } > + > + if (!cache_entry_expired(ce)) > + goto out; > + > + if (unlikely(!tcon->ses->server->ops->get_dfs_refer)) { > + rc = -ENOSYS; > + } else { > + rc = tcon->ses->server->ops->get_dfs_refer(xid, tcon->ses, path, > + &refs, &numrefs, > + dc->dc_nlsc, > + tcon->remap); > + if (!rc) { > + mutex_lock(&dfs_cache_list_lock); > + ce = __update_cache_entry(npath, refs, numrefs); > + mutex_unlock(&dfs_cache_list_lock); > + dump_refs(refs, numrefs); > + free_dfs_info_array(refs, numrefs); > + if (IS_ERR(ce)) > + rc = PTR_ERR(ce); > + } > + } > + if (rc) > + cifs_dbg(FYI, "%s: failed to update expired entry\n", __func__); > +out: > + free_xid(xid); > + free_normalized_path(path, npath); > +} > + > +/* > + * Worker that will refresh DFS cache based on lowest TTL value from a DFS > + * referral. > + * > + * FIXME: ensure that all requests are sent to DFS root for refreshing the > + * cache. > + */ > +static void refresh_cache_worker(struct work_struct *work) > +{ > + struct dfs_cache *dc = container_of(work, struct dfs_cache, > + dc_refresh.work); > + struct dfs_cache_vol_info *vi; > + struct TCP_Server_Info *server; > + LIST_HEAD(list); > + struct cifs_tcon *tcon, *ntcon; > + > + cifs_dbg(FYI, "%s: refreshing DFS referral cache\n", __func__); > + > + mutex_lock(&dc->dc_lock); > + > + list_for_each_entry(vi, &dc->dc_vol_list, vi_list) { > + cifs_dbg(FYI, "%s: vol path: %s\n", __func__, vi->vi_fullpath); > + server = cifs_find_tcp_session(&vi->vi_vol); > + if (!server) > + continue; > + cifs_dbg(FYI, "%s: found a tcp ses: %p\n", __func__, server); > + if (server->tcpStatus != CifsGood) > + goto next; > + get_tcons(server, &list); > + list_for_each_entry_safe(tcon, ntcon, &list, ulist) { > + do_refresh_tcon(dc, tcon); > + list_del_init(&tcon->ulist); > + cifs_put_tcon(tcon); > + } > +next: > + cifs_put_tcp_session(server, 0); > + } > + queue_delayed_work(cifsiod_wq, &dc->dc_refresh, dc->dc_ttl * HZ); > + mutex_unlock(&dc->dc_lock); > + > + cifs_dbg(FYI, "%s: finished refreshing DFS referral cache\n", __func__); > +} > diff --git a/fs/cifs/dfs_cache.h b/fs/cifs/dfs_cache.h > new file mode 100644 > index 000000000000..22f366514f3a > --- /dev/null > +++ b/fs/cifs/dfs_cache.h > @@ -0,0 +1,97 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * DFS referral cache routines > + * > + * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> > + */ > + > +#ifndef _CIFS_DFS_CACHE_H > +#define _CIFS_DFS_CACHE_H > + > +#include <linux/nls.h> > +#include <linux/list.h> > +#include "cifsglob.h" > + > +struct dfs_cache_tgt_list { > + int tl_numtgts; > + struct list_head tl_list; > +}; > + > +struct dfs_cache_tgt_iterator { > + char *it_name; > + struct list_head it_list; > +}; > + > +extern int dfs_cache_init(void); > +extern void dfs_cache_destroy(void); > +extern const struct file_operations dfscache_proc_fops; > + > +extern int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, > + const struct nls_table *nls_codepage, int remap, > + const char *path, struct dfs_info3_param *ref, > + struct dfs_cache_tgt_list *tgt_list); > +extern int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, > + struct dfs_cache_tgt_list *tgt_list); > +extern int dfs_cache_update_tgthint(const unsigned int xid, > + struct cifs_ses *ses, > + const struct nls_table *nls_codepage, > + int remap, const char *path, > + const struct dfs_cache_tgt_iterator *it); > +extern int > +dfs_cache_noreq_update_tgthint(const char *path, > + const struct dfs_cache_tgt_iterator *it); > +extern int dfs_cache_get_tgt_referral(const char *path, > + const struct dfs_cache_tgt_iterator *it, > + struct dfs_info3_param *ref); > +extern int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath); > +extern int dfs_cache_update_vol(const char *fullpath, > + struct TCP_Server_Info *server); > +extern void dfs_cache_del_vol(const char *fullpath); > + > +static inline struct dfs_cache_tgt_iterator * > +dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, > + struct dfs_cache_tgt_iterator *it) > +{ > + if (!tl || list_empty(&tl->tl_list) || !it || > + list_is_last(&it->it_list, &tl->tl_list)) > + return NULL; > + return list_next_entry(it, it_list); > +} > + > +static inline struct dfs_cache_tgt_iterator * > +dfs_cache_get_tgt_iterator(struct dfs_cache_tgt_list *tl) > +{ > + if (!tl) > + return NULL; > + return list_first_entry_or_null(&tl->tl_list, > + struct dfs_cache_tgt_iterator, > + it_list); > +} > + > +static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl) > +{ > + struct dfs_cache_tgt_iterator *it, *nit; > + > + if (!tl || list_empty(&tl->tl_list)) > + return; > + list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) { > + list_del(&it->it_list); > + kfree(it->it_name); > + kfree(it); > + } > + tl->tl_numtgts = 0; > +} > + > +static inline const char * > +dfs_cache_get_tgt_name(const struct dfs_cache_tgt_iterator *it) > +{ > + return it ? it->it_name : NULL; > +} > + > +static inline int > +dfs_cache_get_nr_tgts(const struct dfs_cache_tgt_list *tl) > +{ > + return tl ? tl->tl_numtgts : 0; > +} > + > +#endif /* _CIFS_DFS_CACHE_H */ > -- > 2.13.7 >
diff --git a/fs/cifs/Makefile b/fs/cifs/Makefile index 85817991ee68..51af69a1a328 100644 --- a/fs/cifs/Makefile +++ b/fs/cifs/Makefile @@ -17,7 +17,7 @@ cifs-$(CONFIG_CIFS_ACL) += cifsacl.o cifs-$(CONFIG_CIFS_UPCALL) += cifs_spnego.o -cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o +cifs-$(CONFIG_CIFS_DFS_UPCALL) += dns_resolve.o cifs_dfs_ref.o dfs_cache.o cifs-$(CONFIG_CIFS_FSCACHE) += fscache.o cache.o diff --git a/fs/cifs/cifs_debug.c b/fs/cifs/cifs_debug.c index ba178b09de0b..593fb422d0f3 100644 --- a/fs/cifs/cifs_debug.c +++ b/fs/cifs/cifs_debug.c @@ -30,6 +30,9 @@ #include "cifsproto.h" #include "cifs_debug.h" #include "cifsfs.h" +#ifdef CONFIG_CIFS_DFS_UPCALL +#include "dfs_cache.h" +#endif #ifdef CONFIG_CIFS_SMB_DIRECT #include "smbdirect.h" #endif @@ -629,6 +632,11 @@ cifs_proc_init(void) &cifs_security_flags_proc_fops); proc_create("LookupCacheEnabled", 0644, proc_fs_cifs, &cifs_lookup_cache_proc_fops); + +#ifdef CONFIG_CIFS_DFS_UPCALL + proc_create("dfscache", 0644, proc_fs_cifs, &dfscache_proc_fops); +#endif + #ifdef CONFIG_CIFS_SMB_DIRECT proc_create("rdma_readwrite_threshold", 0644, proc_fs_cifs, &cifs_rdma_readwrite_threshold_proc_fops); @@ -663,6 +671,10 @@ cifs_proc_clean(void) remove_proc_entry("SecurityFlags", proc_fs_cifs); remove_proc_entry("LinuxExtensionsEnabled", proc_fs_cifs); remove_proc_entry("LookupCacheEnabled", proc_fs_cifs); + +#ifdef CONFIG_CIFS_DFS_UPCALL + remove_proc_entry("dfscache", proc_fs_cifs); +#endif #ifdef CONFIG_CIFS_SMB_DIRECT remove_proc_entry("rdma_readwrite_threshold", proc_fs_cifs); remove_proc_entry("smbd_max_frmr_depth", proc_fs_cifs); diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 9534f46e6ad2..bd183ec17066 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -1014,6 +1014,11 @@ struct cifs_tcon { struct list_head pending_opens; /* list of incomplete opens */ struct cached_fid crfid; /* Cached root fid */ /* BB add field for back pointer to sb struct(s)? */ +#ifdef CONFIG_CIFS_DFS_UPCALL + char *dfs_path; + int remap:2; + struct list_head ulist; /* cache update list */ +#endif }; /* diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index f4dd2a3795dd..efa5e36b3762 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h @@ -527,6 +527,9 @@ extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, extern void cifs_cleanup_volume_info_contents(struct smb_vol *volume_info); +extern struct TCP_Server_Info * +cifs_find_tcp_session(struct smb_vol *vol); + void cifs_readdata_release(struct kref *refcount); int cifs_async_readv(struct cifs_readdata *rdata); int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid); diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 86649230b4f2..b4fed69add86 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -2294,7 +2294,7 @@ static int match_server(struct TCP_Server_Info *server, struct smb_vol *vol) return 1; } -static struct TCP_Server_Info * +struct TCP_Server_Info * cifs_find_tcp_session(struct smb_vol *vol) { struct TCP_Server_Info *server; diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c new file mode 100644 index 000000000000..18da279e9354 --- /dev/null +++ b/fs/cifs/dfs_cache.c @@ -0,0 +1,1379 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DFS referral cache routines + * + * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> + */ + +#include <linux/rcupdate.h> +#include <linux/rculist.h> +#include <linux/jhash.h> +#include <linux/ktime.h> +#include <linux/slab.h> +#include <linux/nls.h> +#include <linux/workqueue.h> +#include "cifsglob.h" +#include "smb2pdu.h" +#include "smb2proto.h" +#include "cifsproto.h" +#include "cifs_debug.h" +#include "cifs_unicode.h" +#include "smb2glob.h" + +#include "dfs_cache.h" + +#define DFS_CACHE_HTABLE_SIZE 32 +#define DFS_CACHE_MAX_ENTRIES 64 + +#define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \ + DFSREF_STORAGE_SERVER)) + +struct dfs_cache_tgt { + char *t_name; + struct list_head t_list; +}; + +struct dfs_cache_entry { + struct hlist_node ce_hlist; + const char *ce_path; + int ce_ttl; + int ce_srvtype; + int ce_flags; + struct timespec64 ce_etime; + int ce_path_consumed; + int ce_numtgts; + struct list_head ce_tlist; + struct dfs_cache_tgt *ce_tgthint; + struct rcu_head ce_rcu; +}; + +static struct kmem_cache *dfs_cache_slab __read_mostly; + +struct dfs_cache_vol_info { + char *vi_fullpath; + struct smb_vol vi_vol; + struct list_head vi_list; +}; + +struct dfs_cache { + struct mutex dc_lock; + struct nls_table *dc_nlsc; + struct list_head dc_vol_list; + int dc_ttl; + struct delayed_work dc_refresh; +}; + +static struct dfs_cache dfs_cache; + +/* + * Number of entries in the cache + */ +static size_t dfs_cache_count; + +static DEFINE_MUTEX(dfs_cache_list_lock); +static struct hlist_head dfs_cache_htable[DFS_CACHE_HTABLE_SIZE]; + +static void refresh_cache_worker(struct work_struct *work); + +static inline bool is_path_valid(const char *path) +{ + return path && (strchr(path + 1, '\\') || strchr(path + 1, '/')); +} + +static inline int get_normalized_path(const char *path, char **npath) +{ + if (*path == '\\') { + *npath = (char *)path; + } else { + *npath = kstrndup(path, strlen(path), GFP_KERNEL); + if (!*npath) + return -ENOMEM; + convert_delimiter(*npath, '\\'); + } + return 0; +} + +static inline void free_normalized_path(const char *path, char *npath) +{ + if (path != npath) + kfree(npath); +} + +static inline bool cache_entry_expired(const struct dfs_cache_entry *ce) +{ + struct timespec64 ts; + + ts = current_kernel_time64(); + return timespec64_compare(&ts, &ce->ce_etime) >= 0; +} + +static inline void free_tgts(struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t, *n; + + list_for_each_entry_safe(t, n, &ce->ce_tlist, t_list) { + list_del(&t->t_list); + kfree(t->t_name); + kfree(t); + } +} + +static void free_cache_entry(struct rcu_head *rcu) +{ + struct dfs_cache_entry *ce = container_of(rcu, struct dfs_cache_entry, + ce_rcu); + kmem_cache_free(dfs_cache_slab, ce); +} + +static inline void flush_cache_ent(struct dfs_cache_entry *ce) +{ + if (hlist_unhashed(&ce->ce_hlist)) + return; + + hlist_del_init_rcu(&ce->ce_hlist); + kfree(ce->ce_path); + free_tgts(ce); + dfs_cache_count--; + call_rcu(&ce->ce_rcu, free_cache_entry); +} + +static void flush_cache_ents(void) +{ + int i; + + rcu_read_lock(); + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) { + struct hlist_head *l = &dfs_cache_htable[i]; + struct dfs_cache_entry *ce; + + hlist_for_each_entry_rcu(ce, l, ce_hlist) + flush_cache_ent(ce); + } + rcu_read_unlock(); +} + +/* + * dfs cache /proc file + */ +static int dfscache_proc_show(struct seq_file *m, void *v) +{ + int bucket; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + seq_puts(m, "DFS cache\n---------\n"); + + mutex_lock(&dfs_cache_list_lock); + + rcu_read_lock(); + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { + seq_printf(m, + "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," + "interlink=%s,path_consumed=%d,expired=%s\n", + ce->ce_path, + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", + ce->ce_ttl, ce->ce_etime.tv_nsec, + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", + ce->ce_path_consumed, + cache_entry_expired(ce) ? "yes" : "no"); + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + seq_printf(m, " %s%s\n", + t->t_name, + ce->ce_tgthint == t ? " (target hint)" : ""); + } + + } + rcu_read_unlock(); + + mutex_unlock(&dfs_cache_list_lock); + return 0; +} + +static ssize_t dfscache_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char c; + int rc; + + rc = get_user(c, buffer); + if (rc) + return rc; + + if (c != '0') + return -EINVAL; + + cifs_dbg(FYI, "clearing dfs cache"); + mutex_lock(&dfs_cache_list_lock); + flush_cache_ents(); + mutex_unlock(&dfs_cache_list_lock); + + return count; +} + +static int dfscache_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, dfscache_proc_show, NULL); +} + +const struct file_operations dfscache_proc_fops = { + .open = dfscache_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dfscache_proc_write, +}; + +#ifdef CONFIG_CIFS_DEBUG2 +static inline void dump_tgts(const struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t; + + cifs_dbg(FYI, "target list:\n"); + list_for_each_entry(t, &ce->ce_tlist, t_list) { + cifs_dbg(FYI, " %s%s\n", t->t_name, + ce->ce_tgthint == t ? " (target hint)" : ""); + } +} + +static inline void dump_ce(const struct dfs_cache_entry *ce) +{ + cifs_dbg(FYI, "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," + "interlink=%s,path_consumed=%d,expired=%s\n", ce->ce_path, + ce->ce_srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ce_ttl, + ce->ce_etime.tv_nsec, + IS_INTERLINK_SET(ce->ce_flags) ? "yes" : "no", + ce->ce_path_consumed, + cache_entry_expired(ce) ? "yes" : "no"); + dump_tgts(ce); +} + +static inline void dump_refs(const struct dfs_info3_param *refs, int numrefs) +{ + int i; + + cifs_dbg(FYI, "DFS referrals returned by the server:\n"); + for (i = 0; i < numrefs; i++) { + const struct dfs_info3_param *ref = &refs[i]; + + cifs_dbg(FYI, + "\n" + "flags: 0x%x\n" + "path_consumed: %d\n" + "server_type: 0x%x\n" + "ref_flag: 0x%x\n" + "path_name: %s\n" + "node_name: %s\n" + "ttl: %d (%dm)\n", + ref->flags, ref->path_consumed, ref->server_type, + ref->ref_flag, ref->path_name, ref->node_name, + ref->ttl, ref->ttl / 60); + } +} +#else +#define dump_tgts(e) +#define dump_ce(e) +#define dump_refs(r, n) +#endif + +/** + * dfs_cache_init - Initialize DFS referral cache. + * + * Return zero if initialized successfully, otherwise non-zero. + */ +int dfs_cache_init(void) +{ + int i; + + dfs_cache_slab = kmem_cache_create("cifs_dfs_cache", + sizeof(struct dfs_cache_entry), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!dfs_cache_slab) + return -ENOMEM; + + for (i = 0; i < DFS_CACHE_HTABLE_SIZE; i++) + INIT_HLIST_HEAD(&dfs_cache_htable[i]); + + INIT_LIST_HEAD(&dfs_cache.dc_vol_list); + mutex_init(&dfs_cache.dc_lock); + INIT_DELAYED_WORK(&dfs_cache.dc_refresh, refresh_cache_worker); + dfs_cache.dc_ttl = -1; + dfs_cache.dc_nlsc = load_nls_default(); + + cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); + return 0; +} + +static inline unsigned int cache_entry_hash(const void *data, int size) +{ + unsigned int h; + + h = jhash(data, size, 0); + return h & (DFS_CACHE_HTABLE_SIZE - 1); +} + +/* Check whether second path component of @path is SYSVOL or NETLOGON */ +static inline bool is_sysvol_or_netlogon(const char *path) +{ + const char *s; + char sep = path[0]; + + s = strchr(path + 1, sep) + 1; + return !strncasecmp(s, "sysvol", strlen("sysvol")) || + !strncasecmp(s, "netlogon", strlen("netlogon")); +} + +/* Return target hint of a DFS cache entry */ +static inline char *get_tgt_name(const struct dfs_cache_entry *ce) +{ + struct dfs_cache_tgt *t = ce->ce_tgthint; + + return t ? t->t_name : ERR_PTR(-ENOENT); +} + +/* Return expire time out of a new entry's TTL */ +static inline struct timespec64 get_expire_time(int ttl) +{ + struct timespec64 ts = { + .tv_sec = ttl, + .tv_nsec = 0, + }; + + return timespec64_add(current_kernel_time64(), ts); +} + +/* Allocate a new DFS target */ +static inline struct dfs_cache_tgt *alloc_tgt(const char *name) +{ + struct dfs_cache_tgt *t; + + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (!t) + return ERR_PTR(-ENOMEM); + t->t_name = kstrndup(name, strlen(name), GFP_KERNEL); + if (!t->t_name) { + kfree(t); + return ERR_PTR(-ENOMEM); + } + INIT_LIST_HEAD(&t->t_list); + return t; +} + +/* + * Copy DFS referral information to a cache entry and conditionally update + * target hint. + */ +static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, + struct dfs_cache_entry *ce, const char *tgthint) +{ + int i; + + ce->ce_ttl = refs[0].ttl; + ce->ce_etime = get_expire_time(ce->ce_ttl); + ce->ce_srvtype = refs[0].server_type; + ce->ce_flags = refs[0].ref_flag; + ce->ce_path_consumed = refs[0].path_consumed; + + for (i = 0; i < numrefs; i++) { + struct dfs_cache_tgt *t; + + t = alloc_tgt(refs[i].node_name); + if (IS_ERR(t)) { + free_tgts(ce); + return PTR_ERR(t); + } + if (tgthint && !strncasecmp(t->t_name, tgthint, + strlen(tgthint))) { + list_add(&t->t_list, &ce->ce_tlist); + tgthint = NULL; + } else { + list_add_tail(&t->t_list, &ce->ce_tlist); + } + ce->ce_numtgts++; + } + + ce->ce_tgthint = list_first_entry_or_null(&ce->ce_tlist, + struct dfs_cache_tgt, t_list); + + return 0; +} + +/* Allocate a new cache entry */ +static struct dfs_cache_entry * +alloc_cache_entry(const char *path, const struct dfs_info3_param *refs, + int numrefs) +{ + struct dfs_cache_entry *ce; + int rc; + + ce = kmem_cache_zalloc(dfs_cache_slab, GFP_KERNEL); + if (!ce) + return ERR_PTR(-ENOMEM); + + ce->ce_path = kstrdup_const(path, GFP_KERNEL); + if (!ce->ce_path) { + kfree(ce); + return ERR_PTR(-ENOMEM); + } + INIT_HLIST_NODE(&ce->ce_hlist); + INIT_LIST_HEAD(&ce->ce_tlist); + + rc = copy_ref_data(refs, numrefs, ce, NULL); + if (rc) { + kfree(ce->ce_path); + kfree(ce); + ce = ERR_PTR(rc); + } + return ce; +} + +static void remove_oldest_entry(void) +{ + int bucket; + struct dfs_cache_entry *ce; + struct dfs_cache_entry *to_del = NULL; + + rcu_read_lock(); + hash_for_each_rcu(dfs_cache_htable, bucket, ce, ce_hlist) { + if (!to_del || timespec64_compare(&ce->ce_etime, + &to_del->ce_etime) < 0) + to_del = ce; + } + if (!to_del) { + cifs_dbg(FYI, "%s: no entry to remove", __func__); + goto out; + } + cifs_dbg(FYI, "%s: removing entry", __func__); + dump_ce(to_del); + flush_cache_ent(to_del); +out: + rcu_read_unlock(); +} + +/* Add a new DFS cache entry */ +static inline struct dfs_cache_entry * +add_cache_entry(unsigned int hash, const char *path, + const struct dfs_info3_param *refs, int numrefs) +{ + struct dfs_cache_entry *ce; + + ce = alloc_cache_entry(path, refs, numrefs); + if (IS_ERR(ce)) + return ce; + + hlist_add_head_rcu(&ce->ce_hlist, &dfs_cache_htable[hash]); + + mutex_lock(&dfs_cache.dc_lock); + if (dfs_cache.dc_ttl < 0) { + dfs_cache.dc_ttl = ce->ce_ttl; + queue_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, + dfs_cache.dc_ttl * HZ); + } else { + dfs_cache.dc_ttl = min_t(int, dfs_cache.dc_ttl, ce->ce_ttl); + mod_delayed_work(cifsiod_wq, &dfs_cache.dc_refresh, + dfs_cache.dc_ttl * HZ); + } + mutex_unlock(&dfs_cache.dc_lock); + + return ce; +} + +static struct dfs_cache_entry *__find_cache_entry(unsigned int hash, + const char *path, int len) +{ + struct dfs_cache_entry *ce; + bool found = false; + + rcu_read_lock(); + hlist_for_each_entry_rcu(ce, &dfs_cache_htable[hash], ce_hlist) { + if (!strncasecmp(ce->ce_path, path, len)) { +#ifdef CONFIG_CIFS_DEBUG2 + char *name = get_tgt_name(ce); + + if (unlikely(IS_ERR(name))) { + rcu_read_unlock(); + return ERR_CAST(name); + } + cifs_dbg(FYI, "%s: cache hit\n", __func__); + cifs_dbg(FYI, "%s: target hint: %s\n", __func__, name); +#endif + found = true; + break; + } + } + rcu_read_unlock(); + return found ? ce : ERR_PTR(-ENOENT); +} + +/* + * Find a DFS cache entry in hash table and optionally check prefix path against + * @path. + * Use whole path components in the match. + * Return ERR_PTR(-ENOENT) if the entry is not found. + */ +static inline struct dfs_cache_entry *find_cache_entry(const char *path, + unsigned int *hash) +{ + int len = strlen(path); + + *hash = cache_entry_hash(path, len); + return __find_cache_entry(*hash, path, len); +} + +static inline void destroy_slab_cache(void) +{ + rcu_barrier(); + kmem_cache_destroy(dfs_cache_slab); +} + +static inline void free_vol(struct dfs_cache_vol_info *vi) +{ + list_del(&vi->vi_list); + kfree(vi->vi_fullpath); + cifs_cleanup_volume_info_contents(&vi->vi_vol); + kfree(vi); +} + +static inline void free_vol_list(void) +{ + struct dfs_cache_vol_info *vi, *nvi; + + list_for_each_entry_safe(vi, nvi, &dfs_cache.dc_vol_list, vi_list) + free_vol(vi); +} + +/** + * dfs_cache_destroy - destroy DFS referral cache + */ +void dfs_cache_destroy(void) +{ + cancel_delayed_work_sync(&dfs_cache.dc_refresh); + unload_nls(dfs_cache.dc_nlsc); + free_vol_list(); + mutex_destroy(&dfs_cache.dc_lock); + + flush_cache_ents(); + destroy_slab_cache(); + mutex_destroy(&dfs_cache_list_lock); + + cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__); +} + +static inline struct dfs_cache_entry * +__update_cache_entry(const char *path, const struct dfs_info3_param *refs, + int numrefs) +{ + int rc; + unsigned int h; + struct dfs_cache_entry *ce; + char *s, *th = NULL; + + ce = find_cache_entry(path, &h); + if (IS_ERR(ce)) + return ce; + + if (ce->ce_tgthint) { + s = ce->ce_tgthint->t_name; + th = kstrndup(s, strlen(s), GFP_KERNEL); + if (!th) + return ERR_PTR(-ENOMEM); + } + + free_tgts(ce); + ce->ce_numtgts = 0; + + rc = copy_ref_data(refs, numrefs, ce, th); + kfree(th); + + if (rc) + ce = ERR_PTR(rc); + + return ce; +} + +/* Update an expired cache entry by getting a new DFS referral from server */ +static struct dfs_cache_entry * +update_cache_entry(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_cache_entry *ce) +{ + int rc; + struct dfs_info3_param *refs = NULL; + int numrefs = 0; + + cifs_dbg(FYI, "%s: update expired cache entry\n", __func__); + /* + * Check if caller provided enough parameters to update an expired + * entry. + */ + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) + return ERR_PTR(-ETIME); + if (unlikely(!nls_codepage)) + return ERR_PTR(-ETIME); + + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, path); + + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, &numrefs, + nls_codepage, remap); + if (rc) + ce = ERR_PTR(rc); + else + ce = __update_cache_entry(path, refs, numrefs); + + dump_refs(refs, numrefs); + free_dfs_info_array(refs, numrefs); + + return ce; +} + +/* + * Find, create or update a DFS cache entry. + * + * If the entry wasn't found, it will create a new one. Or if it was found but + * expired, then it will update the entry accordingly. + * + * For interlinks, __cifs_dfs_mount() and expand_dfs_referral() are supposed to + * handle them properly. + */ +static struct dfs_cache_entry * +do_dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, bool noreq) +{ + int rc; + unsigned int h; + struct dfs_cache_entry *ce; + struct dfs_info3_param *nrefs; + int numnrefs; + + cifs_dbg(FYI, "%s: search path: %s\n", __func__, path); + + ce = find_cache_entry(path, &h); + if (IS_ERR(ce)) { + cifs_dbg(FYI, "%s: cache miss\n", __func__); + /* + * If @noreq is set, no requests will be sent to the server for + * either updating or getting a new DFS referral. + */ + if (noreq) + return ce; + /* + * No cache entry was found, so check for valid parameters that + * will be required to get a new DFS referral and then create a + * new cache entry. + */ + if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) { + ce = ERR_PTR(-ENOSYS); + return ce; + } + if (unlikely(!nls_codepage)) { + ce = ERR_PTR(-EINVAL); + return ce; + } + + nrefs = NULL; + numnrefs = 0; + + cifs_dbg(FYI, "%s: DFS referral request for %s\n", __func__, + path); + + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &nrefs, + &numnrefs, nls_codepage, + remap); + if (rc) { + ce = ERR_PTR(rc); + return ce; + } + + dump_refs(nrefs, numnrefs); + + cifs_dbg(FYI, "%s: new cache entry\n", __func__); + + if (dfs_cache_count >= DFS_CACHE_MAX_ENTRIES) { + cifs_dbg(FYI, "%s: reached max cache size (%d)", + __func__, DFS_CACHE_MAX_ENTRIES); + remove_oldest_entry(); + } + ce = add_cache_entry(h, path, nrefs, numnrefs); + free_dfs_info_array(nrefs, numnrefs); + + if (IS_ERR(ce)) + return ce; + + dfs_cache_count++; + } + + dump_ce(ce); + + /* Just return the found cache entry in case @noreq is set */ + if (noreq) + return ce; + + if (cache_entry_expired(ce)) { + cifs_dbg(FYI, "%s: expired cache entry\n", __func__); + ce = update_cache_entry(xid, ses, nls_codepage, remap, path, + ce); + if (IS_ERR(ce)) { + cifs_dbg(FYI, "%s: failed to update expired entry\n", + __func__); + } + } + return ce; +} + +/* Set up a new DFS referral from a given cache entry */ +static int setup_ref(const char *path, const struct dfs_cache_entry *ce, + struct dfs_info3_param *ref, const char *tgt) +{ + int rc; + + cifs_dbg(FYI, "%s: set up new ref\n", __func__); + + memset(ref, 0, sizeof(*ref)); + + ref->path_name = kstrndup(path, strlen(path), GFP_KERNEL); + if (!ref->path_name) + return -ENOMEM; + + ref->path_consumed = ce->ce_path_consumed; + + ref->node_name = kstrndup(tgt, strlen(tgt), GFP_KERNEL); + if (!ref->node_name) { + rc = -ENOMEM; + goto err_free_path; + } + + ref->ttl = ce->ce_ttl; + ref->server_type = ce->ce_srvtype; + ref->ref_flag = ce->ce_flags; + + return 0; + +err_free_path: + kfree(ref->path_name); + ref->path_name = NULL; + return rc; +} + +/* Return target list of a DFS cache entry */ +static int get_tgt_list(const struct dfs_cache_entry *ce, + struct dfs_cache_tgt_list *tl) +{ + int rc; + struct list_head *head = &tl->tl_list; + struct dfs_cache_tgt *t; + struct dfs_cache_tgt_iterator *it, *nit; + + memset(tl, 0, sizeof(*tl)); + INIT_LIST_HEAD(head); + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + it = kzalloc(sizeof(*it), GFP_KERNEL); + if (!it) { + rc = -ENOMEM; + goto err_free_it; + } + + it->it_name = kstrndup(t->t_name, strlen(t->t_name), + GFP_KERNEL); + if (!it->it_name) { + rc = -ENOMEM; + goto err_free_it; + } + + if (ce->ce_tgthint == t) + list_add(&it->it_list, head); + else + list_add_tail(&it->it_list, head); + } + tl->tl_numtgts = ce->ce_numtgts; + + return 0; + +err_free_it: + list_for_each_entry_safe(it, nit, head, it_list) { + kfree(it->it_name); + kfree(it); + } + return rc; +} + +/** + * dfs_cache_find - find a DFS cache entry + * + * If it doesn't find the cache entry, then it will get a DFS referral + * for @path and create a new entry. + * + * In case the cache entry exists but expired, it will get a DFS referral + * for @path and then update the respective cache entry. + * + * These parameters are passed down to the get_dfs_refer() call if it + * needs to be issued: + * @xid: syscall xid + * @ses: smb session to issue the request on + * @nls_codepage: charset conversion + * @remap: path character remapping type + * @path: path to lookup in DFS referral cache. + * + * @ref: when non-NULL, store single DFS referral result in it. + * @tgt_list: when non-NULL, store complete DFS target list in it. + * + * Return zero if the target was found, otherwise non-zero. + */ +int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); + if (!IS_ERR(ce)) { + if (ref) + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); + else + rc = 0; + if (!rc && tgt_list) + rc = get_tgt_list(ce, tgt_list); + } else { + rc = PTR_ERR(ce); + } + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_noreq_find - find a DFS cache entry without sending any requests to + * the currently connected server. + * + * NOTE: This function will neither update a cache entry in case it was + * expired, nor create a new cache entry if @path hasn't been found. It heavily + * relies on an existing cache entry. + * + * @path: path to lookup in the DFS referral cache. + * @ref: when non-NULL, store single DFS referral result in it. + * @tgt_list: when non-NULL, store complete DFS target list in it. + * + * Return 0 if successful. + * Return -ENOENT if the entry was not found. + * Return non-zero for other errors. + */ +int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + if (ref) + rc = setup_ref(path, ce, ref, get_tgt_name(ce)); + else + rc = 0; + if (!rc && tgt_list) + rc = get_tgt_list(ce, tgt_list); +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_update_tgthint - update target hint of a DFS cache entry + * + * If it doesn't find the cache entry, then it will get a DFS referral for @path + * and create a new entry. + * + * In case the cache entry exists but expired, it will get a DFS referral + * for @path and then update the respective cache entry. + * + * @xid: syscall id + * @ses: smb session + * @nls_codepage: charset conversion + * @remap: type of character remapping for paths + * @path: path to lookup in DFS referral cache. + * @it: DFS target iterator + * + * Return zero if the target hint was updated successfully, otherwise non-zero. + */ +int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, + const struct dfs_cache_tgt_iterator *it) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + ce = do_dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + rc = 0; + + t = ce->ce_tgthint; + + if (likely(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strncasecmp(t->t_name, it->it_name, strlen(it->it_name))) { + ce->ce_tgthint = t; + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, + it->it_name); + break; + } + } + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry + * without sending any requests to the currently connected server. + * + * NOTE: This function will neither update a cache entry in case it was + * expired, nor create a new cache entry if @path hasn't been found. It heavily + * relies on an existing cache entry. + * + * @path: path to lookup in DFS referral cache. + * @it: target iterator which contains the target hint to update the cache + * entry with. + * + * Return zero if the target hint was updated successfully, otherwise non-zero. + */ +int dfs_cache_noreq_update_tgthint(const char *path, + const struct dfs_cache_tgt_iterator *it) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + struct dfs_cache_tgt *t; + + if (unlikely(!is_path_valid(path)) || !it) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + + ce = do_dfs_cache_find(0, NULL, NULL, 0, npath, true); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + rc = 0; + + t = ce->ce_tgthint; + + if (unlikely(!strncasecmp(it->it_name, t->t_name, strlen(t->t_name)))) + goto out; + + list_for_each_entry(t, &ce->ce_tlist, t_list) { + if (!strncasecmp(t->t_name, it->it_name, strlen(it->it_name))) { + ce->ce_tgthint = t; + cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, + it->it_name); + break; + } + } + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +/** + * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given + * target iterator (@it). + * + * @path: path to lookup in DFS referral cache. + * @it: DFS target iterator. + * @ref: DFS referral pointer to set up the gathered information. + * + * Return zero if the DFS referral was set up correctly, otherwise non-zero. + */ +int dfs_cache_get_tgt_referral(const char *path, + const struct dfs_cache_tgt_iterator *it, + struct dfs_info3_param *ref) +{ + int rc; + char *npath; + struct dfs_cache_entry *ce; + unsigned int h; + + if (!it || !ref) + return -EINVAL; + if (unlikely(!is_path_valid(path))) + return -EINVAL; + + rc = get_normalized_path(path, &npath); + if (rc) + return rc; + + cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); + + mutex_lock(&dfs_cache_list_lock); + + ce = find_cache_entry(npath, &h); + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name); + + rc = setup_ref(path, ce, ref, it->it_name); + +out: + mutex_unlock(&dfs_cache_list_lock); + free_normalized_path(path, npath); + return rc; +} + +static int dup_vol(struct smb_vol *vol, struct smb_vol *new) +{ + memcpy(new, vol, sizeof(*new)); + + if (vol->username) { + new->username = kstrndup(vol->username, strlen(vol->username), + GFP_KERNEL); + if (!new->username) + return -ENOMEM; + } + if (vol->password) { + new->password = kstrndup(vol->password, strlen(vol->password), + GFP_KERNEL); + if (!new->password) + goto err_free_username; + } + if (vol->UNC) { + cifs_dbg(FYI, "%s: vol->UNC: %s\n", __func__, vol->UNC); + new->UNC = kstrndup(vol->UNC, strlen(vol->UNC), GFP_KERNEL); + if (!new->UNC) + goto err_free_password; + } + if (vol->domainname) { + new->domainname = kstrndup(vol->domainname, + strlen(vol->domainname), GFP_KERNEL); + if (!new->domainname) + goto err_free_unc; + } + if (vol->iocharset) { + new->iocharset = kstrndup(vol->iocharset, + strlen(vol->iocharset), GFP_KERNEL); + if (!new->iocharset) + goto err_free_domainname; + } + if (vol->prepath) { + cifs_dbg(FYI, "%s: vol->prepath: %s\n", __func__, vol->prepath); + new->prepath = kstrndup(vol->prepath, strlen(vol->prepath), + GFP_KERNEL); + if (!new->prepath) + goto err_free_iocharset; + } + + return 0; + +err_free_iocharset: + kfree(new->iocharset); +err_free_domainname: + kfree(new->domainname); +err_free_unc: + kfree(new->UNC); +err_free_password: + kfree(new->password); +err_free_username: + kfree(new->username); + kfree(new); + return -ENOMEM; +} + +/** + * dfs_cache_add_vol - add a cifs volume during mount() that will be handled by + * DFS cache refresh worker. + * + * @vol: cifs volume. + * @fullpath: origin full path. + * + * Return zero if volume was set up correctly, otherwise non-zero. + */ +int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath) +{ + int rc; + struct dfs_cache_vol_info *vi; + + if (!vol || !fullpath) + return -EINVAL; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + vi = kzalloc(sizeof(*vi), GFP_KERNEL); + if (!vi) + return -ENOMEM; + + vi->vi_fullpath = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); + if (!vi->vi_fullpath) { + rc = -ENOMEM; + goto err_free_vi; + } + + rc = dup_vol(vol, &vi->vi_vol); + if (rc) + goto err_free_fullpath; + + mutex_lock(&dfs_cache.dc_lock); + list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list); + mutex_unlock(&dfs_cache.dc_lock); + return 0; + +err_free_fullpath: + kfree(vi->vi_fullpath); +err_free_vi: + kfree(vi); + return rc; +} + +static inline struct dfs_cache_vol_info *find_vol(const char *fullpath) +{ + struct dfs_cache_vol_info *vi; + + list_for_each_entry(vi, &dfs_cache.dc_vol_list, vi_list) { + cifs_dbg(FYI, "%s: vi->vi_fullpath: %s\n", __func__, + vi->vi_fullpath); + if (!strncasecmp(vi->vi_fullpath, fullpath, + strlen(vi->vi_fullpath))) + return vi; + } + return ERR_PTR(-ENOENT); +} + +/** + * dfs_cache_update_vol - update vol info in DFS cache after failover + * + * @fullpath: fullpath to look up in volume list. + * @server: TCP ses pointer. + * + * Return zero if volume was updated, otherwise non-zero. + */ +int dfs_cache_update_vol(const char *fullpath, struct TCP_Server_Info *server) +{ + int rc; + struct dfs_cache_vol_info *vi; + + if (!fullpath || !server) + return -EINVAL; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + mutex_lock(&dfs_cache.dc_lock); + + vi = find_vol(fullpath); + if (IS_ERR(vi)) { + rc = PTR_ERR(vi); + goto out; + } + + cifs_dbg(FYI, "%s: updating volume info\n", __func__); + memcpy(&vi->vi_vol.dstaddr, &server->dstaddr, + sizeof(vi->vi_vol.dstaddr)); + rc = 0; + +out: + mutex_unlock(&dfs_cache.dc_lock); + return rc; +} + +/** + * dfs_cache_del_vol - remove volume info in DFS cache during umount() + * + * @fullpath: fullpath to look up in volume list. + * + * Return zero if volume was deleted, otherwise non-zero. + */ +void dfs_cache_del_vol(const char *fullpath) +{ + struct dfs_cache_vol_info *vi; + + if (!fullpath || !*fullpath) + return; + + cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); + + mutex_lock(&dfs_cache.dc_lock); + vi = find_vol(fullpath); + if (!IS_ERR(vi)) + free_vol(vi); + mutex_unlock(&dfs_cache.dc_lock); +} + +/* Get all tcons that are within a DFS namespace and can be refreshed */ +static void get_tcons(struct TCP_Server_Info *server, struct list_head *head) +{ + struct cifs_ses *ses; + struct cifs_tcon *tcon; + + INIT_LIST_HEAD(head); + + spin_lock(&cifs_tcp_ses_lock); + list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { + list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { + if (!tcon->need_reconnect && !tcon->need_reopen_files && + tcon->dfs_path) { + tcon->tc_count++; + list_add_tail(&tcon->ulist, head); + } + } + if (ses->tcon_ipc && !ses->tcon_ipc->need_reconnect && + ses->tcon_ipc->dfs_path) { + list_add_tail(&ses->tcon_ipc->ulist, head); + } + } + spin_unlock(&cifs_tcp_ses_lock); +} + +/* Refresh DFS cache entry from a given tcon */ +static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon) +{ + int rc = 0; + unsigned int xid; + char *path, *npath; + unsigned int h; + struct dfs_cache_entry *ce; + struct dfs_info3_param *refs = NULL; + int numrefs = 0; + + xid = get_xid(); + + path = tcon->dfs_path + 1; + + cifs_dbg(FYI, "%s: dfs path: %s\n", __func__, path); + + rc = get_normalized_path(path, &npath); + if (rc) + goto out; + + mutex_lock(&dfs_cache_list_lock); + ce = find_cache_entry(npath, &h); + mutex_unlock(&dfs_cache_list_lock); + + if (IS_ERR(ce)) { + rc = PTR_ERR(ce); + goto out; + } + + if (!cache_entry_expired(ce)) + goto out; + + if (unlikely(!tcon->ses->server->ops->get_dfs_refer)) { + rc = -ENOSYS; + } else { + rc = tcon->ses->server->ops->get_dfs_refer(xid, tcon->ses, path, + &refs, &numrefs, + dc->dc_nlsc, + tcon->remap); + if (!rc) { + mutex_lock(&dfs_cache_list_lock); + ce = __update_cache_entry(npath, refs, numrefs); + mutex_unlock(&dfs_cache_list_lock); + dump_refs(refs, numrefs); + free_dfs_info_array(refs, numrefs); + if (IS_ERR(ce)) + rc = PTR_ERR(ce); + } + } + if (rc) + cifs_dbg(FYI, "%s: failed to update expired entry\n", __func__); +out: + free_xid(xid); + free_normalized_path(path, npath); +} + +/* + * Worker that will refresh DFS cache based on lowest TTL value from a DFS + * referral. + * + * FIXME: ensure that all requests are sent to DFS root for refreshing the + * cache. + */ +static void refresh_cache_worker(struct work_struct *work) +{ + struct dfs_cache *dc = container_of(work, struct dfs_cache, + dc_refresh.work); + struct dfs_cache_vol_info *vi; + struct TCP_Server_Info *server; + LIST_HEAD(list); + struct cifs_tcon *tcon, *ntcon; + + cifs_dbg(FYI, "%s: refreshing DFS referral cache\n", __func__); + + mutex_lock(&dc->dc_lock); + + list_for_each_entry(vi, &dc->dc_vol_list, vi_list) { + cifs_dbg(FYI, "%s: vol path: %s\n", __func__, vi->vi_fullpath); + server = cifs_find_tcp_session(&vi->vi_vol); + if (!server) + continue; + cifs_dbg(FYI, "%s: found a tcp ses: %p\n", __func__, server); + if (server->tcpStatus != CifsGood) + goto next; + get_tcons(server, &list); + list_for_each_entry_safe(tcon, ntcon, &list, ulist) { + do_refresh_tcon(dc, tcon); + list_del_init(&tcon->ulist); + cifs_put_tcon(tcon); + } +next: + cifs_put_tcp_session(server, 0); + } + queue_delayed_work(cifsiod_wq, &dc->dc_refresh, dc->dc_ttl * HZ); + mutex_unlock(&dc->dc_lock); + + cifs_dbg(FYI, "%s: finished refreshing DFS referral cache\n", __func__); +} diff --git a/fs/cifs/dfs_cache.h b/fs/cifs/dfs_cache.h new file mode 100644 index 000000000000..22f366514f3a --- /dev/null +++ b/fs/cifs/dfs_cache.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * DFS referral cache routines + * + * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> + */ + +#ifndef _CIFS_DFS_CACHE_H +#define _CIFS_DFS_CACHE_H + +#include <linux/nls.h> +#include <linux/list.h> +#include "cifsglob.h" + +struct dfs_cache_tgt_list { + int tl_numtgts; + struct list_head tl_list; +}; + +struct dfs_cache_tgt_iterator { + char *it_name; + struct list_head it_list; +}; + +extern int dfs_cache_init(void); +extern void dfs_cache_destroy(void); +extern const struct file_operations dfscache_proc_fops; + +extern int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, + const struct nls_table *nls_codepage, int remap, + const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list); +extern int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref, + struct dfs_cache_tgt_list *tgt_list); +extern int dfs_cache_update_tgthint(const unsigned int xid, + struct cifs_ses *ses, + const struct nls_table *nls_codepage, + int remap, const char *path, + const struct dfs_cache_tgt_iterator *it); +extern int +dfs_cache_noreq_update_tgthint(const char *path, + const struct dfs_cache_tgt_iterator *it); +extern int dfs_cache_get_tgt_referral(const char *path, + const struct dfs_cache_tgt_iterator *it, + struct dfs_info3_param *ref); +extern int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath); +extern int dfs_cache_update_vol(const char *fullpath, + struct TCP_Server_Info *server); +extern void dfs_cache_del_vol(const char *fullpath); + +static inline struct dfs_cache_tgt_iterator * +dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, + struct dfs_cache_tgt_iterator *it) +{ + if (!tl || list_empty(&tl->tl_list) || !it || + list_is_last(&it->it_list, &tl->tl_list)) + return NULL; + return list_next_entry(it, it_list); +} + +static inline struct dfs_cache_tgt_iterator * +dfs_cache_get_tgt_iterator(struct dfs_cache_tgt_list *tl) +{ + if (!tl) + return NULL; + return list_first_entry_or_null(&tl->tl_list, + struct dfs_cache_tgt_iterator, + it_list); +} + +static inline void dfs_cache_free_tgts(struct dfs_cache_tgt_list *tl) +{ + struct dfs_cache_tgt_iterator *it, *nit; + + if (!tl || list_empty(&tl->tl_list)) + return; + list_for_each_entry_safe(it, nit, &tl->tl_list, it_list) { + list_del(&it->it_list); + kfree(it->it_name); + kfree(it); + } + tl->tl_numtgts = 0; +} + +static inline const char * +dfs_cache_get_tgt_name(const struct dfs_cache_tgt_iterator *it) +{ + return it ? it->it_name : NULL; +} + +static inline int +dfs_cache_get_nr_tgts(const struct dfs_cache_tgt_list *tl) +{ + return tl ? tl->tl_numtgts : 0; +} + +#endif /* _CIFS_DFS_CACHE_H */