Commit 4e31bada authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '6.2-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs fixes from Steve French:

 - important fix for packet signature calculation error

 - three fixes to correct DFS deadlock, and DFS refresh problem

 - remove an unused DFS function, and duplicate tcon refresh code

 - DFS cache lookup fix

 - uninitialized rc fix

* tag '6.2-rc4-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: remove unused function
  cifs: do not include page data when checking signature
  cifs: fix return of uninitialized rc in dfs_cache_update_tgthint()
  cifs: handle cache lookup errors different than -ENOENT
  cifs: remove duplicate code in __refresh_tcon()
  cifs: don't take exclusive lock for updating target hints
  cifs: avoid re-lookups in dfs_cache_find()
  cifs: fix potential deadlock in cache_refresh_path()
parents 8440ffcd a1b7c845
Loading
Loading
Loading
Loading
+96 −147
Original line number Diff line number Diff line
@@ -269,7 +269,7 @@ static int dfscache_proc_show(struct seq_file *m, void *v)
			list_for_each_entry(t, &ce->tlist, list) {
				seq_printf(m, "  %s%s\n",
					   t->name,
					   ce->tgthint == t ? " (target hint)" : "");
					   READ_ONCE(ce->tgthint) == t ? " (target hint)" : "");
			}
		}
	}
@@ -321,7 +321,7 @@ static inline void dump_tgts(const struct cache_entry *ce)
	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)" : "");
			 READ_ONCE(ce->tgthint) == t ? " (target hint)" : "");
	}
}

@@ -427,7 +427,7 @@ static int cache_entry_hash(const void *data, int size, unsigned int *hash)
/* Return target hint of a DFS cache entry */
static inline char *get_tgt_name(const struct cache_entry *ce)
{
	struct cache_dfs_tgt *t = ce->tgthint;
	struct cache_dfs_tgt *t = READ_ONCE(ce->tgthint);

	return t ? t->name : ERR_PTR(-ENOENT);
}
@@ -470,6 +470,7 @@ static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed)
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;

	ce->ttl = max_t(int, refs[0].ttl, CACHE_MIN_TTL);
@@ -496,8 +497,9 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
		ce->numtgts++;
	}

	ce->tgthint = list_first_entry_or_null(&ce->tlist,
					       struct cache_dfs_tgt, list);
	target = list_first_entry_or_null(&ce->tlist, struct cache_dfs_tgt,
					  list);
	WRITE_ONCE(ce->tgthint, target);

	return 0;
}
@@ -558,7 +560,8 @@ static void remove_oldest_entry_locked(void)
}

/* Add a new DFS cache entry */
static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)
static struct cache_entry *add_cache_entry_locked(struct dfs_info3_param *refs,
						  int numrefs)
{
	int rc;
	struct cache_entry *ce;
@@ -573,11 +576,11 @@ static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)

	rc = cache_entry_hash(refs[0].path_name, strlen(refs[0].path_name), &hash);
	if (rc)
		return rc;
		return ERR_PTR(rc);

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

	spin_lock(&cache_ttl_lock);
	if (!cache_ttl) {
@@ -594,7 +597,7 @@ static int add_cache_entry_locked(struct dfs_info3_param *refs, int numrefs)

	atomic_inc(&cache_count);

	return 0;
	return ce;
}

/* Check if two DFS paths are equal.  @s1 and @s2 are expected to be in @cache_cp's charset */
@@ -641,7 +644,9 @@ static struct cache_entry *__lookup_cache_entry(const char *path, unsigned int h
 *
 * Use whole path components in the match.  Must be called with htable_rw_lock held.
 *
 * Return cached entry if successful.
 * Return ERR_PTR(-ENOENT) if the entry is not found.
 * Return error ptr otherwise.
 */
static struct cache_entry *lookup_cache_entry(const char *path)
{
@@ -711,14 +716,15 @@ void dfs_cache_destroy(void)
static int update_cache_entry_locked(struct cache_entry *ce, const struct dfs_info3_param *refs,
				     int numrefs)
{
	struct cache_dfs_tgt *target;
	char *th = NULL;
	int rc;
	char *s, *th = NULL;

	WARN_ON(!rwsem_is_locked(&htable_rw_lock));

	if (ce->tgthint) {
		s = ce->tgthint->name;
		th = kstrdup(s, GFP_ATOMIC);
	target = READ_ONCE(ce->tgthint);
	if (target) {
		th = kstrdup(target->name, GFP_ATOMIC);
		if (!th)
			return -ENOMEM;
	}
@@ -767,51 +773,75 @@ static int get_dfs_referral(const unsigned int xid, struct cifs_ses *ses, const
 *
 * For interlinks, cifs_mount() and expand_dfs_referral() are supposed to
 * handle them properly.
 *
 * On success, return entry with acquired lock for reading, otherwise error ptr.
 */
static int cache_refresh_path(const unsigned int xid, struct cifs_ses *ses, const char *path)
static struct cache_entry *cache_refresh_path(const unsigned int xid,
					      struct cifs_ses *ses,
					      const char *path,
					      bool force_refresh)
{
	int rc;
	struct cache_entry *ce;
	struct dfs_info3_param *refs = NULL;
	struct cache_entry *ce;
	int numrefs = 0;
	bool newent = false;
	int rc;

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

	down_write(&htable_rw_lock);
	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(path);
	if (!IS_ERR(ce)) {
		if (!cache_entry_expired(ce)) {
			dump_ce(ce);
			up_write(&htable_rw_lock);
			return 0;
		}
	} else {
		newent = true;
		if (!force_refresh && !cache_entry_expired(ce))
			return ce;
	} else if (PTR_ERR(ce) != -ENOENT) {
		up_read(&htable_rw_lock);
		return ce;
	}

	/*
	 * Either the entry was not found, or it is expired.
	 * Unlock shared access as we don't want to hold any locks while getting
	 * a new referral.  The @ses used for performing the I/O could be
	 * reconnecting and it acquires @htable_rw_lock to look up the dfs cache
	 * in order to failover -- if necessary.
	 */
	up_read(&htable_rw_lock);

	/*
	 * Either the entry was not found, or it is expired, or it is a forced
	 * refresh.
	 * Request a new DFS referral in order to create or update a cache entry.
	 */
	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
	if (rc)
		goto out_unlock;
	if (rc) {
		ce = ERR_PTR(rc);
		goto out;
	}

	dump_refs(refs, numrefs);

	if (!newent) {
	down_write(&htable_rw_lock);
	/* Re-check as another task might have it added or refreshed already */
	ce = lookup_cache_entry(path);
	if (!IS_ERR(ce)) {
		if (force_refresh || cache_entry_expired(ce)) {
			rc = update_cache_entry_locked(ce, refs, numrefs);
		goto out_unlock;
			if (rc)
				ce = ERR_PTR(rc);
		}
	} else if (PTR_ERR(ce) == -ENOENT) {
		ce = add_cache_entry_locked(refs, numrefs);
	}

	rc = add_cache_entry_locked(refs, numrefs);

out_unlock:
	if (IS_ERR(ce)) {
		up_write(&htable_rw_lock);
		goto out;
	}

	downgrade_write(&htable_rw_lock);
out:
	free_dfs_info_array(refs, numrefs);
	return rc;
	return ce;
}

/*
@@ -878,7 +908,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)
		}
		it->it_path_consumed = t->path_consumed;

		if (ce->tgthint == t)
		if (READ_ONCE(ce->tgthint) == t)
			list_add(&it->it_list, head);
		else
			list_add_tail(&it->it_list, head);
@@ -931,15 +961,8 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
	if (IS_ERR(npath))
		return PTR_ERR(npath);

	rc = cache_refresh_path(xid, ses, npath);
	if (rc)
		goto out_free_path;

	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(npath);
	ce = cache_refresh_path(xid, ses, npath, false);
	if (IS_ERR(ce)) {
		up_read(&htable_rw_lock);
		rc = PTR_ERR(ce);
		goto out_free_path;
	}
@@ -1002,72 +1025,6 @@ int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
	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
 * @cp: codepage
 * @remap: type of character remapping for paths
 * @path: path to lookup in DFS referral cache
 * @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,
			     const struct nls_table *cp, int remap, const char *path,
			     const struct dfs_cache_tgt_iterator *it)
{
	int rc;
	const char *npath;
	struct cache_entry *ce;
	struct cache_dfs_tgt *t;

	npath = dfs_cache_canonical_path(path, cp, remap);
	if (IS_ERR(npath))
		return PTR_ERR(npath);

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

	rc = cache_refresh_path(xid, ses, npath);
	if (rc)
		goto out_free_path;

	down_write(&htable_rw_lock);

	ce = lookup_cache_entry(npath);
	if (IS_ERR(ce)) {
		rc = PTR_ERR(ce);
		goto out_unlock;
	}

	t = ce->tgthint;

	if (likely(!strcasecmp(it->it_name, t->name)))
		goto out_unlock;

	list_for_each_entry(t, &ce->tlist, list) {
		if (!strcasecmp(t->name, it->it_name)) {
			ce->tgthint = t;
			cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
				 it->it_name);
			break;
		}
	}

out_unlock:
	up_write(&htable_rw_lock);
out_free_path:
	kfree(npath);
	return rc;
}

/**
 * dfs_cache_noreq_update_tgthint - update target hint of a DFS cache entry
 * without sending any requests to the currently connected server.
@@ -1092,21 +1049,20 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt

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

	if (!down_write_trylock(&htable_rw_lock))
		return;
	down_read(&htable_rw_lock);

	ce = lookup_cache_entry(path);
	if (IS_ERR(ce))
		goto out_unlock;

	t = ce->tgthint;
	t = READ_ONCE(ce->tgthint);

	if (unlikely(!strcasecmp(it->it_name, t->name)))
		goto out_unlock;

	list_for_each_entry(t, &ce->tlist, list) {
		if (!strcasecmp(t->name, it->it_name)) {
			ce->tgthint = t;
			WRITE_ONCE(ce->tgthint, t);
			cifs_dbg(FYI, "%s: new target hint: %s\n", __func__,
				 it->it_name);
			break;
@@ -1114,7 +1070,7 @@ void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt
	}

out_unlock:
	up_write(&htable_rw_lock);
	up_read(&htable_rw_lock);
}

/**
@@ -1320,35 +1276,37 @@ static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, c
 * 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)
static void mark_for_reconnect_if_needed(struct TCP_Server_Info *server,
					 struct dfs_cache_tgt_list *old_tl,
					 struct dfs_cache_tgt_list *new_tl)
{
	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))
	struct dfs_cache_tgt_iterator *oit, *nit;

	for (oit = dfs_cache_get_tgt_iterator(old_tl); oit;
	     oit = dfs_cache_get_next_tgt(old_tl, oit)) {
		for (nit = dfs_cache_get_tgt_iterator(new_tl); nit;
		     nit = dfs_cache_get_next_tgt(new_tl, nit)) {
			if (target_share_equal(server,
					       dfs_cache_get_tgt_name(oit),
					       dfs_cache_get_tgt_name(nit)))
				return;
		}
	}

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

/* Refresh dfs referral of tcon and mark it for reconnect if needed */
static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_refresh)
{
	struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
	struct dfs_cache_tgt_list old_tl = DFS_CACHE_TGT_LIST_INIT(old_tl);
	struct dfs_cache_tgt_list new_tl = DFS_CACHE_TGT_LIST_INIT(new_tl);
	struct cifs_ses *ses = CIFS_DFS_ROOT_SES(tcon->ses);
	struct cifs_tcon *ipc = ses->tcon_ipc;
	struct dfs_info3_param *refs = NULL;
	bool needs_refresh = false;
	struct cache_entry *ce;
	unsigned int xid;
	int numrefs = 0;
	int rc = 0;

	xid = get_xid();
@@ -1357,9 +1315,8 @@ static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_r
	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);
		rc = get_targets(ce, &old_tl);
		cifs_dbg(FYI, "%s: get_targets: %d\n", __func__, rc);
	}
	up_read(&htable_rw_lock);

@@ -1376,26 +1333,18 @@ static int __refresh_tcon(const char *path, struct cifs_tcon *tcon, bool force_r
	}
	spin_unlock(&ipc->tc_lock);

	rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
	if (!rc) {
		/* Create or update a cache entry with the new referral */
		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);
	ce = cache_refresh_path(xid, ses, path, true);
	if (!IS_ERR(ce)) {
		rc = get_targets(ce, &new_tl);
		up_read(&htable_rw_lock);
		cifs_dbg(FYI, "%s: get_targets: %d\n", __func__, rc);
		mark_for_reconnect_if_needed(tcon->ses->server, &old_tl, &new_tl);
	}

out:
	free_xid(xid);
	dfs_cache_free_tgts(&tl);
	free_dfs_info_array(refs, numrefs);
	dfs_cache_free_tgts(&old_tl);
	dfs_cache_free_tgts(&new_tl);
	return rc;
}

+0 −3
Original line number Diff line number Diff line
@@ -35,9 +35,6 @@ int dfs_cache_find(const unsigned int xid, struct cifs_ses *ses, const struct nl
		   struct dfs_cache_tgt_list *tgt_list);
int dfs_cache_noreq_find(const char *path, struct dfs_info3_param *ref,
			 struct dfs_cache_tgt_list *tgt_list);
int dfs_cache_update_tgthint(const unsigned int xid, struct cifs_ses *ses,
			     const struct nls_table *cp, int remap, const char *path,
			     const struct dfs_cache_tgt_iterator *it);
void dfs_cache_noreq_update_tgthint(const char *path, const struct dfs_cache_tgt_iterator *it);
int dfs_cache_get_tgt_referral(const char *path, const struct dfs_cache_tgt_iterator *it,
			       struct dfs_info3_param *ref);
+9 −6
Original line number Diff line number Diff line
@@ -4163,12 +4163,15 @@ smb2_readv_callback(struct mid_q_entry *mid)
				(struct smb2_hdr *)rdata->iov[0].iov_base;
	struct cifs_credits credits = { .value = 0, .instance = 0 };
	struct smb_rqst rqst = { .rq_iov = &rdata->iov[1],
				 .rq_nvec = 1,
				 .rq_pages = rdata->pages,
				 .rq_offset = rdata->page_offset,
				 .rq_npages = rdata->nr_pages,
				 .rq_pagesz = rdata->pagesz,
				 .rq_tailsz = rdata->tailsz };
				 .rq_nvec = 1, };

	if (rdata->got_bytes) {
		rqst.rq_pages = rdata->pages;
		rqst.rq_offset = rdata->page_offset;
		rqst.rq_npages = rdata->nr_pages;
		rqst.rq_pagesz = rdata->pagesz;
		rqst.rq_tailsz = rdata->tailsz;
	}

	WARN_ONCE(rdata->server != mid->server,
		  "rdata server %p != mid server %p",