// 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_cache.h"
#define CACHE_HTABLE_SIZE 32
#define CACHE_MAX_ENTRIES 64
#define CACHE_MIN_TTL 120 /* 2 minutes */
#define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
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;
};
/* List of referral server sessions per dfs mount */
struct mount_group {
struct list_head list;
uuid_t id;
struct cifs_ses *sessions[CACHE_MAX_ENTRIES];
int num_sessions;
spinlock_t lock;
struct list_head refresh_list;
struct kref refcount;
};
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_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);
static LIST_HEAD(mount_group_list);
static DEFINE_MUTEX(mount_group_list_lock);
static void refresh_cache_worker(struct work_struct *work);
static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);
static void get_ipc_unc(const char *ref_path, char *ipc, size_t ipclen)
{
const char *host;
size_t len;
extract_unc_hostname(ref_path, &host, &len);
scnprintf(ipc, ipclen, "\\\\%.*s\\IPC$", (int)len, host);
}
static struct cifs_ses *find_ipc_from_server_path(struct cifs_ses **ses, const char *path)
{
char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0};
get_ipc_unc(path, unc, sizeof(unc));
for (; *ses; ses++) {
if (!strcasecmp(unc, (*ses)->tcon_ipc->treeName))
return *ses;
}
return ERR_PTR(-ENOENT);
}
static void __mount_group_release(struct mount_group *mg)
{
int i;
for (i = 0; i < mg->num_sessions; i++)
cifs_put_smb_se
|