// 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 <linux/uuid.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 "dns_resolve.h"#include "dfs.h" #include "dfs_cache.h" #define CACHE_HTABLE_SIZE 512#define CACHE_MAX_ENTRIES 1024#define CACHE_MIN_TTL 120 /* 2 minutes */#define CACHE_DEFAULT_TTL 300 /* 5 minutes */ struct cache_dfs_tgt { char *name; int path_consumed; struct list_head list;}; struct cache_entry { struct hlist_node hlist; const char *path; int hdr_flags; /* RESP_GET_DFS_REFERRAL.ReferralHeaderFlags */ int ttl; /* DFS_REREFERRAL_V3.TimeToLive */ int srvtype; /* DFS_REREFERRAL_V3.ServerType */ int ref_flags; /* DFS_REREFERRAL_V3.ReferralEntryFlags */ struct timespec64 etime; int path_consumed; /* RESP_GET_DFS_REFERRAL.PathConsumed */ int numtgts; struct list_head tlist; struct cache_dfs_tgt *tgthint;}; static struct kmem_cache *cache_slab __read_mostly;struct workqueue_struct *dfscache_wq; atomic_t dfs_cache_ttl; static struct nls_table *cache_cp; /* * 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); /** * dfs_cache_canonical_path - get a canonical DFS path * * @path: DFS path * @cp: codepage * @remap: mapping type * * Return canonical path if success, otherwise error. */char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap){ char *tmp; int plen = 0; char *npath; if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/')) return ERR_PTR(-EINVAL); if (unlikely(strcmp(cp->charset, cache_cp->charset))) { tmp = (char *)cifs_strndup_to_utf16(path, strlen(path), &plen, cp, remap); if (!tmp) { cifs_dbg(VFS, "%s: failed to convert path to utf16\n", __func__); return ERR_PTR(-EINVAL); } npath = cifs_strndup_from_utf16(tmp, plen, true, cache_cp); kfree(tmp); if (!npath) { cifs_dbg(VFS, "%s: failed to convert path from utf16\n", __func__); return ERR_PTR(-EINVAL); } } else { npath = kstrdup(path, GFP_KERNEL); if (!npath) return ERR_PTR(-ENOMEM); } convert_delimiter(npath, '\\'); return npath;} static inline bool cache_entry_expired(const struct cache_entry *ce){ 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); } ce->numtgts = 0; WRITE_ONCE(ce->tgthint, NULL);} static inline void flush_cache_ent(struct cache_entry *ce){ cifs_dbg(FYI, "%s: %s\n", __func__, ce->path); 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,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n", ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags, str_yes_no(DFS_INTERLINK(ce->hdr_flags)), ce->path_consumed, str_yes_no(cache_entry_expired(ce))); list_for_each_entry(t, &ce->tlist, list) { seq_printf(m, " %s%s\n", t->name, READ_ONCE(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_DEBUG2static 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, READ_ONCE(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,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n", ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags, str_yes_no(DFS_INTERLINK(ce->hdr_flags)), ce->path_consumed, str_yes_no(cache_entry_expired(ce))); 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_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM, 0); 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); atomic_set(&dfs_cache_ttl, CACHE_DEFAULT_TTL); cache_cp = load_nls("utf8"); if (!cache_cp) cache_cp = load_nls_default(); cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); return 0; out_destroy_wq: destroy_workqueue(dfscache_wq); return rc;} static int cache_entry_hash(const void *data, int size, unsigned int *hash){ int i, clen; const unsigned char *s = data; wchar_t c; unsigned int h = 0; for (i = 0; i < size; i += clen) { clen = cache_cp->char2uni(&s[i], size - i, &c); if (unlikely(clen < 0)) { cifs_dbg(VFS, "%s: can't convert char\n", __func__); return clen; } c = cifs_toupper(c); h = jhash(&c, sizeof(c), h); } *hash = h % CACHE_HTABLE_SIZE; return 0;} /* Return target hint of a DFS cache entry */static inline char *get_tgt_name(const struct cache_entry *ce){ struct cache_dfs_tgt *t = READ_ONCE(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, int path_consumed){ struct cache_dfs_tgt *t; t = kmalloc_obj(*t); if (!t) return ERR_PTR(-ENOMEM); t->name = kstrdup(name, GFP_KERNEL); if (!t->name) { kfree(t); return ERR_PTR(-ENOMEM); } t->path_consumed = path_consumed; 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){ struct cache_dfs_tgt *target; int i; for (i = 0; i < numrefs; i++) { struct cache_dfs_tgt *t; t = alloc_target(refs[i].node_name, refs[i].path_consumed); 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); } } target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt, list); WRITE_ONCE(ce->tgthint, target); ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); ce->etime = get_expire_time(ce->ttl); ce->srvtype = refs[0].server_type; ce->hdr_flags = refs[0].flags; ce->ref_flags = refs[0].ref_flag; ce->path_consumed = refs[0].path_consumed; ce->numtgts = numrefs; return 0;} /* Allocate a new cache entry */static struct cache_entry *alloc_cache_entry(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 = refs[0].path_name; refs[0].path_name = NULL; 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;} /* Remove all referrals that have a single target or oldest entry */static void purge_cache(void){ int i; struct cache_entry *ce; struct cache_entry *oldest = NULL; for (i = 0; i < CACHE_HTABLE_SIZE; i++) { struct hlist_head *l = &cache_htable[i]; struct hlist_node *n; hlist_for_each_entry_safe(ce, n, l, hlist) { if (hlist_unhashed(&ce->hlist)) continue; if (ce->numtgts == 1) flush_cache_ent(ce); else if (!oldest || timespec64_compare(&ce->etime, &oldest->etime) < 0) oldest = ce; } } if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES && oldest) flush_cache_ent(oldest);} /* Add a new DFS cache entry */static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs){ int rc; struct cache_entry *ce; unsigned int hash; int ttl; WARN_ON(!rwsem_is_locked(&htable_rw_lock)); if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) { cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES); purge_cache(); } rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash); if (rc) return ERR_PTR(rc); ce = alloc_cache_entry(refs, numrefs); if (IS_ERR(ce)) return ce; ttl = min_t(int, atomic_read(&dfs_cache_ttl), ce->ttl); atomic_set(&dfs_cache_ttl, ttl);Lines 1–500 of 1375. Load more
// 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 <linux/uuid.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 "dns_resolve.h" #include "dfs.h" #include "dfs_cache.h" #define CACHE_HTABLE_SIZE 512 #define CACHE_MAX_ENTRIES 1024 #define CACHE_MIN_TTL 120 /* 2 minutes */ #define CACHE_DEFAULT_TTL 300 /* 5 minutes */ struct cache_dfs_tgt { char *name; int path_consumed; struct list_head list; }; struct cache_entry { struct hlist_node hlist; const char *path; int hdr_flags; /* RESP_GET_DFS_REFERRAL.ReferralHeaderFlags */ int ttl; /* DFS_REREFERRAL_V3.TimeToLive */ int srvtype; /* DFS_REREFERRAL_V3.ServerType */ int ref_flags; /* DFS_REREFERRAL_V3.ReferralEntryFlags */ struct timespec64 etime; int path_consumed; /* RESP_GET_DFS_REFERRAL.PathConsumed */ int numtgts; struct list_head tlist; struct cache_dfs_tgt *tgthint; }; static struct kmem_cache *cache_slab __read_mostly; struct workqueue_struct *dfscache_wq; atomic_t dfs_cache_ttl; static struct nls_table *cache_cp; /* * 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); /** * dfs_cache_canonical_path - get a canonical DFS path * * @path: DFS path * @cp: codepage * @remap: mapping type * * Return canonical path if success, otherwise error. */ char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap) { char *tmp; int plen = 0; char *npath; if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/')) return ERR_PTR(-EINVAL); if (unlikely(strcmp(cp->charset, cache_cp->charset))) { tmp = (char *)cifs_strndup_to_utf16(path, strlen(path), &plen, cp, remap); if (!tmp) { cifs_dbg(VFS, "%s: failed to convert path to utf16\n", __func__); return ERR_PTR(-EINVAL); } npath = cifs_strndup_from_utf16(tmp, plen, true, cache_cp); kfree(tmp); if (!npath) { cifs_dbg(VFS, "%s: failed to convert path from utf16\n", __func__); return ERR_PTR(-EINVAL); } } else { npath = kstrdup(path, GFP_KERNEL); if (!npath) return ERR_PTR(-ENOMEM); } convert_delimiter(npath, '\\'); return npath; } static inline bool cache_entry_expired(const struct cache_entry *ce) { 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); } ce->numtgts = 0; WRITE_ONCE(ce->tgthint, NULL); } static inline void flush_cache_ent(struct cache_entry *ce) { cifs_dbg(FYI, "%s: %s\n", __func__, ce->path); 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,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n", ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags, str_yes_no(DFS_INTERLINK(ce->hdr_flags)), ce->path_consumed, str_yes_no(cache_entry_expired(ce))); list_for_each_entry(t, &ce->tlist, list) { seq_printf(m, " %s%s\n", t->name, READ_ONCE(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, READ_ONCE(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,hdr_flags=0x%x,ref_flags=0x%x,interlink=%s,path_consumed=%d,expired=%s\n", ce->path, ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags, str_yes_no(DFS_INTERLINK(ce->hdr_flags)), ce->path_consumed, str_yes_no(cache_entry_expired(ce))); 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_UNBOUND|WQ_FREEZABLE|WQ_MEM_RECLAIM, 0); 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); atomic_set(&dfs_cache_ttl, CACHE_DEFAULT_TTL); cache_cp = load_nls("utf8"); if (!cache_cp) cache_cp = load_nls_default(); cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__); return 0; out_destroy_wq: destroy_workqueue(dfscache_wq); return rc; } static int cache_entry_hash(const void *data, int size, unsigned int *hash) { int i, clen; const unsigned char *s = data; wchar_t c; unsigned int h = 0; for (i = 0; i < size; i += clen) { clen = cache_cp->char2uni(&s[i], size - i, &c); if (unlikely(clen < 0)) { cifs_dbg(VFS, "%s: can't convert char\n", __func__); return clen; } c = cifs_toupper(c); h = jhash(&c, sizeof(c), h); } *hash = h % CACHE_HTABLE_SIZE; return 0; } /* Return target hint of a DFS cache entry */ static inline char *get_tgt_name(const struct cache_entry *ce) { struct cache_dfs_tgt *t = READ_ONCE(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, int path_consumed) { struct cache_dfs_tgt *t; t = kmalloc_obj(*t); if (!t) return ERR_PTR(-ENOMEM); t->name = kstrdup(name, GFP_KERNEL); if (!t->name) { kfree(t); return ERR_PTR(-ENOMEM); } t->path_consumed = path_consumed; 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) { struct cache_dfs_tgt *target; int i; for (i = 0; i < numrefs; i++) { struct cache_dfs_tgt *t; t = alloc_target(refs[i].node_name, refs[i].path_consumed); 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); } } target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt, list); WRITE_ONCE(ce->tgthint, target); ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL); ce->etime = get_expire_time(ce->ttl); ce->srvtype = refs[0].server_type; ce->hdr_flags = refs[0].flags; ce->ref_flags = refs[0].ref_flag; ce->path_consumed = refs[0].path_consumed; ce->numtgts = numrefs; return 0; } /* Allocate a new cache entry */ static struct cache_entry *alloc_cache_entry(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 = refs[0].path_name; refs[0].path_name = NULL; 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; } /* Remove all referrals that have a single target or oldest entry */ static void purge_cache(void) { int i; struct cache_entry *ce; struct cache_entry *oldest = NULL; for (i = 0; i < CACHE_HTABLE_SIZE; i++) { struct hlist_head *l = &cache_htable[i]; struct hlist_node *n; hlist_for_each_entry_safe(ce, n, l, hlist) { if (hlist_unhashed(&ce->hlist)) continue; if (ce->numtgts == 1) flush_cache_ent(ce); else if (!oldest || timespec64_compare(&ce->etime, &oldest->etime) < 0) oldest = ce; } } if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES && oldest) flush_cache_ent(oldest); } /* Add a new DFS cache entry */ static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs) { int rc; struct cache_entry *ce; unsigned int hash; int ttl; WARN_ON(!rwsem_is_locked(&htable_rw_lock)); if (atomic_read(&cache_count) >= CACHE_MAX_ENTRIES) { cifs_dbg(FYI, "%s: reached max cache size (%d)\n", __func__, CACHE_MAX_ENTRIES); purge_cache(); } rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash); if (rc) return ERR_PTR(rc); ce = alloc_cache_entry(refs, numrefs); if (IS_ERR(ce)) return ce; ttl = min_t(int, atomic_read(&dfs_cache_ttl), ce->ttl); atomic_set(&dfs_cache_ttl, ttl);