diff options
Diffstat (limited to 'fs/cifs/dfs_cache.c')
| -rw-r--r-- | fs/cifs/dfs_cache.c | 1578 |
1 files changed, 0 insertions, 1578 deletions
diff --git a/fs/cifs/dfs_cache.c b/fs/cifs/dfs_cache.c deleted file mode 100644 index df81c718d2fa..000000000000 --- a/fs/cifs/dfs_cache.c +++ /dev/null @@ -1,1578 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * DFS referral cache routines - * - * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de> - */ - -#include <linux/jhash.h> -#include <linux/ktime.h> -#include <linux/slab.h> -#include <linux/proc_fs.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 CACHE_HTABLE_SIZE 32 -#define CACHE_MAX_ENTRIES 64 - -#define IS_INTERLINK_SET(v) ((v) & (DFSREF_REFERRAL_SERVER | \ - DFSREF_STORAGE_SERVER)) - -struct cache_dfs_tgt { - char *name; - struct list_head list; -}; - -struct cache_entry { - struct hlist_node hlist; - const char *path; - int ttl; - int srvtype; - int flags; - struct timespec64 etime; - int path_consumed; - int numtgts; - struct list_head tlist; - struct cache_dfs_tgt *tgthint; -}; - -struct vol_info { - char *fullpath; - spinlock_t smb_vol_lock; - struct smb_vol smb_vol; - char *mntdata; - struct list_head list; - struct list_head rlist; - struct kref refcnt; -}; - -static struct kmem_cache *cache_slab __read_mostly; -static struct workqueue_struct *dfscache_wq __read_mostly; - -static int cache_ttl; -static DEFINE_SPINLOCK(cache_ttl_lock); - -static struct nls_table *cache_nlsc; - -/* - * Number of entries in the cache - */ -static atomic_t cache_count; - -static struct hlist_head cache_htable[CACHE_HTABLE_SIZE]; -static DECLARE_RWSEM(htable_rw_lock); - -static LIST_HEAD(vol_list); -static DEFINE_SPINLOCK(vol_list_lock); - -static void refresh_cache_worker(struct work_struct *work); - -static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker); - -static int get_normalized_path(const char *path, char **npath) -{ - if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/')) - return -EINVAL; - - 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 cache_entry *ce) -{ - struct timespec64 ts; - - ktime_get_coarse_real_ts64(&ts); - return timespec64_compare(&ts, &ce->etime) >= 0; -} - -static inline void free_tgts(struct cache_entry *ce) -{ - struct cache_dfs_tgt *t, *n; - - list_for_each_entry_safe(t, n, &ce->tlist, list) { - list_del(&t->list); - kfree(t->name); - kfree(t); - } -} - -static inline void flush_cache_ent(struct cache_entry *ce) -{ - hlist_del_init(&ce->hlist); - kfree(ce->path); - free_tgts(ce); - atomic_dec(&cache_count); - kmem_cache_free(cache_slab, ce); -} - -static void flush_cache_ents(void) -{ - int i; - - for (i = 0; i < CACHE_HTABLE_SIZE; i++) { - struct hlist_head *l = &cache_htable[i]; - struct hlist_node *n; - struct cache_entry *ce; - - hlist_for_each_entry_safe(ce, n, l, hlist) { - if (!hlist_unhashed(&ce->hlist)) - flush_cache_ent(ce); - } - } -} - -/* - * dfs cache /proc file - */ -static int dfscache_proc_show(struct seq_file *m, void *v) -{ - int i; - struct cache_entry *ce; - struct cache_dfs_tgt *t; - - seq_puts(m, "DFS cache\n---------\n"); - - down_read(&htable_rw_lock); - for (i = 0; i < CACHE_HTABLE_SIZE; i++) { - struct hlist_head *l = &cache_htable[i]; - - hlist_for_each_entry(ce, l, hlist) { - if (hlist_unhashed(&ce->hlist)) - continue; - - seq_printf(m, - "cache entry: path=%s,type=%s,ttl=%d,etime=%ld," - "interlink=%s,path_consumed=%d,expired=%s\n", - ce->path, - ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", - ce->ttl, ce->etime.tv_nsec, - IS_INTERLINK_SET(ce->flags) ? "yes" : "no", - ce->path_consumed, - cache_entry_expired(ce) ? "yes" : "no"); - - list_for_each_entry(t, &ce->tlist, list) { - seq_printf(m, " %s%s\n", - t->name, - ce->tgthint == t ? " (target hint)" : ""); - } - } - } - up_read(&htable_rw_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\n"); - - down_write(&htable_rw_lock); - flush_cache_ents(); - up_write(&htable_rw_lock); - - return count; -} - -static int dfscache_proc_open(struct inode *inode, struct file *file) -{ - return single_open(file, dfscache_proc_show, NULL); -} - -const struct proc_ops dfscache_proc_ops = { - .proc_open = dfscache_proc_open, - .proc_read = seq_read, - .proc_lseek = seq_lseek, - .proc_release = single_release, - .proc_write = dfscache_proc_write, -}; - -#ifdef CONFIG_CIFS_DEBUG2 -static inline void dump_tgts(const struct cache_entry *ce) -{ - struct cache_dfs_tgt *t; - - cifs_dbg(FYI, "target list:\n"); - list_for_each_entry(t, &ce->tlist, list) { - cifs_dbg(FYI, " %s%s\n", t->name, - ce->tgthint == t ? " (target hint)" : ""); - } -} - -static inline void dump_ce(const struct 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->path, - ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, - ce->etime.tv_nsec, - IS_INTERLINK_SET(ce->flags) ? "yes" : "no", - 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 rc; - int i; - - dfscache_wq = alloc_workqueue("cifs-dfscache", - WQ_FREEZABLE | WQ_MEM_RECLAIM, 1); - if (!dfscache_wq) - return -ENOMEM; - - cache_slab = kmem_cache_create("cifs_dfs_cache", - sizeof(struct cache_entry), 0, - SLAB_HWCACHE_ALIGN, NULL); - if (!cache_slab) { - rc = -ENOMEM; - goto out_destroy_wq; - } - - for (i = 0; i < CACHE_HTABLE_SIZE; i++) - INIT_HLIST_HEAD(&cache_htable[i]); - - atomic_set(&cache_count, 0); - cache_nlsc = load_nls_default(); - - cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); - return 0; - -out_destroy_wq: - destroy_workqueue(dfscache_wq); - return rc; -} - -static inline unsigned int cache_entry_hash(const void *data, int size) -{ - unsigned int h; - - h = jhash(data, size, 0); - return h & (CACHE_HTABLE_SIZE - 1); -} - -/* Check whether second path component of @path is SYSVOL or NETLOGON */ -static inline bool is_sysvol_or_netlogon(const char *path) -{ - const char *s; - char sep = path[0]; - - s = strchr(path + 1, sep) + 1; - return !strncasecmp(s, "sysvol", strlen("sysvol")) || - !strncasecmp(s, "netlogon", strlen("netlogon")); -} - -/* Return target hint of a DFS cache entry */ -static inline char *get_tgt_name(const struct cache_entry *ce) -{ - struct cache_dfs_tgt *t = ce->tgthint; - - return 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, - }; - struct timespec64 now; - - ktime_get_coarse_real_ts64(&now); - return timespec64_add(now, ts); -} - -/* Allocate a new DFS target */ -static struct cache_dfs_tgt *alloc_target(const char *name) -{ - struct cache_dfs_tgt *t; - - t = kmalloc(sizeof(*t), GFP_ATOMIC); - if (!t) - return ERR_PTR(-ENOMEM); - t->name = kstrndup(name, strlen(name), GFP_ATOMIC); - if (!t->name) { - kfree(t); - return ERR_PTR(-ENOMEM); - } - INIT_LIST_HEAD(&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 cache_entry *ce, const char *tgthint) -{ - int i; - - ce->ttl = refs[0].ttl; - ce->etime = get_expire_time(ce->ttl); - ce->srvtype = refs[0].server_type; - ce->flags = refs[0].ref_flag; - ce->path_consumed = refs[0].path_consumed; - - for (i = 0; i < numrefs; i++) { - struct cache_dfs_tgt *t; - - t = alloc_target(refs[i].node_name); - if (IS_ERR(t)) { - free_tgts(ce); - return PTR_ERR(t); - } - if (tgthint && !strcasecmp(t->name, tgthint)) { - list_add(&t->list, &ce->tlist); - tgthint = NULL; - } else { - list_add_tail(&t->list, &ce->tlist); - } - ce->numtgts++; - } - - ce->tgthint = list_first_entry_or_null(&ce->tlist, - struct cache_dfs_tgt, list); - - return 0; -} - -/* Allocate a new cache entry */ -static struct cache_entry *alloc_cache_entry(const char *path, - const struct dfs_info3_param *refs, - int numrefs) -{ - struct cache_entry *ce; - int rc; - - ce = kmem_cache_zalloc(cache_slab, GFP_KERNEL); - if (!ce) - return ERR_PTR(-ENOMEM); - - ce->path = kstrndup(path, strlen(path), GFP_KERNEL); - if (!ce->path) { - kmem_cache_free(cache_slab, ce); - return ERR_PTR(-ENOMEM); - } - INIT_HLIST_NODE(&ce->hlist); - INIT_LIST_HEAD(&ce->tlist); - - rc = copy_ref_data(refs, numrefs, ce, NULL); - if (rc) { - kfree(ce->path); - kmem_cache_free(cache_slab, ce); - ce = ERR_PTR(rc); - } - return ce; -} - -/* Must be called with htable_rw_lock held */ -static void remove_oldest_entry(void) -{ - int i; - struct cache_entry *ce; - struct cache_entry *to_del = NULL; - - for (i = 0; i < CACHE_HTABLE_SIZE; i++) { - struct hlist_head *l = &cache_htable[i]; - - hlist_for_each_entry(ce, l, hlist) { - if (hlist_unhashed(&ce->hlist)) - continue; - if (!to_del || timespec64_compare(&ce->etime, - &to_del->etime) < 0) - to_del = ce; - } - } - - if (!to_del) { - cifs_dbg(FYI, "%s: no entry to remove\n", __func__); - return; - } - - cifs_dbg(FYI, "%s: removing entry\n", __func__); - dump_ce(to_del); - flush_cache_ent(to_del); -} - -/* Add a new DFS cache entry */ -static int add_cache_entry(const char *path, unsigned int hash, - struct dfs_info3_param *refs, int numrefs) -{ - struct cache_entry *ce; - - ce = alloc_cache_entry(path, refs, numrefs); - if (IS_ERR(ce)) - return PTR_ERR(ce); - - spin_lock(&cache_ttl_lock); - if (!cache_ttl) { - cache_ttl = ce->ttl; - queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ); - } else { - cache_ttl = min_t(int, cache_ttl, ce->ttl); - mod_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ); - } - spin_unlock(&cache_ttl_lock); - - down_write(&htable_rw_lock); - hlist_add_head(&ce->hlist, &cache_htable[hash]); - dump_ce(ce); - up_write(&htable_rw_lock); - - return 0; -} - -/* - * Find a DFS cache entry in hash table and optionally check prefix path against - * @path. - * Use whole path components in the match. - * Must be called with htable_rw_lock held. - * - * Return ERR_PTR(-ENOENT) if the entry is not found. - */ -static struct cache_entry *lookup_cache_entry(const char *path, - unsigned int *hash) -{ - struct cache_entry *ce; - unsigned int h; - bool found = false; - - h = cache_entry_hash(path, strlen(path)); - - hlist_for_each_entry(ce, &cache_htable[h], hlist) { - if (!strcasecmp(path, ce->path)) { - found = true; - dump_ce(ce); - break; - } - } - - if (!found) - ce = ERR_PTR(-ENOENT); - if (hash) - *hash = h; - - return ce; -} - -static void __vol_release(struct vol_info *vi) -{ - kfree(vi->fullpath); - kfree(vi->mntdata); - cifs_cleanup_volume_info_contents(&vi->smb_vol); - kfree(vi); -} - -static void vol_release(struct kref *kref) -{ - struct vol_info *vi = container_of(kref, struct vol_info, refcnt); - - spin_lock(&vol_list_lock); - list_del(&vi->list); - spin_unlock(&vol_list_lock); - __vol_release(vi); -} - -static inline void free_vol_list(void) -{ - struct vol_info *vi, *nvi; - - list_for_each_entry_safe(vi, nvi, &vol_list, list) { - list_del_init(&vi->list); - __vol_release(vi); - } -} - -/** - * dfs_cache_destroy - destroy DFS referral cache - */ -void dfs_cache_destroy(void) -{ - cancel_delayed_work_sync(&refresh_task); - unload_nls(cache_nlsc); - free_vol_list(); - flush_cache_ents(); - kmem_cache_destroy(cache_slab); - destroy_workqueue(dfscache_wq); - - cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__); -} - -/* Must be called with htable_rw_lock held */ -static int __update_cache_entry(const char *path, - const struct dfs_info3_param *refs, - int numrefs) -{ - int rc; - struct cache_entry *ce; - char *s, *th = NULL; - - ce = lookup_cache_entry(path, NULL); - if (IS_ERR(ce)) - return PTR_ERR(ce); - - if (ce->tgthint) { - s = ce->tgthint->name; - th = kstrndup(s, strlen(s), GFP_ATOMIC); - if (!th) - return -ENOMEM; - } - - free_tgts(ce); - ce->numtgts = 0; - - rc = copy_ref_data(refs, numrefs, ce, th); - - kfree(th); - - return rc; -} - -static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, - const struct nls_table *nls_codepage, int remap, - const char *path, struct dfs_info3_param **refs, - int *numrefs) -{ - cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path); - - if (!ses || !ses->server || !ses->server->ops->get_dfs_refer) - return -EOPNOTSUPP; - if (unlikely(!nls_codepage)) - return -EINVAL; - - *refs = NULL; - *numrefs = 0; - - return ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, - nls_codepage, remap); -} - -/* Update an expired cache entry by getting a new DFS referral from server */ -static int update_cache_entry(const char *path, - const struct dfs_info3_param *refs, - int numrefs) -{ - - int rc; - - down_write(&htable_rw_lock); - rc = __update_cache_entry(path, refs, numrefs); - up_write(&htable_rw_lock); - - return rc; -} - -/* - * 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 int __dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, - const struct nls_table *nls_codepage, int remap, - const char *path, bool noreq) -{ - int rc; - unsigned int hash; - struct cache_entry *ce; - struct dfs_info3_param *refs = NULL; - int numrefs = 0; - bool newent = false; - - cifs_dbg(FYI, "%s: search path: %s\n", __func__, path); - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(path, &hash); - - /* - * If @noreq is set, no requests will be sent to the server. Just return - * the cache entry. - */ - if (noreq) { - up_read(&htable_rw_lock); - return PTR_ERR_OR_ZERO(ce); - } - - if (!IS_ERR(ce)) { - if (!cache_entry_expired(ce)) { - dump_ce(ce); - up_read(&htable_rw_lock); - return 0; - } - } else { - newent = true; - } - - up_read(&htable_rw_lock); - - /* - * No entry was found. - * - * Request a new DFS referral in order to create a new cache entry, or - * updating an existing one. - */ - rc = get_dfs_referral(xid, ses, nls_codepage, remap, path, - &refs, &numrefs); - if (rc) - return rc; - - dump_refs(refs, numrefs); - - if (!newent) { - rc = update_cache_entry(path, refs, numrefs); - goto out_free_refs; - } - - if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) { - cifs_dbg(FYI, "%s: reached max cache size (%d)\n", - __func__, CACHE_MAX_ENTRIES); - down_write(&htable_rw_lock); - remove_oldest_entry(); - up_write(&htable_rw_lock); - } - - rc = add_cache_entry(path, hash, refs, numrefs); - if (!rc) - atomic_inc(&cache_count); - -out_free_refs: - free_dfs_info_array(refs, numrefs); - return rc; -} - -/* - * Set up a DFS referral from a given cache entry. - * - * Must be called with htable_rw_lock held. - */ -static int setup_referral(const char *path, struct cache_entry *ce, - struct dfs_info3_param *ref, const char *target) -{ - 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_ATOMIC); - if (!ref->path_name) - return -ENOMEM; - - ref->node_name = kstrndup(target, strlen(target), GFP_ATOMIC); - if (!ref->node_name) { - rc = -ENOMEM; - goto err_free_path; - } - - ref->path_consumed = ce->path_consumed; - ref->ttl = ce->ttl; - ref->server_type = ce->srvtype; - ref->ref_flag = 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_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) -{ - int rc; - struct list_head *head = &tl->tl_list; - struct cache_dfs_tgt *t; - struct dfs_cache_tgt_iterator *it, *nit; - - memset(tl, 0, sizeof(*tl)); - INIT_LIST_HEAD(head); - - list_for_each_entry(t, &ce->tlist, list) { - it = kzalloc(sizeof(*it), GFP_ATOMIC); - if (!it) { - rc = -ENOMEM; - goto err_free_it; - } - - it->it_name = kstrndup(t->name, strlen(t->name), GFP_ATOMIC); - if (!it->it_name) { - kfree(it); - rc = -ENOMEM; - goto err_free_it; - } - - if (ce->tgthint == t) - list_add(&it->it_list, head); - else - list_add_tail(&it->it_list, head); - } - - tl->tl_numtgts = 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 cache_entry *ce; - - rc = get_normalized_path(path, &npath); - if (rc) - return rc; - - rc = __dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); - if (rc) - goto out_free_path; - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - up_read(&htable_rw_lock); - rc = PTR_ERR(ce); - goto out_free_path; - } - - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else - rc = 0; - if (!rc && tgt_list) - rc = get_targets(ce, tgt_list); - - up_read(&htable_rw_lock); - -out_free_path: - 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 cache_entry *ce; - - rc = get_normalized_path(path, &npath); - if (rc) - return rc; - - cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); - goto out_unlock; - } - - if (ref) - rc = setup_referral(path, ce, ref, get_tgt_name(ce)); - else - rc = 0; - if (!rc && tgt_list) - rc = get_targets(ce, tgt_list); - -out_unlock: - up_read(&htable_rw_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 cache_entry *ce; - struct cache_dfs_tgt *t; - - rc = get_normalized_path(path, &npath); - if (rc) - return rc; - - cifs_dbg(FYI, "%s: update target hint - path: %s\n", __func__, npath); - - rc = __dfs_cache_find(xid, ses, nls_codepage, remap, npath, false); - if (rc) - goto out_free_path; - - down_write(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); - goto out_unlock; - } - - t = ce->tgthint; - - if (likely(!strcasecmp(it->it_name, t->name))) - goto out_unlock; - - list_for_each_entry(t, &ce->tlist, list) { - if (!strcasecmp(t->name, it->it_name)) { - ce->tgthint = t; - cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, - it->it_name); - break; - } - } - -out_unlock: - up_write(&htable_rw_lock); -out_free_path: - 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 cache_entry *ce; - struct cache_dfs_tgt *t; - - if (!it) - return -EINVAL; - - rc = get_normalized_path(path, &npath); - if (rc) - return rc; - - cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); - - down_write(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); - goto out_unlock; - } - - rc = 0; - t = ce->tgthint; - - if (unlikely(!strcasecmp(it->it_name, t->name))) - goto out_unlock; - - list_for_each_entry(t, &ce->tlist, list) { - if (!strcasecmp(t->name, it->it_name)) { - ce->tgthint = t; - cifs_dbg(FYI, "%s: new target hint: %s\n", __func__, - it->it_name); - break; - } - } - -out_unlock: - up_write(&htable_rw_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 cache_entry *ce; - - if (!it || !ref) - return -EINVAL; - - rc = get_normalized_path(path, &npath); - if (rc) - return rc; - - cifs_dbg(FYI, "%s: path: %s\n", __func__, npath); - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); - goto out_unlock; - } - - cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name); - - rc = setup_referral(path, ce, ref, it->it_name); - -out_unlock: - up_read(&htable_rw_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: - kzfree(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. - * - * @mntdata: mount data. - * @vol: cifs volume. - * @fullpath: origin full path. - * - * Return zero if volume was set up correctly, otherwise non-zero. - */ -int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, const char *fullpath) -{ - int rc; - struct vol_info *vi; - - if (!vol || !fullpath || !mntdata) - return -EINVAL; - - cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); - - vi = kzalloc(sizeof(*vi), GFP_KERNEL); - if (!vi) - return -ENOMEM; - - vi->fullpath = kstrndup(fullpath, strlen(fullpath), GFP_KERNEL); - if (!vi->fullpath) { - rc = -ENOMEM; - goto err_free_vi; - } - - rc = dup_vol(vol, &vi->smb_vol); - if (rc) - goto err_free_fullpath; - - vi->mntdata = mntdata; - spin_lock_init(&vi->smb_vol_lock); - kref_init(&vi->refcnt); - - spin_lock(&vol_list_lock); - list_add_tail(&vi->list, &vol_list); - spin_unlock(&vol_list_lock); - - return 0; - -err_free_fullpath: - kfree(vi->fullpath); -err_free_vi: - kfree(vi); - return rc; -} - -/* Must be called with vol_list_lock held */ -static struct vol_info *find_vol(const char *fullpath) -{ - struct vol_info *vi; - - list_for_each_entry(vi, &vol_list, list) { - cifs_dbg(FYI, "%s: vi->fullpath: %s\n", __func__, vi->fullpath); - if (!strcasecmp(vi->fullpath, fullpath)) - return vi; - } - return ERR_PTR(-ENOENT); -} - -/** - * 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) -{ - struct vol_info *vi; - - if (!fullpath || !server) - return -EINVAL; - - cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); - - spin_lock(&vol_list_lock); - vi = find_vol(fullpath); - if (IS_ERR(vi)) { - spin_unlock(&vol_list_lock); - return PTR_ERR(vi); - } - kref_get(&vi->refcnt); - spin_unlock(&vol_list_lock); - - cifs_dbg(FYI, "%s: updating volume info\n", __func__); - spin_lock(&vi->smb_vol_lock); - memcpy(&vi->smb_vol.dstaddr, &server->dstaddr, - sizeof(vi->smb_vol.dstaddr)); - spin_unlock(&vi->smb_vol_lock); - - kref_put(&vi->refcnt, vol_release); - - return 0; -} - -/** - * dfs_cache_del_vol - remove volume info in DFS cache during umount() - * - * @fullpath: fullpath to look up in volume list. - */ -void dfs_cache_del_vol(const char *fullpath) -{ - struct vol_info *vi; - - if (!fullpath || !*fullpath) - return; - - cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); - - spin_lock(&vol_list_lock); - vi = find_vol(fullpath); - spin_unlock(&vol_list_lock); - - kref_put(&vi->refcnt, vol_release); -} - -/** - * dfs_cache_get_tgt_share - parse a DFS target - * - * @it: DFS target iterator. - * @share: tree name. - * @share_len: length of tree name. - * @prefix: prefix path. - * @prefix_len: length of prefix path. - * - * Return zero if target was parsed correctly, otherwise non-zero. - */ -int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, - const char **share, size_t *share_len, - const char **prefix, size_t *prefix_len) -{ - char *s, sep; - - if (!it || !share || !share_len || !prefix || !prefix_len) - return -EINVAL; - - sep = it->it_name[0]; - if (sep != '\\' && sep != '/') - return -EINVAL; - - s = strchr(it->it_name + 1, sep); - if (!s) - return -EINVAL; - - s = strchrnul(s + 1, sep); - - *share = it->it_name; - *share_len = s - it->it_name; - *prefix = *s ? s + 1 : s; - *prefix_len = &it->it_name[strlen(it->it_name)] - *prefix; - - return 0; -} - -/* Get all tcons that are within a DFS namespace and can be refreshed */ -static void get_tcons(struct TCP_Server_Info *server, struct list_head *head) -{ - 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); -} - -static bool is_dfs_link(const char *path) -{ - char *s; - - s = strchr(path + 1, '\\'); - if (!s) - return false; - return !!strchr(s + 1, '\\'); -} - -static char *get_dfs_root(const char *path) -{ - char *s, *npath; - - s = strchr(path + 1, '\\'); - if (!s) - return ERR_PTR(-EINVAL); - - s = strchr(s + 1, '\\'); - if (!s) - return ERR_PTR(-EINVAL); - - npath = kstrndup(path, s - path, GFP_KERNEL); - if (!npath) - return ERR_PTR(-ENOMEM); - - return npath; -} - -static inline void put_tcp_server(struct TCP_Server_Info *server) -{ - cifs_put_tcp_session(server, 0); -} - -static struct TCP_Server_Info *get_tcp_server(struct smb_vol *vol) -{ - struct TCP_Server_Info *server; - - server = cifs_find_tcp_session(vol); - if (IS_ERR_OR_NULL(server)) - return NULL; - - spin_lock(&GlobalMid_Lock); - if (server->tcpStatus != CifsGood) { - spin_unlock(&GlobalMid_Lock); - put_tcp_server(server); - return NULL; - } - spin_unlock(&GlobalMid_Lock); - - return server; -} - -/* Find root SMB session out of a DFS link path */ -static struct cifs_ses *find_root_ses(struct vol_info *vi, - struct cifs_tcon *tcon, - const char *path) -{ - char *rpath; - int rc; - struct cache_entry *ce; - struct dfs_info3_param ref = {0}; - char *mdata = NULL, *devname = NULL; - struct TCP_Server_Info *server; - struct cifs_ses *ses; - struct smb_vol vol = {NULL}; - - rpath = get_dfs_root(path); - if (IS_ERR(rpath)) - return ERR_CAST(rpath); - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(rpath, NULL); - if (IS_ERR(ce)) { - up_read(&htable_rw_lock); - ses = ERR_CAST(ce); - goto out; - } - - rc = setup_referral(path, ce, &ref, get_tgt_name(ce)); - if (rc) { - up_read(&htable_rw_lock); - ses = ERR_PTR(rc); - goto out; - } - - up_read(&htable_rw_lock); - - mdata = cifs_compose_mount_options(vi->mntdata, rpath, &ref, - &devname); - free_dfs_info_param(&ref); - - if (IS_ERR(mdata)) { - ses = ERR_CAST(mdata); - mdata = NULL; - goto out; - } - - rc = cifs_setup_volume_info(&vol, mdata, devname, false); - kfree(devname); - - if (rc) { - ses = ERR_PTR(rc); - goto out; - } - - server = get_tcp_server(&vol); - if (!server) { - ses = ERR_PTR(-EHOSTDOWN); - goto out; - } - - ses = cifs_get_smb_ses(server, &vol); - -out: - cifs_cleanup_volume_info_contents(&vol); - kfree(mdata); - kfree(rpath); - - return ses; -} - -/* Refresh DFS cache entry from a given tcon */ -static int refresh_tcon(struct vol_info *vi, struct cifs_tcon *tcon) -{ - int rc = 0; - unsigned int xid; - char *path, *npath; - struct cache_entry *ce; - struct cifs_ses *root_ses = NULL, *ses; - struct dfs_info3_param *refs = NULL; - int numrefs = 0; - - xid = get_xid(); - - path = tcon->dfs_path + 1; - - rc = get_normalized_path(path, &npath); - if (rc) - goto out_free_xid; - - down_read(&htable_rw_lock); - - ce = lookup_cache_entry(npath, NULL); - if (IS_ERR(ce)) { - rc = PTR_ERR(ce); - up_read(&htable_rw_lock); - goto out_free_path; - } - - if (!cache_entry_expired(ce)) { - up_read(&htable_rw_lock); - goto out_free_path; - } - - up_read(&htable_rw_lock); - - /* If it's a DFS Link, then use root SMB session for refreshing it */ - if (is_dfs_link(npath)) { - ses = root_ses = find_root_ses(vi, tcon, npath); - if (IS_ERR(ses)) { - rc = PTR_ERR(ses); - root_ses = NULL; - goto out_free_path; - } - } else { - ses = tcon->ses; - } - - rc = get_dfs_referral(xid, ses, cache_nlsc, tcon->remap, npath, &refs, - &numrefs); - if (!rc) { - dump_refs(refs, numrefs); - rc = update_cache_entry(npath, refs, numrefs); - free_dfs_info_array(refs, numrefs); - } - - if (root_ses) - cifs_put_smb_ses(root_ses); - -out_free_path: - free_normalized_path(path, npath); - -out_free_xid: - free_xid(xid); - return rc; -} - -/* - * Worker that will refresh DFS cache based on lowest TTL value from a DFS - * referral. - */ -static void refresh_cache_worker(struct work_struct *work) -{ - struct vol_info *vi, *nvi; - struct TCP_Server_Info *server; - LIST_HEAD(vols); - LIST_HEAD(tcons); - struct cifs_tcon *tcon, *ntcon; - int rc; - - /* - * Find SMB volumes that are eligible (server->tcpStatus == CifsGood) - * for refreshing. - */ - spin_lock(&vol_list_lock); - list_for_each_entry(vi, &vol_list, list) { - server = get_tcp_server(&vi->smb_vol); - if (!server) - continue; - - kref_get(&vi->refcnt); - list_add_tail(&vi->rlist, &vols); - put_tcp_server(server); - } - spin_unlock(&vol_list_lock); - - /* Walk through all TCONs and refresh any expired cache entry */ - list_for_each_entry_safe(vi, nvi, &vols, rlist) { - spin_lock(&vi->smb_vol_lock); - server = get_tcp_server(&vi->smb_vol); - spin_unlock(&vi->smb_vol_lock); - - if (!server) - goto next_vol; - - get_tcons(server, &tcons); - rc = 0; - - list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) { - /* - * Skip tcp server if any of its tcons failed to refresh - * (possibily due to reconnects). - */ - if (!rc) - rc = refresh_tcon(vi, tcon); - - list_del_init(&tcon->ulist); - cifs_put_tcon(tcon); - } - - put_tcp_server(server); - -next_vol: - list_del_init(&vi->rlist); - kref_put(&vi->refcnt, vol_release); - } - - spin_lock(&cache_ttl_lock); - queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ); - spin_unlock(&cache_ttl_lock); -} |
