dfs_cache.c 39.4 KB
Newer Older
P
Paulo Alcantara 已提交
1 2 3 4
// SPDX-License-Identifier: GPL-2.0
/*
 * DFS referral cache routines
 *
5
 * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de>
P
Paulo Alcantara 已提交
6 7 8 9 10
 */

#include <linux/jhash.h>
#include <linux/ktime.h>
#include <linux/slab.h>
11
#include <linux/proc_fs.h>
P
Paulo Alcantara 已提交
12 13
#include <linux/nls.h>
#include <linux/workqueue.h>
14
#include <linux/uuid.h>
P
Paulo Alcantara 已提交
15 16 17 18 19 20 21
#include "cifsglob.h"
#include "smb2pdu.h"
#include "smb2proto.h"
#include "cifsproto.h"
#include "cifs_debug.h"
#include "cifs_unicode.h"
#include "smb2glob.h"
22
#include "dns_resolve.h"
P
Paulo Alcantara 已提交
23 24 25

#include "dfs_cache.h"

26 27
#define CACHE_HTABLE_SIZE 32
#define CACHE_MAX_ENTRIES 64
28
#define CACHE_MIN_TTL 120 /* 2 minutes */
P
Paulo Alcantara 已提交
29

30
#define IS_DFS_INTERLINK(v) (((v) & DFSREF_REFERRAL_SERVER) && !((v) & DFSREF_STORAGE_SERVER))
P
Paulo Alcantara 已提交
31

32 33
struct cache_dfs_tgt {
	char *name;
34
	int path_consumed;
35
	struct list_head list;
P
Paulo Alcantara 已提交
36 37
};

38 39 40
struct cache_entry {
	struct hlist_node hlist;
	const char *path;
41 42 43 44
	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 */
45
	struct timespec64 etime;
46
	int path_consumed; /* RESP_GET_DFS_REFERRAL.PathConsumed */
47 48 49
	int numtgts;
	struct list_head tlist;
	struct cache_dfs_tgt *tgthint;
P
Paulo Alcantara 已提交
50 51
};

52 53
/* List of referral server sessions per dfs mount */
struct mount_group {
54
	struct list_head list;
55 56 57 58 59 60
	uuid_t id;
	struct cifs_ses *sessions[CACHE_MAX_ENTRIES];
	int num_sessions;
	spinlock_t lock;
	struct list_head refresh_list;
	struct kref refcount;
P
Paulo Alcantara 已提交
61 62
};

63 64
static struct kmem_cache *cache_slab __read_mostly;
static struct workqueue_struct *dfscache_wq __read_mostly;
P
Paulo Alcantara 已提交
65

66
static int cache_ttl;
67 68
static DEFINE_SPINLOCK(cache_ttl_lock);

69
static struct nls_table *cache_cp;
P
Paulo Alcantara 已提交
70 71 72 73

/*
 * Number of entries in the cache
 */
74
static atomic_t cache_count;
75 76

static struct hlist_head cache_htable[CACHE_HTABLE_SIZE];
77
static DECLARE_RWSEM(htable_rw_lock);
P
Paulo Alcantara 已提交
78

79 80
static LIST_HEAD(mount_group_list);
static DEFINE_MUTEX(mount_group_list_lock);
P
Paulo Alcantara 已提交
81 82 83

static void refresh_cache_worker(struct work_struct *work);

84 85
static DECLARE_DELAYED_WORK(refresh_task, refresh_cache_worker);

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
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++) {
101
		if (!strcasecmp(unc, (*ses)->tcon_ipc->tree_name))
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
			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_ses(mg->sessions[i]);
	kfree(mg);
}

static void mount_group_release(struct kref *kref)
{
	struct mount_group *mg = container_of(kref, struct mount_group, refcount);

	mutex_lock(&mount_group_list_lock);
	list_del(&mg->list);
	mutex_unlock(&mount_group_list_lock);
	__mount_group_release(mg);
}

static struct mount_group *find_mount_group_locked(const uuid_t *id)
{
	struct mount_group *mg;

	list_for_each_entry(mg, &mount_group_list, list) {
		if (uuid_equal(&mg->id, id))
			return mg;
	}
	return ERR_PTR(-ENOENT);
}

static struct mount_group *__get_mount_group_locked(const uuid_t *id)
{
	struct mount_group *mg;

	mg = find_mount_group_locked(id);
	if (!IS_ERR(mg))
		return mg;

	mg = kmalloc(sizeof(*mg), GFP_KERNEL);
	if (!mg)
		return ERR_PTR(-ENOMEM);
	kref_init(&mg->refcount);
	uuid_copy(&mg->id, id);
	mg->num_sessions = 0;
	spin_lock_init(&mg->lock);
	list_add(&mg->list, &mount_group_list);
	return mg;
}

static struct mount_group *get_mount_group(const uuid_t *id)
{
	struct mount_group *mg;

	mutex_lock(&mount_group_list_lock);
	mg = __get_mount_group_locked(id);
	if (!IS_ERR(mg))
		kref_get(&mg->refcount);
	mutex_unlock(&mount_group_list_lock);

	return mg;
}

static void free_mount_group_list(void)
{
	struct mount_group *mg, *tmp_mg;

	list_for_each_entry_safe(mg, tmp_mg, &mount_group_list, list) {
		list_del_init(&mg->list);
		__mount_group_release(mg);
	}
}

179 180 181 182 183 184 185 186 187 188
/**
 * 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)
P
Paulo Alcantara 已提交
189
{
190 191 192 193
	char *tmp;
	int plen = 0;
	char *npath;

194
	if (!path || strlen(path) < 3 || (*path != '\\' && *path != '/'))
195 196 197 198 199 200 201 202
		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);
		}
P
Paulo Alcantara 已提交
203

204 205 206 207 208 209 210
		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);
		}
P
Paulo Alcantara 已提交
211
	} else {
212 213 214
		npath = kstrdup(path, GFP_KERNEL);
		if (!npath)
			return ERR_PTR(-ENOMEM);
P
Paulo Alcantara 已提交
215
	}
216 217
	convert_delimiter(npath, '\\');
	return npath;
P
Paulo Alcantara 已提交
218 219
}

220
static inline bool cache_entry_expired(const struct cache_entry *ce)
P
Paulo Alcantara 已提交
221 222 223
{
	struct timespec64 ts;

224
	ktime_get_coarse_real_ts64(&ts);
225
	return timespec64_compare(&ts, &ce->etime) >= 0;
P
Paulo Alcantara 已提交
226 227
}

228
static inline void free_tgts(struct cache_entry *ce)
P
Paulo Alcantara 已提交
229
{
230
	struct cache_dfs_tgt *t, *n;
P
Paulo Alcantara 已提交
231

232 233 234
	list_for_each_entry_safe(t, n, &ce->tlist, list) {
		list_del(&t->list);
		kfree(t->name);
P
Paulo Alcantara 已提交
235 236 237 238
		kfree(t);
	}
}

239
static inline void flush_cache_ent(struct cache_entry *ce)
P
Paulo Alcantara 已提交
240
{
241
	hlist_del_init(&ce->hlist);
242
	kfree(ce->path);
P
Paulo Alcantara 已提交
243
	free_tgts(ce);
244 245
	atomic_dec(&cache_count);
	kmem_cache_free(cache_slab, ce);
P
Paulo Alcantara 已提交
246 247 248 249 250 251
}

static void flush_cache_ents(void)
{
	int i;

252 253
	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
		struct hlist_head *l = &cache_htable[i];
254
		struct hlist_node *n;
255
		struct cache_entry *ce;
P
Paulo Alcantara 已提交
256

257 258 259 260
		hlist_for_each_entry_safe(ce, n, l, hlist) {
			if (!hlist_unhashed(&ce->hlist))
				flush_cache_ent(ce);
		}
P
Paulo Alcantara 已提交
261 262 263 264 265 266 267 268
	}
}

/*
 * dfs cache /proc file
 */
static int dfscache_proc_show(struct seq_file *m, void *v)
{
269
	int i;
270 271
	struct cache_entry *ce;
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
272 273 274

	seq_puts(m, "DFS cache\n---------\n");

275 276 277
	down_read(&htable_rw_lock);
	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
		struct hlist_head *l = &cache_htable[i];
P
Paulo Alcantara 已提交
278

279 280 281 282 283
		hlist_for_each_entry(ce, l, hlist) {
			if (hlist_unhashed(&ce->hlist))
				continue;

			seq_printf(m,
284 285
				   "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",
286
				   ce->ttl, ce->etime.tv_nsec, ce->hdr_flags, ce->ref_flags,
287
				   IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
288
				   ce->path_consumed, cache_entry_expired(ce) ? "yes" : "no");
289 290 291 292 293 294 295

			list_for_each_entry(t, &ce->tlist, list) {
				seq_printf(m, "  %s%s\n",
					   t->name,
					   ce->tgthint == t ? " (target hint)" : "");
			}
		}
P
Paulo Alcantara 已提交
296
	}
297
	up_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314

	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;

J
Joe Perches 已提交
315
	cifs_dbg(FYI, "clearing dfs cache\n");
316 317

	down_write(&htable_rw_lock);
P
Paulo Alcantara 已提交
318
	flush_cache_ents();
319
	up_write(&htable_rw_lock);
P
Paulo Alcantara 已提交
320 321 322 323 324 325 326 327 328

	return count;
}

static int dfscache_proc_open(struct inode *inode, struct file *file)
{
	return single_open(file, dfscache_proc_show, NULL);
}

329 330 331 332 333 334
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,
P
Paulo Alcantara 已提交
335 336 337
};

#ifdef CONFIG_CIFS_DEBUG2
338
static inline void dump_tgts(const struct cache_entry *ce)
P
Paulo Alcantara 已提交
339
{
340
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
341 342

	cifs_dbg(FYI, "target list:\n");
343 344 345
	list_for_each_entry(t, &ce->tlist, list) {
		cifs_dbg(FYI, "  %s%s\n", t->name,
			 ce->tgthint == t ? " (target hint)" : "");
P
Paulo Alcantara 已提交
346 347 348
	}
}

349
static inline void dump_ce(const struct cache_entry *ce)
P
Paulo Alcantara 已提交
350
{
351
	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",
J
Joe Perches 已提交
352
		 ce->path,
353 354
		 ce->srvtype == DFS_TYPE_ROOT ? "root" : "link", ce->ttl,
		 ce->etime.tv_nsec,
355
		 ce->hdr_flags, ce->ref_flags,
356
		 IS_DFS_INTERLINK(ce->hdr_flags) ? "yes" : "no",
357
		 ce->path_consumed,
P
Paulo Alcantara 已提交
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
		 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)
{
397
	int rc;
P
Paulo Alcantara 已提交
398 399
	int i;

400
	dfscache_wq = alloc_workqueue("cifs-dfscache", WQ_FREEZABLE | WQ_UNBOUND, 1);
401
	if (!dfscache_wq)
P
Paulo Alcantara 已提交
402 403
		return -ENOMEM;

404 405 406 407 408 409 410 411 412 413
	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]);
P
Paulo Alcantara 已提交
414

415
	atomic_set(&cache_count, 0);
416 417 418
	cache_cp = load_nls("utf8");
	if (!cache_cp)
		cache_cp = load_nls_default();
P
Paulo Alcantara 已提交
419 420 421

	cifs_dbg(FYI, "%s: initialized DFS referral cache\n", __func__);
	return 0;
422 423 424 425

out_destroy_wq:
	destroy_workqueue(dfscache_wq);
	return rc;
P
Paulo Alcantara 已提交
426 427
}

428
static int cache_entry_hash(const void *data, int size, unsigned int *hash)
P
Paulo Alcantara 已提交
429
{
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
	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;
P
Paulo Alcantara 已提交
446 447 448
}

/* Return target hint of a DFS cache entry */
449
static inline char *get_tgt_name(const struct cache_entry *ce)
P
Paulo Alcantara 已提交
450
{
451
	struct cache_dfs_tgt *t = ce->tgthint;
P
Paulo Alcantara 已提交
452

453
	return t ? t->name : ERR_PTR(-ENOENT);
P
Paulo Alcantara 已提交
454 455 456 457 458 459 460 461 462
}

/* 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,
	};
463
	struct timespec64 now;
P
Paulo Alcantara 已提交
464

465 466
	ktime_get_coarse_real_ts64(&now);
	return timespec64_add(now, ts);
P
Paulo Alcantara 已提交
467 468 469
}

/* Allocate a new DFS target */
470
static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed)
P
Paulo Alcantara 已提交
471
{
472
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
473

474
	t = kmalloc(sizeof(*t), GFP_ATOMIC);
P
Paulo Alcantara 已提交
475 476
	if (!t)
		return ERR_PTR(-ENOMEM);
A
Al Viro 已提交
477
	t->name = kstrdup(name, GFP_ATOMIC);
478
	if (!t->name) {
P
Paulo Alcantara 已提交
479 480 481
		kfree(t);
		return ERR_PTR(-ENOMEM);
	}
482
	t->path_consumed = path_consumed;
483
	INIT_LIST_HEAD(&t->list);
P
Paulo Alcantara 已提交
484 485 486 487 488 489 490 491
	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,
492
			 struct cache_entry *ce, const char *tgthint)
P
Paulo Alcantara 已提交
493 494 495
{
	int i;

496
	ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
497 498
	ce->etime = get_expire_time(ce->ttl);
	ce->srvtype = refs[0].server_type;
499 500
	ce->hdr_flags = refs[0].flags;
	ce->ref_flags = refs[0].ref_flag;
501
	ce->path_consumed = refs[0].path_consumed;
P
Paulo Alcantara 已提交
502 503

	for (i = 0; i < numrefs; i++) {
504
		struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
505

506
		t = alloc_target(refs[i].node_name, refs[i].path_consumed);
P
Paulo Alcantara 已提交
507 508 509 510
		if (IS_ERR(t)) {
			free_tgts(ce);
			return PTR_ERR(t);
		}
511 512
		if (tgthint && !strcasecmp(t->name, tgthint)) {
			list_add(&t->list, &ce->tlist);
P
Paulo Alcantara 已提交
513 514
			tgthint = NULL;
		} else {
515
			list_add_tail(&t->list, &ce->tlist);
P
Paulo Alcantara 已提交
516
		}
517
		ce->numtgts++;
P
Paulo Alcantara 已提交
518 519
	}

520 521
	ce->tgthint = list_first_entry_or_null(&ce->tlist,
					       struct cache_dfs_tgt, list);
P
Paulo Alcantara 已提交
522 523 524 525 526

	return 0;
}

/* Allocate a new cache entry */
527
static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
P
Paulo Alcantara 已提交
528
{
529
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
530 531
	int rc;

532
	ce = kmem_cache_zalloc(cache_slab, GFP_KERNEL);
P
Paulo Alcantara 已提交
533 534 535
	if (!ce)
		return ERR_PTR(-ENOMEM);

536 537 538
	ce->path = refs[0].path_name;
	refs[0].path_name = NULL;

539 540
	INIT_HLIST_NODE(&ce->hlist);
	INIT_LIST_HEAD(&ce->tlist);
P
Paulo Alcantara 已提交
541 542 543

	rc = copy_ref_data(refs, numrefs, ce, NULL);
	if (rc) {
544
		kfree(ce->path);
545
		kmem_cache_free(cache_slab, ce);
P
Paulo Alcantara 已提交
546 547 548 549 550
		ce = ERR_PTR(rc);
	}
	return ce;
}

551
static void remove_oldest_entry_locked(void)
P
Paulo Alcantara 已提交
552
{
553
	int i;
554 555
	struct cache_entry *ce;
	struct cache_entry *to_del = NULL;
P
Paulo Alcantara 已提交
556

557 558
	WARN_ON(!rwsem_is_locked(&htable_rw_lock));

559 560 561 562 563 564 565 566 567 568
	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;
		}
P
Paulo Alcantara 已提交
569
	}
570

P
Paulo Alcantara 已提交
571
	if (!to_del) {
J
Joe Perches 已提交
572
		cifs_dbg(FYI, "%s: no entry to remove\n", __func__);
573
		return;
P
Paulo Alcantara 已提交
574
	}
575

J
Joe Perches 已提交
576
	cifs_dbg(FYI, "%s: removing entry\n", __func__);
P
Paulo Alcantara 已提交
577 578 579 580 581
	dump_ce(to_del);
	flush_cache_ent(to_del);
}

/* Add a new DFS cache entry */
582
static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
P
Paulo Alcantara 已提交
583
{
584
	int rc;
585
	struct cache_entry *ce;
586
	unsigned int hash;
P
Paulo Alcantara 已提交
587

588 589 590 591 592 593 594
	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);
		remove_oldest_entry_locked();
	}

595 596 597 598 599
	rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
	if (rc)
		return rc;

	ce = alloc_cache_entry(refs, numrefs);
P
Paulo Alcantara 已提交
600
	if (IS_ERR(ce))
601
		return PTR_ERR(ce);
P
Paulo Alcantara 已提交
602

603 604
	spin_lock(&cache_ttl_lock);
	if (!cache_ttl) {
605 606
		cache_ttl = ce->ttl;
		queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
P
Paulo Alcantara 已提交
607
	} else {
608 609
		cache_ttl = min_t(int, cache_ttl, ce->ttl);
		mod_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
P
Paulo Alcantara 已提交
610
	}
611
	spin_unlock(&cache_ttl_lock);
P
Paulo Alcantara 已提交
612

613 614 615
	hlist_add_head(&ce->hlist, &cache_htable[hash]);
	dump_ce(ce);

616 617
	atomic_inc(&cache_count);

618
	return 0;
P
Paulo Alcantara 已提交
619 620
}

621 622
/* Check if two DFS paths are equal.  @s1 and @s2 are expected to be in @cache_cp's charset */
static bool dfs_path_equal(const char *s1, int len1, const char *s2, int len2)
P
Paulo Alcantara 已提交
623
{
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
	int i, l1, l2;
	wchar_t c1, c2;

	if (len1 != len2)
		return false;

	for (i = 0; i < len1; i += l1) {
		l1 = cache_cp->char2uni(&s1[i], len1 - i, &c1);
		l2 = cache_cp->char2uni(&s2[i], len2 - i, &c2);
		if (unlikely(l1 < 0 && l2 < 0)) {
			if (s1[i] != s2[i])
				return false;
			l1 = 1;
			continue;
		}
		if (l1 != l2)
			return false;
		if (cifs_toupper(c1) != cifs_toupper(c2))
			return false;
	}
	return true;
}
P
Paulo Alcantara 已提交
646

647 648 649
static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len)
{
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
650

651 652
	hlist_for_each_entry(ce, &cache_htable[hash], hlist) {
		if (dfs_path_equal(ce->path, strlen(ce->path), path, len)) {
653
			dump_ce(ce);
654
			return ce;
P
Paulo Alcantara 已提交
655 656
		}
	}
657
	return ERR_PTR(-ENOENT);
658 659 660
}

/*
661 662 663
 * Find a DFS cache entry in hash table and optionally check prefix path against normalized @path.
 *
 * Use whole path components in the match.  Must be called with htable_rw_lock held.
664
 *
665
 * Return ERR_PTR(-ENOENT) if the entry is not found.
666
 */
667
static struct cache_entry *lookup_cache_entry(const char *path)
668
{
669
	struct cache_entry *ce;
670
	int cnt = 0;
671 672 673 674
	const char *s = path, *e;
	char sep = *s;
	unsigned int hash;
	int rc;
675 676 677 678 679

	while ((s = strchr(s, sep)) && ++cnt < 3)
		s++;

	if (cnt < 3) {
680 681 682 683
		rc = cache_entry_hash(path, strlen(path), &hash);
		if (rc)
			return ERR_PTR(rc);
		return __lookup_cache_entry(path, hash, strlen(path));
684 685 686 687 688 689 690
	}
	/*
	 * Handle paths that have more than two path components and are a complete prefix of the DFS
	 * referral request path (@path).
	 *
	 * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
	 */
691
	e = path + strlen(path) - 1;
692
	while (e > s) {
693
		int len;
694 695 696 697 698 699 700

		/* skip separators */
		while (e > s && *e == sep)
			e--;
		if (e == s)
			break;

701 702 703 704 705 706 707 708
		len = e + 1 - path;
		rc = cache_entry_hash(path, len, &hash);
		if (rc)
			return ERR_PTR(rc);
		ce = __lookup_cache_entry(path, hash, len);
		if (!IS_ERR(ce))
			return ce;

709 710 711 712
		/* backward until separator */
		while (e > s && *e != sep)
			e--;
	}
713
	return ERR_PTR(-ENOENT);
P
Paulo Alcantara 已提交
714 715 716 717 718 719 720
}

/**
 * dfs_cache_destroy - destroy DFS referral cache
 */
void dfs_cache_destroy(void)
{
721
	cancel_delayed_work_sync(&refresh_task);
722
	unload_nls(cache_cp);
723
	free_mount_group_list();
P
Paulo Alcantara 已提交
724
	flush_cache_ents();
725
	kmem_cache_destroy(cache_slab);
726
	destroy_workqueue(dfscache_wq);
P
Paulo Alcantara 已提交
727 728 729 730

	cifs_dbg(FYI, "%s: destroyed DFS referral cache\n", __func__);
}

731
/* Update a cache entry with the new referral in @refs */
732
static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_info3_param *refs,
733
				     int numrefs)
P
Paulo Alcantara 已提交
734 735 736 737
{
	int rc;
	char *s, *th = NULL;

738
	WARN_ON(!rwsem_is_locked(&htable_rw_lock));
P
Paulo Alcantara 已提交
739

740 741
	if (ce->tgthint) {
		s = ce->tgthint->name;
A
Al Viro 已提交
742
		th = kstrdup(s, GFP_ATOMIC);
P
Paulo Alcantara 已提交
743
		if (!th)
744
			return -ENOMEM;
P
Paulo Alcantara 已提交
745 746 747
	}

	free_tgts(ce);
748
	ce->numtgts = 0;
P
Paulo Alcantara 已提交
749 750 751

	rc = copy_ref_data(refs, numrefs, ce, th);

752
	kfree(th);
P
Paulo Alcantara 已提交
753

754
	return rc;
P
Paulo Alcantara 已提交
755 756
}

757 758
static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const char *path,
			    struct dfs_info3_param **refs, int *numrefs)
P
Paulo Alcantara 已提交
759
{
760 761 762
	int rc;
	int i;

763
	cifs_dbg(FYI, "%s: get an DFS referral for %s\n", __func__, path);
P
Paulo Alcantara 已提交
764

765 766 767
	*refs = NULL;
	*numrefs = 0;

P
Paulo Alcantara 已提交
768
	if (!ses || !ses->server || !ses->server->ops->get_dfs_refer)
769
		return -EOPNOTSUPP;
770
	if (unlikely(!cache_cp))
771
		return -EINVAL;
P
Paulo Alcantara 已提交
772

773 774 775 776
	rc =  ses->server->ops->get_dfs_refer(xid, ses, path, refs, numrefs, cache_cp,
					      NO_MAP_UNI_RSVD);
	if (!rc) {
		struct dfs_info3_param *ref = *refs;
P
Paulo Alcantara 已提交
777

778 779 780 781
		for (i = 0; i < *numrefs; i++)
			convert_delimiter(ref[i].path_name, '\\');
	}
	return rc;
782
}
P
Paulo Alcantara 已提交
783 784 785 786 787 788 789

/*
 * 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.
 *
790
 * For interlinks, cifs_mount() and expand_dfs_referral() are supposed to
P
Paulo Alcantara 已提交
791 792
 * handle them properly.
 */
793
static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
P
Paulo Alcantara 已提交
794 795
{
	int rc;
796
	struct cache_entry *ce;
797 798 799
	struct dfs_info3_param *refs = NULL;
	int numrefs = 0;
	bool newent = false;
P
Paulo Alcantara 已提交
800 801 802

	cifs_dbg(FYI, "%s: search path: %s\n", __func__, path);

803
	down_write(&htable_rw_lock);
P
Paulo Alcantara 已提交
804

805
	ce = lookup_cache_entry(path);
806 807 808
	if (!IS_ERR(ce)) {
		if (!cache_entry_expired(ce)) {
			dump_ce(ce);
809
			up_write(&htable_rw_lock);
810
			return 0;
P
Paulo Alcantara 已提交
811
		}
812 813 814
	} else {
		newent = true;
	}
P
Paulo Alcantara 已提交
815

816
	/*
817 818
	 * Either the entry was not found, or it is expired.
	 * Request a new DFS referral in order to create or update a cache entry.
819
	 */
820
	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
821
	if (rc)
822
		goto out_unlock;
P
Paulo Alcantara 已提交
823

824
	dump_refs(refs, numrefs);
P
Paulo Alcantara 已提交
825

826
	if (!newent) {
827
		rc = update_cache_entry_locked(ce, refs, numrefs);
828
		goto out_unlock;
829
	}
P
Paulo Alcantara 已提交
830

831
	rc = add_cache_entry_locked(refs, numrefs);
P
Paulo Alcantara 已提交
832

833 834
out_unlock:
	up_write(&htable_rw_lock);
835 836
	free_dfs_info_array(refs, numrefs);
	return rc;
P
Paulo Alcantara 已提交
837 838
}

839 840 841 842 843 844 845
/*
 * 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)
P
Paulo Alcantara 已提交
846 847 848 849 850 851 852
{
	int rc;

	cifs_dbg(FYI, "%s: set up new ref\n", __func__);

	memset(ref, 0, sizeof(*ref));

A
Al Viro 已提交
853
	ref->path_name = kstrdup(path, GFP_ATOMIC);
P
Paulo Alcantara 已提交
854 855 856
	if (!ref->path_name)
		return -ENOMEM;

A
Al Viro 已提交
857
	ref->node_name = kstrdup(target, GFP_ATOMIC);
P
Paulo Alcantara 已提交
858 859 860 861 862
	if (!ref->node_name) {
		rc = -ENOMEM;
		goto err_free_path;
	}

863
	ref->path_consumed = ce->path_consumed;
864 865
	ref->ttl = ce->ttl;
	ref->server_type = ce->srvtype;
866 867
	ref->ref_flag = ce->ref_flags;
	ref->flags = ce->hdr_flags;
P
Paulo Alcantara 已提交
868 869 870 871 872 873 874 875 876 877

	return 0;

err_free_path:
	kfree(ref->path_name);
	ref->path_name = NULL;
	return rc;
}

/* Return target list of a DFS cache entry */
878
static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)
P
Paulo Alcantara 已提交
879 880 881
{
	int rc;
	struct list_head *head = &tl->tl_list;
882
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
883 884 885 886 887
	struct dfs_cache_tgt_iterator *it, *nit;

	memset(tl, 0, sizeof(*tl));
	INIT_LIST_HEAD(head);

888
	list_for_each_entry(t, &ce->tlist, list) {
889
		it = kzalloc(sizeof(*it), GFP_ATOMIC);
P
Paulo Alcantara 已提交
890 891 892 893 894
		if (!it) {
			rc = -ENOMEM;
			goto err_free_it;
		}

A
Al Viro 已提交
895
		it->it_name = kstrdup(t->name, GFP_ATOMIC);
P
Paulo Alcantara 已提交
896
		if (!it->it_name) {
897
			kfree(it);
P
Paulo Alcantara 已提交
898 899 900
			rc = -ENOMEM;
			goto err_free_it;
		}
901
		it->it_path_consumed = t->path_consumed;
P
Paulo Alcantara 已提交
902

903
		if (ce->tgthint == t)
P
Paulo Alcantara 已提交
904 905 906 907
			list_add(&it->it_list, head);
		else
			list_add_tail(&it->it_list, head);
	}
908

909
	tl->tl_numtgts = ce->numtgts;
P
Paulo Alcantara 已提交
910 911 912 913 914

	return 0;

err_free_it:
	list_for_each_entry_safe(it, nit, head, it_list) {
915
		list_del(&it->it_list);
P
Paulo Alcantara 已提交
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
		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
935
 * @cp: codepage
P
Paulo Alcantara 已提交
936 937 938 939 940 941 942 943
 * @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.
 */
944 945
int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nls_table *cp,
		   int remap, const char *path, struct dfs_info3_param *ref,
P
Paulo Alcantara 已提交
946 947 948
		   struct dfs_cache_tgt_list *tgt_list)
{
	int rc;
949
	const char *npath;
950
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
951

952 953 954
	npath = dfs_cache_canonical_path(path, cp, remap);
	if (IS_ERR(npath))
		return PTR_ERR(npath);
P
Paulo Alcantara 已提交
955

956
	rc = cache_refresh_path(xid, ses, npath);
957 958 959 960 961
	if (rc)
		goto out_free_path;

	down_read(&htable_rw_lock);

962
	ce = lookup_cache_entry(npath);
963 964
	if (IS_ERR(ce)) {
		up_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
965
		rc = PTR_ERR(ce);
966
		goto out_free_path;
P
Paulo Alcantara 已提交
967
	}
968 969 970 971 972 973 974 975 976 977 978

	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:
979
	kfree(npath);
P
Paulo Alcantara 已提交
980 981 982 983 984 985 986 987 988 989 990
	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.
 *
991
 * @path: canonical DFS path to lookup in the DFS referral cache.
P
Paulo Alcantara 已提交
992 993 994 995 996 997 998 999 1000 1001 1002
 * @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;
1003
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
1004

1005
	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
1006 1007 1008

	down_read(&htable_rw_lock);

1009
	ce = lookup_cache_entry(path);
P
Paulo Alcantara 已提交
1010 1011
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
1012
		goto out_unlock;
P
Paulo Alcantara 已提交
1013 1014 1015
	}

	if (ref)
1016
		rc = setup_referral(path, ce, ref, get_tgt_name(ce));
P
Paulo Alcantara 已提交
1017 1018 1019
	else
		rc = 0;
	if (!rc && tgt_list)
1020 1021 1022 1023
		rc = get_targets(ce, tgt_list);

out_unlock:
	up_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
	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
1038
 * @cp: codepage
P
Paulo Alcantara 已提交
1039
 * @remap: type of character remapping for paths
1040
 * @path: path to lookup in DFS referral cache
P
Paulo Alcantara 已提交
1041 1042 1043 1044 1045
 * @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,
1046
			     const struct nls_table *cp, int remap, const char *path,
P
Paulo Alcantara 已提交
1047 1048 1049
			     const struct dfs_cache_tgt_iterator *it)
{
	int rc;
1050
	const char *npath;
1051 1052
	struct cache_entry *ce;
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
1053

1054 1055 1056
	npath = dfs_cache_canonical_path(path, cp, remap);
	if (IS_ERR(npath))
		return PTR_ERR(npath);
P
Paulo Alcantara 已提交
1057

1058 1059
	cifs_dbg(FYI, "%s: update target hint - path: %s\n", __func__, npath);

1060
	rc = cache_refresh_path(xid, ses, npath);
1061 1062
	if (rc)
		goto out_free_path;
P
Paulo Alcantara 已提交
1063

1064 1065
	down_write(&htable_rw_lock);

1066
	ce = lookup_cache_entry(npath);
P
Paulo Alcantara 已提交
1067 1068
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
1069
		goto out_unlock;
P
Paulo Alcantara 已提交
1070 1071
	}

1072
	t = ce->tgthint;
P
Paulo Alcantara 已提交
1073

1074
	if (likely(!strcasecmp(it->it_name, t->name)))
1075
		goto out_unlock;
P
Paulo Alcantara 已提交
1076

1077 1078 1079
	list_for_each_entry(t, &ce->tlist, list) {
		if (!strcasecmp(t->name, it->it_name)) {
			ce->tgthint = t;
P
Paulo Alcantara 已提交
1080 1081 1082 1083 1084 1085
			cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
				 it->it_name);
			break;
		}
	}

1086 1087 1088
out_unlock:
	up_write(&htable_rw_lock);
out_free_path:
1089
	kfree(npath);
P
Paulo Alcantara 已提交
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
	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.
 *
1101
 * @path: canonical DFS path to lookup in DFS referral cache.
P
Paulo Alcantara 已提交
1102 1103 1104 1105 1106
 * @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.
 */
1107
int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it)
P
Paulo Alcantara 已提交
1108 1109
{
	int rc;
1110 1111
	struct cache_entry *ce;
	struct cache_dfs_tgt *t;
P
Paulo Alcantara 已提交
1112

1113
	if (!it)
P
Paulo Alcantara 已提交
1114 1115
		return -EINVAL;

1116
	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
P
Paulo Alcantara 已提交
1117

1118
	down_write(&htable_rw_lock);
P
Paulo Alcantara 已提交
1119

1120
	ce = lookup_cache_entry(path);
P
Paulo Alcantara 已提交
1121 1122
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
1123
		goto out_unlock;
P
Paulo Alcantara 已提交
1124 1125 1126
	}

	rc = 0;
1127
	t = ce->tgthint;
P
Paulo Alcantara 已提交
1128

1129
	if (unlikely(!strcasecmp(it->it_name, t->name)))
1130
		goto out_unlock;
P
Paulo Alcantara 已提交
1131

1132 1133 1134
	list_for_each_entry(t, &ce->tlist, list) {
		if (!strcasecmp(t->name, it->it_name)) {
			ce->tgthint = t;
P
Paulo Alcantara 已提交
1135 1136 1137 1138 1139 1140
			cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
				 it->it_name);
			break;
		}
	}

1141 1142
out_unlock:
	up_write(&htable_rw_lock);
P
Paulo Alcantara 已提交
1143 1144 1145 1146 1147 1148 1149
	return rc;
}

/**
 * dfs_cache_get_tgt_referral - returns a DFS referral (@ref) from a given
 * target iterator (@it).
 *
1150
 * @path: canonical DFS path to lookup in DFS referral cache.
P
Paulo Alcantara 已提交
1151 1152 1153 1154 1155
 * @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.
 */
1156
int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
P
Paulo Alcantara 已提交
1157 1158 1159
			       struct dfs_info3_param *ref)
{
	int rc;
1160
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
1161 1162 1163 1164

	if (!it || !ref)
		return -EINVAL;

1165
	cifs_dbg(FYI, "%s: path: %s\n", __func__, path);
P
Paulo Alcantara 已提交
1166

1167
	down_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
1168

1169
	ce = lookup_cache_entry(path);
P
Paulo Alcantara 已提交
1170 1171
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
1172
		goto out_unlock;
P
Paulo Alcantara 已提交
1173 1174 1175 1176
	}

	cifs_dbg(FYI, "%s: target name: %s\n", __func__, it->it_name);

1177
	rc = setup_referral(path, ce, ref, it->it_name);
P
Paulo Alcantara 已提交
1178

1179 1180
out_unlock:
	up_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
1181 1182 1183 1184
	return rc;
}

/**
1185
 * dfs_cache_add_refsrv_session - add SMB session of referral server
P
Paulo Alcantara 已提交
1186
 *
1187 1188
 * @mount_id: mount group uuid to lookup.
 * @ses: reference counted SMB session of referral server.
P
Paulo Alcantara 已提交
1189
 */
1190
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses)
P
Paulo Alcantara 已提交
1191
{
1192
	struct mount_group *mg;
P
Paulo Alcantara 已提交
1193

1194 1195
	if (WARN_ON_ONCE(!mount_id || uuid_is_null(mount_id) || !ses))
		return;
P
Paulo Alcantara 已提交
1196

1197 1198 1199
	mg = get_mount_group(mount_id);
	if (WARN_ON_ONCE(IS_ERR(mg)))
		return;
P
Paulo Alcantara 已提交
1200

1201 1202 1203 1204 1205
	spin_lock(&mg->lock);
	if (mg->num_sessions < ARRAY_SIZE(mg->sessions))
		mg->sessions[mg->num_sessions++] = ses;
	spin_unlock(&mg->lock);
	kref_put(&mg->refcount, mount_group_release);
P
Paulo Alcantara 已提交
1206 1207 1208
}

/**
1209
 * dfs_cache_put_refsrv_sessions - put all referral server sessions
P
Paulo Alcantara 已提交
1210
 *
1211
 * Put all SMB sessions from the given mount group id.
P
Paulo Alcantara 已提交
1212
 *
1213
 * @mount_id: mount group uuid to lookup.
P
Paulo Alcantara 已提交
1214
 */
1215
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id)
P
Paulo Alcantara 已提交
1216
{
1217
	struct mount_group *mg;
P
Paulo Alcantara 已提交
1218

1219
	if (!mount_id || uuid_is_null(mount_id))
P
Paulo Alcantara 已提交
1220 1221
		return;

1222 1223 1224 1225 1226 1227 1228 1229
	mutex_lock(&mount_group_list_lock);
	mg = find_mount_group_locked(mount_id);
	if (IS_ERR(mg)) {
		mutex_unlock(&mount_group_list_lock);
		return;
	}
	mutex_unlock(&mount_group_list_lock);
	kref_put(&mg->refcount, mount_group_release);
P
Paulo Alcantara 已提交
1230 1231
}

1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255
/* Extract share from DFS target and return a pointer to prefix path or NULL */
static const char *parse_target_share(const char *target, char **share)
{
	const char *s, *seps = "/\\";
	size_t len;

	s = strpbrk(target + 1, seps);
	if (!s)
		return ERR_PTR(-EINVAL);

	len = strcspn(s + 1, seps);
	if (!len)
		return ERR_PTR(-EINVAL);
	s += len;

	len = s - target + 1;
	*share = kstrndup(target, len, GFP_KERNEL);
	if (!*share)
		return ERR_PTR(-ENOMEM);

	s = target + len;
	return s + strspn(s, seps);
}

1256 1257 1258
/**
 * dfs_cache_get_tgt_share - parse a DFS target
 *
1259
 * @path: DFS full path
1260 1261 1262 1263 1264 1265
 * @it: DFS target iterator.
 * @share: tree name.
 * @prefix: prefix path.
 *
 * Return zero if target was parsed correctly, otherwise non-zero.
 */
1266 1267
int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it, char **share,
			    char **prefix)
1268
{
1269
	char sep;
1270 1271
	char *target_share;
	char *ppath = NULL;
1272 1273 1274
	const char *target_ppath, *dfsref_ppath;
	size_t target_pplen, dfsref_pplen;
	size_t len, c;
1275

1276
	if (!it || !path || !share || !prefix || strlen(path) < it->it_path_consumed)
1277 1278 1279 1280 1281 1282
		return -EINVAL;

	sep = it->it_name[0];
	if (sep != '\\' && sep != '/')
		return -EINVAL;

1283 1284 1285
	target_ppath = parse_target_share(it->it_name, &target_share);
	if (IS_ERR(target_ppath))
		return PTR_ERR(target_ppath);
1286

1287 1288 1289
	/* point to prefix in DFS referral path */
	dfsref_ppath = path + it->it_path_consumed;
	dfsref_ppath += strspn(dfsref_ppath, "/\\");
1290

1291 1292
	target_pplen = strlen(target_ppath);
	dfsref_pplen = strlen(dfsref_ppath);
1293

1294 1295 1296 1297 1298 1299
	/* merge prefix paths from DFS referral path and target node */
	if (target_pplen || dfsref_pplen) {
		len = target_pplen + dfsref_pplen + 2;
		ppath = kzalloc(len, GFP_KERNEL);
		if (!ppath) {
			kfree(target_share);
1300 1301
			return -ENOMEM;
		}
1302 1303 1304 1305
		c = strscpy(ppath, target_ppath, len);
		if (c && dfsref_pplen)
			ppath[c] = sep;
		strlcat(ppath, dfsref_ppath, len);
1306
	}
1307 1308
	*share = target_share;
	*prefix = ppath;
1309 1310 1311
	return 0;
}

1312 1313 1314 1315 1316
static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
{
	char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
	const char *host;
	size_t hostlen;
1317
	struct sockaddr_storage ss;
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331
	bool match;
	int rc;

	if (strcasecmp(s1, s2))
		return false;

	/*
	 * Resolve share's hostname and check if server address matches.  Otherwise just ignore it
	 * as we could not have upcall to resolve hostname or failed to convert ip address.
	 */
	match = true;
	extract_unc_hostname(s1, &host, &hostlen);
	scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);

1332
	rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
1333 1334 1335 1336 1337 1338
	if (rc < 0) {
		cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
			 __func__, (int)hostlen, host);
		return true;
	}

1339 1340 1341
	cifs_server_lock(server);
	match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, (struct sockaddr *)&ss);
	cifs_server_unlock(server);
1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364

	return match;
}

/*
 * Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new
 * target shares in @refs.
 */
static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cache_tgt_list *tl,
					 const struct dfs_info3_param *refs, int numrefs)
{
	struct dfs_cache_tgt_iterator *it;
	int i;

	for (it = dfs_cache_get_tgt_iterator(tl); it; it = dfs_cache_get_next_tgt(tl, it)) {
		for (i = 0; i < numrefs; i++) {
			if (target_share_equal(tcon->ses->server, dfs_cache_get_tgt_name(it),
					       refs[i].node_name))
				return;
		}
	}

	cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__);
1365
	cifs_signal_cifsd_for_reconnect(tcon->ses->server, true);
1366 1367 1368
}

/* Refresh dfs referral of tcon and mark it for reconnect if needed */
1369 1370
static int __refresh_tcon(const char *path, struct cifs_ses **sessions, struct cifs_tcon *tcon,
			  bool force_refresh)
1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426
{
	struct cifs_ses *ses;
	struct cache_entry *ce;
	struct dfs_info3_param *refs = NULL;
	int numrefs = 0;
	bool needs_refresh = false;
	struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
	int rc = 0;
	unsigned int xid;

	ses = find_ipc_from_server_path(sessions, path);
	if (IS_ERR(ses)) {
		cifs_dbg(FYI, "%s: could not find ipc session\n", __func__);
		return PTR_ERR(ses);
	}

	down_read(&htable_rw_lock);
	ce = lookup_cache_entry(path);
	needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
	if (!IS_ERR(ce)) {
		rc = get_targets(ce, &tl);
		if (rc)
			cifs_dbg(FYI, "%s: could not get dfs targets: %d\n", __func__, rc);
	}
	up_read(&htable_rw_lock);

	if (!needs_refresh) {
		rc = 0;
		goto out;
	}

	xid = get_xid();
	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
	free_xid(xid);

	/* Create or update a cache entry with the new referral */
	if (!rc) {
		dump_refs(refs, numrefs);

		down_write(&htable_rw_lock);
		ce = lookup_cache_entry(path);
		if (IS_ERR(ce))
			add_cache_entry_locked(refs, numrefs);
		else if (force_refresh || cache_entry_expired(ce))
			update_cache_entry_locked(ce, refs, numrefs);
		up_write(&htable_rw_lock);

		mark_for_reconnect_if_needed(tcon, &tl, refs, numrefs);
	}

out:
	dfs_cache_free_tgts(&tl);
	free_dfs_info_array(refs, numrefs);
	return rc;
}

1427 1428 1429 1430 1431
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
{
	struct TCP_Server_Info *server = tcon->ses->server;

	mutex_lock(&server->refpath_lock);
1432 1433 1434 1435 1436 1437
	if (server->origin_fullpath) {
		if (server->leaf_fullpath && strcasecmp(server->leaf_fullpath,
							server->origin_fullpath))
			__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, force_refresh);
		__refresh_tcon(server->origin_fullpath + 1, sessions, tcon, force_refresh);
	}
1438 1439 1440 1441 1442
	mutex_unlock(&server->refpath_lock);

	return 0;
}

1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455
/**
 * dfs_cache_remount_fs - remount a DFS share
 *
 * Reconfigure dfs mount by forcing a new DFS referral and if the currently cached targets do not
 * match any of the new targets, mark it for reconnect.
 *
 * @cifs_sb: cifs superblock.
 *
 * Return zero if remounted, otherwise non-zero.
 */
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
{
	struct cifs_tcon *tcon;
1456
	struct TCP_Server_Info *server;
1457 1458 1459 1460 1461 1462 1463 1464
	struct mount_group *mg;
	struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
	int rc;

	if (!cifs_sb || !cifs_sb->master_tlink)
		return -EINVAL;

	tcon = cifs_sb_master_tcon(cifs_sb);
1465 1466 1467 1468
	server = tcon->ses->server;

	if (!server->origin_fullpath) {
		cifs_dbg(FYI, "%s: not a dfs mount\n", __func__);
1469 1470 1471 1472
		return 0;
	}

	if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
1473
		cifs_dbg(FYI, "%s: no dfs mount group id\n", __func__);
1474 1475 1476 1477 1478 1479 1480
		return -EINVAL;
	}

	mutex_lock(&mount_group_list_lock);
	mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
	if (IS_ERR(mg)) {
		mutex_unlock(&mount_group_list_lock);
1481
		cifs_dbg(FYI, "%s: no ipc session for refreshing referral\n", __func__);
1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
		return PTR_ERR(mg);
	}
	kref_get(&mg->refcount);
	mutex_unlock(&mount_group_list_lock);

	spin_lock(&mg->lock);
	memcpy(&sessions, mg->sessions, mg->num_sessions * sizeof(mg->sessions[0]));
	spin_unlock(&mg->lock);

	/*
	 * After reconnecting to a different server, unique ids won't match anymore, so we disable
	 * serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
	 */
	cifs_autodisable_serverino(cifs_sb);
	/*
	 * Force the use of prefix path to support failover on DFS paths that resolve to targets
	 * that have different prefix paths.
	 */
	cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
	rc = refresh_tcon(sessions, tcon, true);

	kref_put(&mg->refcount, mount_group_release);
	return rc;
}

1507 1508 1509 1510 1511
/*
 * Refresh all active dfs mounts regardless of whether they are in cache or not.
 * (cache can be cleared)
 */
static void refresh_mounts(struct cifs_ses **sessions)
P
Paulo Alcantara 已提交
1512
{
1513
	struct TCP_Server_Info *server;
P
Paulo Alcantara 已提交
1514
	struct cifs_ses *ses;
1515 1516
	struct cifs_tcon *tcon, *ntcon;
	struct list_head tcons;
P
Paulo Alcantara 已提交
1517

1518
	INIT_LIST_HEAD(&tcons);
P
Paulo Alcantara 已提交
1519 1520

	spin_lock(&cifs_tcp_ses_lock);
1521
	list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) {
1522
		if (!server->leaf_fullpath)
1523 1524
			continue;

1525 1526
		list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
			list_for_each_entry(tcon, &ses->tcon_list, tcon_list) {
1527
				spin_lock(&tcon->tc_lock);
1528
				if (!tcon->ipc && !tcon->need_reconnect) {
1529 1530 1531
					tcon->tc_count++;
					list_add_tail(&tcon->ulist, &tcons);
				}
1532
				spin_unlock(&tcon->tc_lock);
P
Paulo Alcantara 已提交
1533 1534 1535 1536
			}
		}
	}
	spin_unlock(&cifs_tcp_ses_lock);
1537

1538
	list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
1539 1540
		struct TCP_Server_Info *server = tcon->ses->server;

1541
		list_del_init(&tcon->ulist);
1542 1543

		mutex_lock(&server->refpath_lock);
1544 1545
		if (server->leaf_fullpath)
			__refresh_tcon(server->leaf_fullpath + 1, sessions, tcon, false);
1546 1547
		mutex_unlock(&server->refpath_lock);

1548
		cifs_put_tcon(tcon);
1549 1550 1551
	}
}

1552
static void refresh_cache(struct cifs_ses **sessions)
1553
{
1554
	int i;
1555
	struct cifs_ses *ses;
P
Paulo Alcantara 已提交
1556
	unsigned int xid;
1557 1558 1559
	char *ref_paths[CACHE_MAX_ENTRIES];
	int count = 0;
	struct cache_entry *ce;
P
Paulo Alcantara 已提交
1560

1561
	/*
1562 1563 1564
	 * Refresh all cached entries.  Get all new referrals outside critical section to avoid
	 * starvation while performing SMB2 IOCTL on broken or slow connections.

1565 1566 1567
	 * The cache entries may cover more paths than the active mounts
	 * (e.g. domain-based DFS referrals or multi tier DFS setups).
	 */
1568
	down_read(&htable_rw_lock);
1569 1570
	for (i = 0; i < CACHE_HTABLE_SIZE; i++) {
		struct hlist_head *l = &cache_htable[i];
P
Paulo Alcantara 已提交
1571

1572
		hlist_for_each_entry(ce, l, hlist) {
1573 1574 1575 1576
			if (count == ARRAY_SIZE(ref_paths))
				goto out_unlock;
			if (hlist_unhashed(&ce->hlist) || !cache_entry_expired(ce) ||
			    IS_ERR(find_ipc_from_server_path(sessions, ce->path)))
1577
				continue;
1578 1579 1580
			ref_paths[count++] = kstrdup(ce->path, GFP_ATOMIC);
		}
	}
P
Paulo Alcantara 已提交
1581

1582 1583
out_unlock:
	up_read(&htable_rw_lock);
P
Paulo Alcantara 已提交
1584

1585 1586 1587 1588 1589
	for (i = 0; i < count; i++) {
		char *path = ref_paths[i];
		struct dfs_info3_param *refs = NULL;
		int numrefs = 0;
		int rc = 0;
1590

1591 1592
		if (!path)
			continue;
P
Paulo Alcantara 已提交
1593

1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611
		ses = find_ipc_from_server_path(sessions, path);
		if (IS_ERR(ses))
			goto next_referral;

		xid = get_xid();
		rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
		free_xid(xid);

		if (!rc) {
			down_write(&htable_rw_lock);
			ce = lookup_cache_entry(path);
			/*
			 * We need to re-check it because other tasks might have it deleted or
			 * updated.
			 */
			if (!IS_ERR(ce) && cache_entry_expired(ce))
				update_cache_entry_locked(ce, refs, numrefs);
			up_write(&htable_rw_lock);
1612
		}
1613 1614 1615 1616

next_referral:
		kfree(path);
		free_dfs_info_array(refs, numrefs);
P
Paulo Alcantara 已提交
1617 1618 1619 1620
	}
}

/*
1621
 * Worker that will refresh DFS cache and active mounts based on lowest TTL value from a DFS
P
Paulo Alcantara 已提交
1622 1623 1624 1625
 * referral.
 */
static void refresh_cache_worker(struct work_struct *work)
{
1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638
	struct list_head mglist;
	struct mount_group *mg, *tmp_mg;
	struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
	int max_sessions = ARRAY_SIZE(sessions) - 1;
	int i = 0, count;

	INIT_LIST_HEAD(&mglist);

	/* Get refereces of mount groups */
	mutex_lock(&mount_group_list_lock);
	list_for_each_entry(mg, &mount_group_list, list) {
		kref_get(&mg->refcount);
		list_add(&mg->refresh_list, &mglist);
1639
	}
1640
	mutex_unlock(&mount_group_list_lock);
1641

1642 1643 1644 1645
	/* Fill in local array with an NULL-terminated list of all referral server sessions */
	list_for_each_entry(mg, &mglist, refresh_list) {
		if (i >= max_sessions)
			break;
1646

1647 1648 1649 1650 1651 1652 1653 1654 1655
		spin_lock(&mg->lock);
		if (i + mg->num_sessions > max_sessions)
			count = max_sessions - i;
		else
			count = mg->num_sessions;
		memcpy(&sessions[i], mg->sessions, count * sizeof(mg->sessions[0]));
		spin_unlock(&mg->lock);
		i += count;
	}
1656

1657 1658 1659 1660 1661
	if (sessions[0]) {
		/* Refresh all active mounts and cached entries */
		refresh_mounts(sessions);
		refresh_cache(sessions);
	}
1662

1663 1664 1665
	list_for_each_entry_safe(mg, tmp_mg, &mglist, refresh_list) {
		list_del_init(&mg->refresh_list);
		kref_put(&mg->refcount, mount_group_release);
P
Paulo Alcantara 已提交
1666
	}
1667 1668

	spin_lock(&cache_ttl_lock);
1669
	queue_delayed_work(dfscache_wq, &refresh_task, cache_ttl * HZ);
1670
	spin_unlock(&cache_ttl_lock);
P
Paulo Alcantara 已提交
1671
}