// 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"
|