1375 lines · 31.6 KB · C
RawDownload
// 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.