Commit 42caeba7 authored by Paulo Alcantara's avatar Paulo Alcantara Committed by Steve French
Browse files

cifs: fix path comparison and hash calc



Fix cache lookup and hash calculations when handling paths with
different cases.

Signed-off-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: default avatarAurelien Aptel <aaptel@suse.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent c870a8e7
Loading
Loading
Loading
Loading
+94 −76
Original line number Diff line number Diff line
@@ -424,12 +424,24 @@ int dfs_cache_init(void)
	return rc;
}

static inline unsigned int cache_entry_hash(const void *data, int size)
static int cache_entry_hash(const void *data, int size, unsigned int *hash)
{
	unsigned int h;
	int i, clen;
	const unsigned char *s = data;
	wchar_t c;
	unsigned int h = 0;

	h = jhash(data, size, 0);
	return h & (CACHE_HTABLE_SIZE - 1);
	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 */
@@ -511,9 +523,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
}

/* Allocate a new cache entry */
static struct cache_entry *alloc_cache_entry(const char *path,
					     const struct dfs_info3_param *refs,
					     int numrefs)
static struct cache_entry *alloc_cache_entry(struct dfs_info3_param *refs, int numrefs)
{
	struct cache_entry *ce;
	int rc;
@@ -522,11 +532,9 @@ static struct cache_entry *alloc_cache_entry(const char *path,
	if (!ce)
		return ERR_PTR(-ENOMEM);

	ce->path = kstrdup(path, GFP_KERNEL);
	if (!ce->path) {
		kmem_cache_free(cache_slab, 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);

@@ -568,12 +576,18 @@ static void remove_oldest_entry_locked(void)
}

/* Add a new DFS cache entry */
static int add_cache_entry_locked(const char *path, unsigned int hash,
				  struct dfs_info3_param *refs, int numrefs)
static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
{
	int rc;
	struct cache_entry *ce;
	unsigned int hash;

	ce = alloc_cache_entry(path, refs, numrefs);
	convert_delimiter(refs[0].path_name, '\\');
	rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
	if (rc)
		return rc;

	ce = alloc_cache_entry(refs, numrefs);
	if (IS_ERR(ce))
		return PTR_ERR(ce);

@@ -593,57 +607,69 @@ static int add_cache_entry_locked(const char *path, unsigned int hash,
	return 0;
}

static struct cache_entry *__lookup_cache_entry(const char *path)
/* 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)
{
	struct cache_entry *ce;
	unsigned int h;
	bool found = false;
	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;
}

	h = cache_entry_hash(path, strlen(path));
static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int hash, int len)
{
	struct cache_entry *ce;

	hlist_for_each_entry(ce, &cache_htable[h], hlist) {
		if (!strcasecmp(path, ce->path)) {
			found = true;
	hlist_for_each_entry(ce, &cache_htable[hash], hlist) {
		if (dfs_path_equal(ce->path, strlen(ce->path), path, len)) {
			dump_ce(ce);
			break;
			return ce;
		}
	}

	if (!found)
		ce = ERR_PTR(-ENOENT);
	return ce;
	return ERR_PTR(-EEXIST);
}

/*
 * Find a DFS cache entry in hash table and optionally check prefix path against
 * @path.
 * Use whole path components in the match.
 * Must be called with htable_rw_lock held.
 * 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.
 *
 * Return ERR_PTR(-ENOENT) if the entry is not found.
 * Return ERR_PTR(-EEXIST) if the entry is not found.
 */
static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *hash)
static struct cache_entry *lookup_cache_entry(const char *path)
{
	struct cache_entry *ce = ERR_PTR(-ENOENT);
	unsigned int h;
	struct cache_entry *ce;
	int cnt = 0;
	char *npath;
	char *s, *e;
	char sep;

	npath = kstrdup(path, GFP_KERNEL);
	if (!npath)
		return ERR_PTR(-ENOMEM);
	const char *s = path, *e;
	char sep = *s;
	unsigned int hash;
	int rc;

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

	if (cnt < 3) {
		h = cache_entry_hash(path, strlen(path));
		ce = __lookup_cache_entry(path);
		goto out;
		rc = cache_entry_hash(path, strlen(path), &hash);
		if (rc)
			return ERR_PTR(rc);
		return __lookup_cache_entry(path, hash, strlen(path));
	}
	/*
	 * Handle paths that have more than two path components and are a complete prefix of the DFS
@@ -651,36 +677,29 @@ static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *ha
	 *
	 * See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
	 */
	h = cache_entry_hash(npath, strlen(npath));
	e = npath + strlen(npath) - 1;
	e = path + strlen(path) - 1;
	while (e > s) {
		char tmp;
		int len;

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

		tmp = *(e+1);
		*(e+1) = 0;

		ce = __lookup_cache_entry(npath);
		if (!IS_ERR(ce)) {
			h = cache_entry_hash(npath, strlen(npath));
			break;
		}

		*(e+1) = tmp;
		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;

		/* backward until separator */
		while (e > s && *e != sep)
			e--;
	}
out:
	if (hash)
		*hash = h;
	kfree(npath);
	return ce;
	return ERR_PTR(-EEXIST);
}

/**
@@ -706,7 +725,7 @@ static int update_cache_entry_locked(const char *path, const struct dfs_info3_pa
	struct cache_entry *ce;
	char *s, *th = NULL;

	ce = lookup_cache_entry(path, NULL);
	ce = lookup_cache_entry(path);
	if (IS_ERR(ce))
		return PTR_ERR(ce);

@@ -756,7 +775,6 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
{
	int rc;
	unsigned int hash;
	struct cache_entry *ce;
	struct dfs_info3_param *refs = NULL;
	int numrefs = 0;
@@ -766,7 +784,7 @@ static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, cons

	down_write(&htable_rw_lock);

	ce = lookup_cache_entry(path, &hash);
	ce = lookup_cache_entry(path);
	if (!IS_ERR(ce)) {
		if (!cache_entry_expired(ce)) {
			dump_ce(ce);
@@ -797,7 +815,7 @@ static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, cons
		remove_oldest_entry_locked();
	}

	rc = add_cache_entry_locked(path, hash, refs, numrefs);
	rc = add_cache_entry_locked(refs, numrefs);
	if (!rc)
		atomic_inc(&cache_count);

@@ -929,7 +947,7 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl

	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(npath, NULL);
	ce = lookup_cache_entry(npath);
	if (IS_ERR(ce)) {
		up_read(&htable_rw_lock);
		rc = PTR_ERR(ce);
@@ -976,7 +994,7 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,

	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(path, NULL);
	ce = lookup_cache_entry(path);
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
		goto out_unlock;
@@ -1033,7 +1051,7 @@ int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,

	down_write(&htable_rw_lock);

	ce = lookup_cache_entry(npath, NULL);
	ce = lookup_cache_entry(npath);
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
		goto out_unlock;
@@ -1087,7 +1105,7 @@ int dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_

	down_write(&htable_rw_lock);

	ce = lookup_cache_entry(path, NULL);
	ce = lookup_cache_entry(path);
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
		goto out_unlock;
@@ -1136,7 +1154,7 @@ int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iter

	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(path, NULL);
	ce = lookup_cache_entry(path);
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
		goto out_unlock;